// Local js definitions: /* global addClass, getSettingValue, hasClass, searchState */ /* global onEach, onEachLazy, removeClass */ "use strict"; // Get a value from the rustdoc-vars div, which is used to convey data from // Rust to the JS. If there is no such element, return null. function getVar(name) { const el = document.getElementById("rustdoc-vars"); if (el) { return el.attributes["data-" + name].value; } else { return null; } } // 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 mobileLocationTitle = document.querySelector(".mobile-topbar h2"); const locationTitle = document.querySelector(".sidebar h2.location"); if (mobileLocationTitle && locationTitle) { mobileLocationTitle.innerHTML = locationTitle.innerHTML; } } // 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 loadCss(cssUrl) { const link = document.createElement("link"); link.href = cssUrl; link.rel = "stylesheet"; 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. loadCss(getVar("static-root-path") + getVar("settings-css")); loadScript(getVar("static-root-path") + getVar("settings-js")); }; 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; }, hideResults: () => { switchDisplayedElement(null); document.title = searchState.titleBeforeSearch; // We also remove the query parameter from the URL. if (browserSupportsHistoryApi()) { history.replaceState(null, window.currentCrate + " - Rust", getNakedUrl() + window.location.hash); } }, 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); }, }; function getPageId() { if (window.location.hash) { const tmp = window.location.hash.replace(/^#/, ""); if (tmp.length > 0) { return tmp; } } return null; } 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. if (savedHash !== window.location.hash) { savedHash = window.location.hash; if (savedHash.length === 0) { return; } expandSection(savedHash.slice(1)); // we remove the '#' } } 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(); switchDisplayedElement(null); if (browserSupportsHistoryApi()) { history.replaceState(null, window.currentCrate + " - Rust", getNakedUrl() + window.location.hash); } 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 (`