From cf1e854e78d641f00f2bc09f19151b4a2dcdf7de Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Wed, 20 Nov 2024 16:35:26 -0700 Subject: [PATCH 1/2] rustdoc: use a button instead of a bar for search This is a response to complaints that the header area takes up too much vertical space, forcing the user to scroll more than they ought to need. It also adds a reasonable way to pick the crate name before actually searching, which was also a feature that people ask for. --- src/librustdoc/html/static/css/rustdoc.css | 90 +++++++++------ src/librustdoc/html/static/js/main.js | 91 ++++++++++++--- src/librustdoc/html/static/js/search.js | 128 ++++++++++++++------- src/librustdoc/html/static/js/storage.js | 30 +---- src/librustdoc/html/templates/page.html | 3 - 5 files changed, 222 insertions(+), 120 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 66a8a19892886..9e3870d82eace 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -175,6 +175,9 @@ h1, h2, h3, h4 { more aggressively when we want them to. */ overflow-wrap: anywhere; } +.search-results-main-heading nav.sub { + grid-area: main-heading-h1; +} .main-heading { position: relative; display: grid; @@ -195,6 +198,16 @@ h1, h2, h3, h4 { align-items: end; padding-top: 5px; } +.search-switcher { + grid-area: main-heading-breadcrumbs; + line-height: 1.25; + display: flex; + flex-wrap: wrap; + color: var(--main-color); + align-items: baseline; + white-space: nowrap; + margin-top: -1px; +} .rustdoc-breadcrumbs a { padding: 4px 0; margin: -4px 0; @@ -249,6 +262,7 @@ rustdoc-toolbar, summary.hideme, .scraped-example-list, .rustdoc-breadcrumbs, +.search-switcher, /* This selector is for the items listed in the "all items" page. */ ul.all-items { font-family: "Fira Sans", Arial, NanumBarunGothic, sans-serif; @@ -996,16 +1010,15 @@ div.where { nav.sub { flex-grow: 1; flex-flow: row nowrap; - margin: 4px 0 0 0; display: flex; - align-items: center; + align-items: start; + margin-top: 4px; } .search-form { position: relative; display: flex; height: 34px; flex-grow: 1; - margin-bottom: 4px; } .src nav.sub { margin: 0 0 -10px 0; @@ -1109,21 +1122,6 @@ table, padding-right: 1.25rem; } -.search-results-title { - margin-top: 0; - white-space: nowrap; - /* flex layout allows shrinking the + +
`; + out.insertBefore(hdr, searchState.outputElement()); + el = document.getElementsByClassName("search-input")[0]; + } + return el; + }, + containerElement: () => { let el = document.getElementById("search"); if (!el) { el = document.createElement("section"); @@ -251,6 +279,16 @@ function preLoadCss(cssUrl) { } return el; }, + outputElement: () => { + const container = searchState.containerElement(); + let el = container.querySelector(".search-out"); + if (!el) { + el = document.createElement("div"); + el.className = "search-out"; + container.appendChild(el); + } + return el; + }, title: document.title, titleBeforeSearch: document.title, timeout: null, @@ -268,22 +306,38 @@ function preLoadCss(cssUrl) { searchState.timeout = null; } }, - isDisplayed: () => searchState.outputElement().parentElement.id === ALTERNATIVE_DISPLAY_ID, + isDisplayed: () => searchState.containerElement().parentElement.id === + ALTERNATIVE_DISPLAY_ID, // Sets the focus on the search bar at the top of the page focus: () => { - searchState.input.focus(); + searchState.showResults(); + searchState.inputElement().focus(); }, // Removes the focus from the search bar. defocus: () => { - searchState.input.blur(); + searchState.inputElement().blur(); + }, + toggle: () => { + if (searchState.isDisplayed()) { + searchState.defocus(); + searchState.hideResults(); + } else { + searchState.focus(); + } }, - showResults: search => { - if (search === null || typeof search === "undefined") { - search = searchState.outputElement(); + showResults: () => { + document.title = searchState.title; + if (searchState.isDisplayed()) { + return; } + const search = searchState.containerElement(); switchDisplayedElement(search); searchState.mouseMovedAfterSearch = false; - document.title = searchState.title; + const btn = document.querySelector("#search-button a"); + if (browserSupportsHistoryApi() && btn && + searchState.getQueryStringParams().search === undefined) { + history.pushState(null, "", btn.href); + } }, removeQueryParameters: () => { // We change the document title. @@ -309,11 +363,8 @@ function preLoadCss(cssUrl) { return params; }, setup: () => { - const search_input = searchState.input; - if (!searchState.input) { - return; - } let searchLoaded = false; + const search_input = searchState.inputElement(); // If you're browsing the nightly docs, the page might need to be refreshed for the // search to work because the hash of the JS scripts might have changed. function sendSearchForm() { @@ -333,8 +384,16 @@ function preLoadCss(cssUrl) { loadSearch(); }); - if (search_input.value !== "") { - loadSearch(); + const btn = document.getElementById("search-button"); + if (btn) { + btn.onclick = event => { + if (event.ctrlKey || event.altKey || event.metaKey) { + return; + } + event.preventDefault(); + searchState.toggle(); + loadSearch(); + }; } const params = searchState.getQueryStringParams(); @@ -346,7 +405,7 @@ function preLoadCss(cssUrl) { setLoadingSearch: () => { const search = searchState.outputElement(); search.innerHTML = "

" + searchState.loadingText + "

"; - searchState.showResults(search); + searchState.showResults(); }, descShards: new Map(), loadDesc: async function({descShard, descIndex}) { diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 9e5cf49721139..790f6976d365b 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -4404,11 +4404,63 @@ ${item.displayPath}${name}\ }); } else if (query.error === null) { output.className = "search-failed" + extraClass; - output.innerHTML = "No results :(
" + + if (query.userQuery === "") { + // Find three random items. First, a fully random item, + // which will serve as the path example. + let rndIdx = Math.floor(Math.random() * (docSearch.searchIndex.length - 1)); + let rndItem = docSearch.searchIndex[rndIdx]; + let rndPath = rndItem.path; + if (rndItem.parent) { + rndPath += "::" + rndItem.parent.name; + } + if (rndPath !== "") { + rndPath += "::"; + } + rndPath += rndItem.name; + // Secondly, a random item that is also a function-like, + // which will be shown as a type signature. This may be + // the same item as the first, or it might just be close. + let startIdx = rndIdx; + let rndType = null; + if (!rndItem.type) { + rndIdx = (rndIdx + 1) % docSearch.searchIndex.length; + while (rndIdx !== startIdx && !rndItem.type) { + rndItem = docSearch.searchIndex[rndIdx]; + rndIdx = (rndIdx + 1) % docSearch.searchIndex.length; + } + } + if (rndItem.type) { + rndType = (await docSearch.formatDisplayTypeSignature(rndItem, "sig")).type; + } + // Finally, a separate random item, shown only as a name. + rndIdx = Math.floor(Math.random() * (docSearch.searchIndex.length - 1)); + rndItem = docSearch.searchIndex[rndIdx]; + startIdx = rndIdx; + rndIdx = (rndIdx + 1) % docSearch.searchIndex.length; + while (rndItem.type && rndIdx !== startIdx) { + rndIdx = (rndIdx + 1) % docSearch.searchIndex.length; + rndItem = docSearch.searchIndex[rndIdx]; + } + const rndName = rndItem.name; + output.innerHTML = "Example searches:"; + } else { + output.innerHTML = "No results :(
" + "Try on DuckDuckGo?

" + - "Or try looking in one of these: