Consolidate search-related vars and functions.

This allows sharing across main.js and search.js without exporting too
many symbols into the global namespace.
This commit is contained in:
Jacob Hoffman-Andrews 2021-04-12 23:50:18 -07:00
parent 276ee6f542
commit 95d652ebce
2 changed files with 171 additions and 179 deletions

View file

@ -80,10 +80,6 @@ function getSearchInput() {
return document.getElementsByClassName("search-input")[0];
}
function getSearchElement() {
return document.getElementById("search");
}
var THEME_PICKER_ELEMENT_ID = "theme-picker";
var THEMES_ELEMENT_ID = "theme-choices";
@ -172,30 +168,123 @@ function hideThemeButtonState() {
(function() {
"use strict";
var disableShortcuts = getSettingValue("disable-shortcuts") === "true";
window.search_input = getSearchInput();
var searchTimeout = null;
var toggleAllDocsId = "toggle-all-docs";
// On the search screen, so you remain on the last tab you opened.
//
// 0 for "In Names"
// 1 for "In Parameters"
// 2 for "In Return Types"
window.currentTab = 0;
window.mouseMovedAfterSearch = true;
var titleBeforeSearch = document.title;
window.searchTitle = null;
window.clearInputTimeout = function() {
if (searchTimeout !== null) {
clearTimeout(searchTimeout);
searchTimeout = null;
window.searchState = {
loadingText: "Loading search results...",
input: getSearchInput(),
outputElement: function() {
return document.getElementById("search");
},
title: null,
titleBeforeSearch: document.title,
timeout: null,
// On the search screen, so you remain on the last tab you opened.
//
// 0 for "In Names"
// 1 for "In Parameters"
// 2 for "In Return Types"
currentTab: 0,
mouseMovedAfterSearch: true,
clearInputTimeout: function() {
if (searchState.timeout !== null) {
clearTimeout(searchState.timeout);
searchState.timeout = null;
}
},
showResults: function(search) {
if (search === null || typeof search === 'undefined') {
search = searchState.outputElement();
}
addClass(main, "hidden");
removeClass(search, "hidden");
searchState.mouseMovedAfterSearch = false;
document.title = searchState.title;
},
hideResults: function(search) {
if (search === null || typeof search === 'undefined') {
search = searchState.outputElement();
}
addClass(search, "hidden");
removeClass(main, "hidden");
document.title = searchState.titleBeforeSearch;
// We also remove the query parameter from the URL.
if (searchState.browserSupportsHistoryApi()) {
history.replaceState("", window.currentCrate + " - Rust",
getNakedUrl() + window.location.hash);
}
},
getQueryStringParams: function() {
var params = {};
window.location.search.substring(1).split("&").
map(function(s) {
var pair = s.split("=");
params[decodeURIComponent(pair[0])] =
typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
});
return params;
},
putBackSearch: function(search_input) {
var search = searchState.outputElement();
if (search_input.value !== "" && hasClass(search, "hidden")) {
searchState.showResults(search);
if (searchState.browserSupportsHistoryApi()) {
var extra = "?search=" + encodeURIComponent(search_input.value);
history.replaceState(search_input.value, "",
getNakedUrl() + extra + window.location.hash);
}
document.title = searchState.title;
}
},
browserSupportsHistoryApi: function() {
return window.history && typeof window.history.pushState === "function";
},
setupLoader: function() {
function loadScript(url) {
var script = document.createElement('script');
script.src = url;
document.head.append(script);
}
var searchLoaded = false;
function loadSearch() {
if (!searchLoaded) {
searchLoaded = true;
loadScript(window.searchJS);
}
}
// `crates{version}.js` should always be loaded before this script, so we can use it safely.
addSearchOptions(window.ALL_CRATES);
addSidebarCrates(window.ALL_CRATES);
searchState.input.addEventListener("focus", function() {
searchState.input.origPlaceholder = searchState.input.placeholder;
searchState.input.placeholder = "Type your search here.";
loadSearch();
});
searchState.input.addEventListener("blur", function() {
searchState.input.placeholder = searchState.input.origPlaceholder;
});
searchState.input.removeAttribute('disabled');
var crateSearchDropDown = document.getElementById("crate-search");
// `crateSearchDropDown` can be null in case there is only crate because in that case, the
// crate filter dropdown is removed.
if (crateSearchDropDown) {
crateSearchDropDown.addEventListener("focus", loadSearch);
}
var params = searchState.getQueryStringParams();
if (params.search !== undefined) {
loadSearch();
}
},
};
if (searchState.input) {
searchState.input.onfocus = function() {
searchState.putBackSearch(this);
};
}
function getPageId() {
if (window.location.hash) {
var tmp = window.location.hash.replace(/^#/, "");
@ -237,61 +326,23 @@ function hideThemeButtonState() {
document.getElementsByTagName("body")[0].style.marginTop = "";
}
window.showSearchResults = function(search) {
if (search === null || typeof search === 'undefined') {
search = getSearchElement();
}
addClass(main, "hidden");
removeClass(search, "hidden");
mouseMovedAfterSearch = false;
document.title = searchTitle;
};
window.hideSearchResults = function(search) {
if (search === null || typeof search === 'undefined') {
search = getSearchElement();
}
addClass(search, "hidden");
removeClass(main, "hidden");
document.title = titleBeforeSearch;
// We also remove the query parameter from the URL.
if (browserSupportsHistoryApi()) {
history.replaceState("", window.currentCrate + " - Rust",
getNakedUrl() + window.location.hash);
}
};
window.getQueryStringParams = function() {
var params = {};
window.location.search.substring(1).split("&").
map(function(s) {
var pair = s.split("=");
params[decodeURIComponent(pair[0])] =
typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]);
});
return params;
};
window.browserSupportsHistoryApi = function() {
return window.history && typeof window.history.pushState === "function";
};
function isHidden(elem) {
return elem.offsetHeight === 0;
}
var toggleAllDocsId = "toggle-all-docs";
var main = document.getElementById("main");
var savedHash = "";
function handleHashes(ev) {
var elem;
var search = getSearchElement();
var search = searchState.outputElement();
if (ev !== null && search && !hasClass(search, "hidden") && ev.newURL) {
// This block occurs when clicking on an element in the navbar while
// in a search.
hideSearchResults(search);
searchState.hideResults(search);
var hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1);
if (browserSupportsHistoryApi()) {
if (searchState.browserSupportsHistoryApi()) {
// `window.location.search`` contains all the query parameters, not just `search`.
history.replaceState(hash, "",
getNakedUrl() + window.location.search + "#" + hash);
@ -432,18 +483,19 @@ function hideThemeButtonState() {
function handleEscape(ev) {
var help = getHelpElement(false);
var search = getSearchElement();
var search = searchState.outputElement();
if (hasClass(help, "hidden") === false) {
displayHelp(false, ev, help);
} else if (hasClass(search, "hidden") === false) {
clearInputTimeout();
searchState.clearInputTimeout();
ev.preventDefault();
hideSearchResults(search);
searchState.hideResults(search);
}
defocusSearchBar();
hideThemeButtonState();
}
var disableShortcuts = getSettingValue("disable-shortcuts") === "true";
function handleShortcut(ev) {
// Don't interfere with browser shortcuts
if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts === true) {
@ -553,7 +605,9 @@ function hideThemeButtonState() {
document.addEventListener("keypress", handleShortcut);
document.addEventListener("keydown", handleShortcut);
document.addEventListener("mousemove", function() { mouseMovedAfterSearch = true; });
document.addEventListener("mousemove", function() {
searchState.mouseMovedAfterSearch = true;
});
var handleSourceHighlight = (function() {
var prev_line_id = 0;
@ -561,7 +615,7 @@ function hideThemeButtonState() {
var set_fragment = function(name) {
var x = window.scrollX,
y = window.scrollY;
if (browserSupportsHistoryApi()) {
if (searchState.browserSupportsHistoryApi()) {
history.replaceState(null, null, "#" + name);
highlightSourceLines();
} else {
@ -1366,34 +1420,11 @@ function hideThemeButtonState() {
};
});
window.putBackSearch = function(search_input) {
var search = getSearchElement();
if (search_input.value !== "" && hasClass(search, "hidden")) {
showSearchResults(search);
if (browserSupportsHistoryApi()) {
var extra = "?search=" + encodeURIComponent(search_input.value);
history.replaceState(search_input.value, "",
getNakedUrl() + extra + window.location.hash);
}
document.title = searchTitle;
}
};
function getSearchLoadingText() {
return "Loading search results...";
}
if (search_input) {
search_input.onfocus = function() {
putBackSearch(this);
};
}
var params = getQueryStringParams();
var params = searchState.getQueryStringParams();
if (params && params.search) {
var search = getSearchElement();
search.innerHTML = "<h3 style=\"text-align: center;\">" + getSearchLoadingText() + "</h3>";
showSearchResults(search);
var search = searchState.outputElement();
search.innerHTML = "<h3 style=\"text-align: center;\">" + searchState.loadingText + "</h3>";
searchState.showResults(search);
}
var sidebar_menu = document.getElementsByClassName("sidebar-menu")[0];
@ -1509,55 +1540,14 @@ function hideThemeButtonState() {
container.appendChild(div_infos);
popup.appendChild(container);
insertAfter(popup, getSearchElement());
insertAfter(popup, searchState.outputElement());
// So that it's only built once and then it'll do nothing when called!
buildHelperPopup = function() {};
}
function loadScript(url) {
var script = document.createElement('script');
script.src = url;
document.head.append(script);
}
function setupSearchLoader() {
var searchLoaded = false;
function loadSearch() {
if (!searchLoaded) {
searchLoaded = true;
loadScript(window.searchJS);
}
}
// `crates{version}.js` should always be loaded before this script, so we can use it safely.
addSearchOptions(window.ALL_CRATES);
addSidebarCrates(window.ALL_CRATES);
search_input.addEventListener("focus", function() {
search_input.origPlaceholder = search_input.placeholder;
search_input.placeholder = "Type your search here.";
loadSearch();
});
search_input.addEventListener("blur", function() {
search_input.placeholder = search_input.origPlaceholder;
});
search_input.removeAttribute('disabled');
var crateSearchDropDown = document.getElementById("crate-search");
// `crateSearchDropDown` can be null in case there is only crate because in that case, the
// crate filter dropdown is removed.
if (crateSearchDropDown) {
crateSearchDropDown.addEventListener("focus", loadSearch);
}
var params = getQueryStringParams();
if (params.search !== undefined) {
loadSearch();
}
}
onHashChange(null);
window.onhashchange = onHashChange;
setupSearchLoader();
searchState.setupLoader();
}());
function copy_path(but) {

View file

@ -39,7 +39,7 @@ var TY_KEYWORD = itemTypes.indexOf("keyword");
// In the search display, allows to switch between tabs.
function printTab(nb) {
if (nb === 0 || nb === 1 || nb === 2) {
currentTab = nb;
searchState.currentTab = nb;
}
var nb_copy = nb;
onEachLazy(document.getElementById("titles").childNodes, function(elem) {
@ -114,14 +114,14 @@ function initSearch(rawSearchIndex) {
var NO_TYPE_FILTER = -1;
var currentResults, index, searchIndex;
var ALIASES = {};
var params = getQueryStringParams();
var params = searchState.getQueryStringParams();
// Populate search bar with query string search term when provided,
// but only if the input bar is empty. This avoid the obnoxious issue
// where you start trying to do a search, and the index loads, and
// suddenly your search is gone!
if (search_input.value === "") {
search_input.value = params.search || "";
if (searchState.input.value === "") {
searchState.input.value = params.search || "";
}
/**
@ -898,12 +898,12 @@ function initSearch(rawSearchIndex) {
}
dst = dst[0];
if (window.location.pathname === dst.pathname) {
hideSearchResults();
searchState.hideResults();
document.location.href = dst.href;
}
};
var mouseover_func = function(e) {
if (mouseMovedAfterSearch) {
if (searchState.mouseMovedAfterSearch) {
var el = e.target;
// to retrieve the real "owner" of the event.
while (el.tagName !== "TR") {
@ -927,7 +927,7 @@ function initSearch(rawSearchIndex) {
});
});
search_input.onkeydown = function(e) {
searchState.input.onkeydown = function(e) {
// "actives" references the currently highlighted item in each search tab.
// Each array in "actives" represents a tab.
var actives = [[], [], []];
@ -940,6 +940,7 @@ function initSearch(rawSearchIndex) {
current += 1;
});
var currentTab = searchState.currentTab;
if (e.which === 38) { // up
if (e.ctrlKey) { // Going through result tabs.
printTab(currentTab > 0 ? currentTab - 1 : 2);
@ -1104,7 +1105,7 @@ function initSearch(rawSearchIndex) {
}
function makeTabHeader(tabNb, text, nbElems) {
if (currentTab === tabNb) {
if (searchState.currentTab === tabNb) {
return "<button class=\"selected\">" + text +
" <div class=\"count\">(" + nbElems + ")</div></button>";
}
@ -1112,13 +1113,13 @@ function initSearch(rawSearchIndex) {
}
function showResults(results) {
var search = getSearchElement();
var search = searchState.outputElement();
if (results.others.length === 1
&& getSettingValue("go-to-only-result") === "true"
// By default, the search DOM element is "empty" (meaning it has no children not
// text content). Once a search has been run, it won't be empty, even if you press
// ESC or empty the search input (which also "cancels" the search).
&& (!search.firstChild || search.firstChild.innerText !== getSearchLoadingText()))
&& (!search.firstChild || search.firstChild.innerText !== searchState.loadingText))
{
var elem = document.createElement("a");
elem.href = results.others[0].href;
@ -1128,7 +1129,7 @@ function initSearch(rawSearchIndex) {
elem.click();
return;
}
var query = getQuery(search_input.value);
var query = getQuery(searchState.input.value);
currentResults = query.id;
@ -1139,6 +1140,7 @@ function initSearch(rawSearchIndex) {
// Navigate to the relevant tab if the current tab is empty, like in case users search
// for "-> String". If they had selected another tab previously, they have to click on
// it again.
var currentTab = searchState.currentTab;
if ((currentTab === 0 && ret_others[1] === 0) ||
(currentTab === 1 && ret_in_args[1] === 0) ||
(currentTab === 2 && ret_returned[1] === 0)) {
@ -1161,7 +1163,7 @@ function initSearch(rawSearchIndex) {
ret_others[0] + ret_in_args[0] + ret_returned[0] + "</div>";
search.innerHTML = output;
showSearchResults(search);
searchState.showResults(search);
initSearchNav();
var elems = document.getElementById("titles").childNodes;
elems[0].onclick = function() { printTab(0); };
@ -1252,8 +1254,8 @@ function initSearch(rawSearchIndex) {
}
function search(e, forced) {
var params = getQueryStringParams();
var query = getQuery(search_input.value.trim());
var params = searchState.getQueryStringParams();
var query = getQuery(searchState.input.value.trim());
if (e) {
e.preventDefault();
@ -1264,17 +1266,17 @@ function initSearch(rawSearchIndex) {
}
if (forced !== true && query.id === currentResults) {
if (query.query.length > 0) {
putBackSearch(search_input);
searchState.putBackSearch(searchState.input);
}
return;
}
// Update document title to maintain a meaningful browser history
searchTitle = "Results for " + query.query + " - Rust";
searchState.title = "Results for " + query.query + " - Rust";
// Because searching is incremental by character, only the most
// recent search query is added to the browser history.
if (browserSupportsHistoryApi()) {
if (searchState.browserSupportsHistoryApi()) {
var newURL = getNakedUrl() + "?search=" + encodeURIComponent(query.raw) +
window.location.hash;
if (!history.state && !params.search) {
@ -1408,37 +1410,37 @@ function initSearch(rawSearchIndex) {
function registerSearchEvents() {
var searchAfter500ms = function() {
clearInputTimeout();
if (search_input.value.length === 0) {
if (browserSupportsHistoryApi()) {
searchState.clearInputTimeout();
if (searchState.input.value.length === 0) {
if (searchState.browserSupportsHistoryApi()) {
history.replaceState("", window.currentCrate + " - Rust",
getNakedUrl() + window.location.hash);
}
hideSearchResults();
searchState.hideResults();
} else {
searchTimeout = setTimeout(search, 500);
searchState.timeout = setTimeout(search, 500);
}
};
search_input.onkeyup = searchAfter500ms;
search_input.oninput = searchAfter500ms;
searchState.input.onkeyup = searchAfter500ms;
searchState.input.oninput = searchAfter500ms;
document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
e.preventDefault();
clearInputTimeout();
searchState.clearInputTimeout();
search();
};
search_input.onchange = function(e) {
searchState.input.onchange = function(e) {
if (e.target !== document.activeElement) {
// To prevent doing anything when it's from a blur event.
return;
}
// Do NOT e.preventDefault() here. It will prevent pasting.
clearInputTimeout();
searchState.clearInputTimeout();
// zero-timeout necessary here because at the time of event handler execution the
// pasted content is not in the input field yet. Shouldnt make any difference for
// change, though.
setTimeout(search, 0);
};
search_input.onpaste = search_input.onchange;
searchState.input.onpaste = searchState.input.onchange;
var selectCrate = document.getElementById("crate-search");
if (selectCrate) {
@ -1450,12 +1452,12 @@ function initSearch(rawSearchIndex) {
// Push and pop states are used to add search results to the browser
// history.
if (browserSupportsHistoryApi()) {
if (searchState.browserSupportsHistoryApi()) {
// Store the previous <title> so we can revert back to it later.
var previousTitle = document.title;
window.addEventListener("popstate", function(e) {
var params = getQueryStringParams();
var params = searchState.getQueryStringParams();
// Revert to the previous title manually since the History
// API ignores the title parameter.
document.title = previousTitle;
@ -1468,7 +1470,7 @@ function initSearch(rawSearchIndex) {
// nothing there, which lets you really go back to a
// previous state with nothing in the bar.
if (params.search && params.search.length > 0) {
search_input.value = params.search;
searchState.input.value = params.search;
// Some browsers fire "onpopstate" for every page load
// (Chrome), while others fire the event only when actually
// popping a state (Firefox), which is why search() is
@ -1476,10 +1478,10 @@ function initSearch(rawSearchIndex) {
// function.
search(e);
} else {
search_input.value = "";
searchState.input.value = "";
// When browsing back from search results the main page
// visibility must be reset.
hideSearchResults();
searchState.hideResults();
}
});
}
@ -1491,9 +1493,9 @@ function initSearch(rawSearchIndex) {
// that try to sync state between the URL and the search input. To work around it,
// do a small amount of re-init on page show.
window.onpageshow = function(){
var qSearch = getQueryStringParams().search;
if (search_input.value === "" && qSearch) {
search_input.value = qSearch;
var qSearch = searchState.getQueryStringParams().search;
if (searchState.input.value === "" && qSearch) {
searchState.input.value = qSearch;
}
search();
};
@ -1502,7 +1504,7 @@ function initSearch(rawSearchIndex) {
index = buildIndex(rawSearchIndex);
registerSearchEvents();
// If there's a search term in the URL, execute the search now.
if (getQueryStringParams().search) {
if (searchState.getQueryStringParams().search) {
search();
}
};