// Local js definitions: /* global addClass, getSettingValue, hasClass, searchState */ /* global onEach, onEachLazy, removeClass, getVar */ "use strict"; // The amount of time that the cursor must remain still over a hover target before // revealing a tooltip. // // https://www.nngroup.com/articles/timing-exposing-content/ window.RUSTDOC_TOOLTIP_HOVER_MS = 300; window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS = 450; // Given a basename (e.g. "storage") and an extension (e.g. ".js"), return a URL // for a resource under the root-path, with the resource-suffix. function resourcePath(basename, extension) { return getVar("root-path") + basename + getVar("resource-suffix") + extension; } function hideMain() { addClass(document.getElementById(MAIN_ID), "hidden"); } function showMain() { removeClass(document.getElementById(MAIN_ID), "hidden"); } function elemIsInParent(elem, parent) { while (elem && elem !== document.body) { if (elem === parent) { return true; } elem = elem.parentElement; } return false; } function blurHandler(event, parentElem, hideCallback) { if (!elemIsInParent(document.activeElement, parentElem) && !elemIsInParent(event.relatedTarget, parentElem) ) { hideCallback(); } } window.rootPath = getVar("root-path"); window.currentCrate = getVar("current-crate"); function setMobileTopbar() { // FIXME: It would be nicer to generate this text content directly in HTML, // but with the current code it's hard to get the right information in the right place. const mobileTopbar = document.querySelector(".mobile-topbar"); const locationTitle = document.querySelector(".sidebar h2.location"); if (mobileTopbar && locationTitle) { const mobileTitle = document.createElement("h2"); mobileTitle.innerHTML = locationTitle.innerHTML; mobileTopbar.appendChild(mobileTitle); } } // Gets the human-readable string for the virtual-key code of the // given KeyboardEvent, ev. // // This function is meant as a polyfill for KeyboardEvent#key, // since it is not supported in IE 11 or Chrome for Android. We also test for // KeyboardEvent#keyCode because the handleShortcut handler is // also registered for the keydown event, because Blink doesn't fire // keypress on hitting the Escape key. // // So I guess you could say things are getting pretty interoperable. function getVirtualKey(ev) { if ("key" in ev && typeof ev.key !== "undefined") { return ev.key; } const c = ev.charCode || ev.keyCode; if (c === 27) { return "Escape"; } return String.fromCharCode(c); } const MAIN_ID = "main-content"; const SETTINGS_BUTTON_ID = "settings-menu"; const ALTERNATIVE_DISPLAY_ID = "alternative-display"; const NOT_DISPLAYED_ID = "not-displayed"; const HELP_BUTTON_ID = "help-button"; function getSettingsButton() { return document.getElementById(SETTINGS_BUTTON_ID); } function getHelpButton() { return document.getElementById(HELP_BUTTON_ID); } // Returns the current URL without any query parameter or hash. function getNakedUrl() { return window.location.href.split("?")[0].split("#")[0]; } /** * This function inserts `newNode` after `referenceNode`. It doesn't work if `referenceNode` * doesn't have a parent node. * * @param {HTMLElement} newNode * @param {HTMLElement} referenceNode */ function insertAfter(newNode, referenceNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); } /** * This function creates a new `
` with the given `id` and `classes` if it doesn't already * exist. * * More information about this in `switchDisplayedElement` documentation. * * @param {string} id * @param {string} classes */ function getOrCreateSection(id, classes) { let el = document.getElementById(id); if (!el) { el = document.createElement("section"); el.id = id; el.className = classes; insertAfter(el, document.getElementById(MAIN_ID)); } return el; } /** * Returns the `
` element which contains the displayed element. * * @return {HTMLElement} */ function getAlternativeDisplayElem() { return getOrCreateSection(ALTERNATIVE_DISPLAY_ID, "content hidden"); } /** * Returns the `
` element which contains the not-displayed elements. * * @return {HTMLElement} */ function getNotDisplayedElem() { return getOrCreateSection(NOT_DISPLAYED_ID, "hidden"); } /** * To nicely switch between displayed "extra" elements (such as search results or settings menu) * and to alternate between the displayed and not displayed elements, we hold them in two different * `
` elements. They work in pair: one holds the hidden elements while the other * contains the displayed element (there can be only one at the same time!). So basically, we switch * elements between the two `
` elements. * * @param {HTMLElement} elemToDisplay */ function switchDisplayedElement(elemToDisplay) { const el = getAlternativeDisplayElem(); if (el.children.length > 0) { getNotDisplayedElem().appendChild(el.firstElementChild); } if (elemToDisplay === null) { addClass(el, "hidden"); showMain(); return; } el.appendChild(elemToDisplay); hideMain(); removeClass(el, "hidden"); } function browserSupportsHistoryApi() { return window.history && typeof window.history.pushState === "function"; } function preLoadCss(cssUrl) { // https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload const link = document.createElement("link"); link.href = cssUrl; link.rel = "preload"; link.as = "style"; document.getElementsByTagName("head")[0].appendChild(link); } (function() { const isHelpPage = window.location.pathname.endsWith("/help.html"); function loadScript(url) { const script = document.createElement("script"); script.src = url; document.head.append(script); } getSettingsButton().onclick = event => { if (event.ctrlKey || event.altKey || event.metaKey) { return; } window.hideAllModals(false); addClass(getSettingsButton(), "rotate"); event.preventDefault(); // Sending request for the CSS and the JS files at the same time so it will // hopefully be loaded when the JS will generate the settings content. loadScript(getVar("static-root-path") + getVar("settings-js")); // Pre-load all theme CSS files, so that switching feels seamless. // // When loading settings.html as a standalone page, the equivalent HTML is // generated in context.rs. setTimeout(() => { const themes = getVar("themes").split(","); for (const theme of themes) { // if there are no themes, do nothing // "".split(",") == [""] if (theme !== "") { preLoadCss(getVar("root-path") + theme + ".css"); } } }, 0); }; window.searchState = { loadingText: "Loading search results...", input: document.getElementsByClassName("search-input")[0], outputElement: () => { let el = document.getElementById("search"); if (!el) { el = document.createElement("section"); el.id = "search"; getNotDisplayedElem().appendChild(el); } return el; }, title: document.title, 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, // tab and back preserves the element that was focused. focusedByTab: [null, null, null], clearInputTimeout: () => { if (searchState.timeout !== null) { clearTimeout(searchState.timeout); searchState.timeout = null; } }, isDisplayed: () => searchState.outputElement().parentElement.id === ALTERNATIVE_DISPLAY_ID, // Sets the focus on the search bar at the top of the page focus: () => { searchState.input.focus(); }, // Removes the focus from the search bar. defocus: () => { searchState.input.blur(); }, showResults: search => { if (search === null || typeof search === "undefined") { search = searchState.outputElement(); } switchDisplayedElement(search); searchState.mouseMovedAfterSearch = false; document.title = searchState.title; }, removeQueryParameters: () => { // We change the document title. document.title = searchState.titleBeforeSearch; if (browserSupportsHistoryApi()) { history.replaceState(null, "", getNakedUrl() + window.location.hash); } }, hideResults: () => { switchDisplayedElement(null); // We also remove the query parameter from the URL. searchState.removeQueryParameters(); }, getQueryStringParams: () => { const params = {}; window.location.search.substring(1).split("&"). map(s => { const pair = s.split("="); params[decodeURIComponent(pair[0])] = typeof pair[1] === "undefined" ? null : decodeURIComponent(pair[1]); }); return params; }, setup: () => { const search_input = searchState.input; if (!searchState.input) { return; } let searchLoaded = false; function loadSearch() { if (!searchLoaded) { searchLoaded = true; loadScript(getVar("static-root-path") + getVar("search-js")); loadScript(resourcePath("search-index", ".js")); } } search_input.addEventListener("focus", () => { search_input.origPlaceholder = search_input.placeholder; search_input.placeholder = "Type your search here."; loadSearch(); }); if (search_input.value !== "") { loadSearch(); } const params = searchState.getQueryStringParams(); if (params.search !== undefined) { searchState.setLoadingSearch(); loadSearch(); } }, setLoadingSearch: () => { const search = searchState.outputElement(); search.innerHTML = "

" + searchState.loadingText + "

"; searchState.showResults(search); }, }; const toggleAllDocsId = "toggle-all-docs"; let savedHash = ""; function handleHashes(ev) { if (ev !== null && searchState.isDisplayed() && ev.newURL) { // This block occurs when clicking on an element in the navbar while // in a search. switchDisplayedElement(null); const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1); if (browserSupportsHistoryApi()) { // `window.location.search`` contains all the query parameters, not just `search`. history.replaceState(null, "", getNakedUrl() + window.location.search + "#" + hash); } const elem = document.getElementById(hash); if (elem) { elem.scrollIntoView(); } } // This part is used in case an element is not visible. const pageId = window.location.hash.replace(/^#/, ""); if (savedHash !== pageId) { savedHash = pageId; if (pageId !== "") { expandSection(pageId); } } } function onHashChange(ev) { // If we're in mobile mode, we should hide the sidebar in any case. hideSidebar(); handleHashes(ev); } function openParentDetails(elem) { while (elem) { if (elem.tagName === "DETAILS") { elem.open = true; } elem = elem.parentNode; } } function expandSection(id) { openParentDetails(document.getElementById(id)); } function handleEscape(ev) { searchState.clearInputTimeout(); searchState.hideResults(); ev.preventDefault(); searchState.defocus(); window.hideAllModals(true); // true = reset focus for tooltips } function handleShortcut(ev) { // Don't interfere with browser shortcuts const disableShortcuts = getSettingValue("disable-shortcuts") === "true"; if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) { return; } if (document.activeElement.tagName === "INPUT" && document.activeElement.type !== "checkbox" && document.activeElement.type !== "radio") { switch (getVirtualKey(ev)) { case "Escape": handleEscape(ev); break; } } else { switch (getVirtualKey(ev)) { case "Escape": handleEscape(ev); break; case "s": case "S": ev.preventDefault(); searchState.focus(); break; case "+": ev.preventDefault(); expandAllDocs(); break; case "-": ev.preventDefault(); collapseAllDocs(); break; case "?": showHelp(); break; default: break; } } } document.addEventListener("keypress", handleShortcut); document.addEventListener("keydown", handleShortcut); function addSidebarItems() { if (!window.SIDEBAR_ITEMS) { return; } const sidebar = document.getElementsByClassName("sidebar-elems")[0]; /** * Append to the sidebar a "block" of links - a heading along with a list (`