From 7376bac2ade82312a42f3d4572922dc2c6b9e3ea Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 12 Sep 2023 23:01:41 +0200 Subject: [PATCH] rustdoc search: Allow to filter on multiple crates --- src/librustdoc/html/static/css/rustdoc.css | 43 ++++-- src/librustdoc/html/static/css/settings.css | 6 +- src/librustdoc/html/static/css/themes/ayu.css | 1 + .../html/static/css/themes/dark.css | 1 + .../html/static/css/themes/light.css | 1 + src/librustdoc/html/static/js/main.js | 3 + src/librustdoc/html/static/js/search.js | 132 ++++++++++++++---- 7 files changed, 146 insertions(+), 41 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index da4da50106abb..3b8cf438c1dbf 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -785,15 +785,20 @@ table, border-radius: 4px; outline: none; cursor: pointer; - -moz-appearance: none; - -webkit-appearance: none; - /* Removes default arrow from firefox */ - text-indent: 0.01px; background-color: var(--main-background-color); color: inherit; line-height: 1.5; font-weight: 500; } +#crate-search-div .popover { + font-size: 1rem; + display: none; + max-width: 50vw; + min-width: 100px; +} +#crate-search-div .setting-line { + margin: 0.6em; +} #crate-search:hover, #crate-search:focus { border-color: var(--crate-search-hover-border); } @@ -823,8 +828,21 @@ so that we can apply CSS-filters to change the arrow color in themes */ #crate-search-div:hover::after, #crate-search-div:focus-within::after { filter: var(--crate-search-div-hover-filter); } -#crate-search > option { - font-size: 1rem; +.popover button { + border-radius: 2px; + outline: 0; + border: 1px solid var(--main-color); + padding: 3px 6px; + background: var(--main-background-color); + color: var(--main-color); +} +.popover button:hover:not(:disabled) { + border-color: var(--settings-button-border-focus); +} +.popover button:disabled { + cursor: initial; + color: var(--disabled-element-color); + border-color: var(--disabled-element-color); } .search-input { /* Override Normalize.css: it has a rule that sets @@ -899,7 +917,7 @@ so that we can apply CSS-filters to change the arrow color in themes */ display: inline; } -.popover { +.popover, .popover-left { position: absolute; top: 100%; right: 0; @@ -911,9 +929,13 @@ so that we can apply CSS-filters to change the arrow color in themes */ color: var(--main-color); --popover-arrow-offset: 11px; } +.popover-left { + left: 0; + right: initial; +} /* This rule is to draw the little arrow connecting the settings menu to the gear icon. */ -.popover::before { +.popover::before, .popover-left::before { content: ''; position: absolute; right: var(--popover-arrow-offset); @@ -924,7 +946,10 @@ so that we can apply CSS-filters to change the arrow color in themes */ transform: rotate(-45deg); top: -5px; } - +.popover-left::before { + left: var(--popover-arrow-offset); + right: initial; +} /* use larger max-width for help popover, but not for help.html */ #help.popover { max-width: 600px; diff --git a/src/librustdoc/html/static/css/settings.css b/src/librustdoc/html/static/css/settings.css index c1324c0760e63..9424c25ba1728 100644 --- a/src/librustdoc/html/static/css/settings.css +++ b/src/librustdoc/html/static/css/settings.css @@ -17,6 +17,9 @@ .setting-radio span, .setting-check span { padding-bottom: 1px; + max-width: 100%; + overflow-x: hidden; + text-overflow: ellipsis; } .setting-radio { @@ -33,7 +36,6 @@ } .setting-check { - margin-right: 20px; display: flex; align-items: center; cursor: pointer; @@ -45,7 +47,7 @@ } .setting-check input:checked { background-color: var(--settings-input-color); - border-width: 1px; + border-width: 2px; content: url('data:image/svg+xml,\ \ '); diff --git a/src/librustdoc/html/static/css/themes/ayu.css b/src/librustdoc/html/static/css/themes/ayu.css index 873a1668f8b99..629b552b9adc9 100644 --- a/src/librustdoc/html/static/css/themes/ayu.css +++ b/src/librustdoc/html/static/css/themes/ayu.css @@ -6,6 +6,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) :root { --main-background-color: #0f1419; --main-color: #c5c5c5; + --disabled-element-color: #9b9797; --settings-input-color: #ffb454; --settings-input-border-color: #999; --settings-button-color: #fff; diff --git a/src/librustdoc/html/static/css/themes/dark.css b/src/librustdoc/html/static/css/themes/dark.css index 2b6e28d35a5ff..4534a72f66c28 100644 --- a/src/librustdoc/html/static/css/themes/dark.css +++ b/src/librustdoc/html/static/css/themes/dark.css @@ -1,6 +1,7 @@ :root { --main-background-color: #353535; --main-color: #ddd; + --disabled-element-color: #9b9797; --settings-input-color: #2196f3; --settings-input-border-color: #999; --settings-button-color: #000; diff --git a/src/librustdoc/html/static/css/themes/light.css b/src/librustdoc/html/static/css/themes/light.css index 9c016db4502c9..97394a6d7cd84 100644 --- a/src/librustdoc/html/static/css/themes/light.css +++ b/src/librustdoc/html/static/css/themes/light.css @@ -1,6 +1,7 @@ :root { --main-background-color: white; --main-color: black; + --disabled-element-color: #9b9797; --settings-input-color: #2196f3; --settings-input-border-color: #717171; --settings-button-color: #000; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index a5ae988f34833..8c0b996a7ec51 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1152,6 +1152,9 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ onEachLazy(document.querySelectorAll(".search-form .popover"), elem => { elem.style.display = "none"; }); + onEachLazy(document.querySelectorAll("#crate-search .popover"), elem => { + elem.style.display = "none"; + }); }; /** diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 0e270bbcc4028..487bbb0d07d31 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1000,15 +1000,20 @@ function initSearch(rawSearchIndex) { * @return {string|null} */ function getFilterCrates() { - const elem = document.getElementById("crate-search"); + const elem = document.getElementById("crate-search-div"); + if (!elem) { + return null; + } - if (elem && - elem.value !== "all crates" && - hasOwnPropertyRustdoc(rawSearchIndex, elem.value) - ) { - return elem.value; + const crates = Array.prototype.slice.call(elem.querySelectorAll("input")); + const filterCrates = crates.filter(c => c.checked).map(c => c.getAttribute("data-crate")); + + if (filterCrates.length === crates.length) { + // No filtering + return null; } - return null; + + return filterCrates; } /** @@ -1151,7 +1156,7 @@ function initSearch(rawSearchIndex) { * * @param {ParsedQuery} parsedQuery - The parsed user query * @param {Object} searchWords - The list of search words to query against - * @param {Object} [filterCrates] - Crate to search in if defined + * @param {Object} [filterCrates] - Crates to search in if defined * @param {Object} [currentCrate] - Current crate, to rank results from this crate higher * * @return {ResultsTable} @@ -1666,10 +1671,12 @@ function initSearch(rawSearchIndex) { const aliases = []; const crateAliases = []; if (filterCrates !== null) { - if (ALIASES.has(filterCrates) && ALIASES.get(filterCrates).has(lowerQuery)) { - const query_aliases = ALIASES.get(filterCrates).get(lowerQuery); - for (const alias of query_aliases) { - aliases.push(createAliasFromItem(searchIndex[alias])); + for (const crate of filterCrates) { + if (ALIASES.has(crate) && ALIASES.get(crate).has(lowerQuery)) { + const query_aliases = ALIASES.get(crate).get(lowerQuery); + for (const alias of query_aliases) { + aliases.push(createAliasFromItem(searchIndex[alias])); + } } } } else { @@ -1778,7 +1785,7 @@ function initSearch(rawSearchIndex) { results_returned, maxEditDistance ) { - if (!row || (filterCrates !== null && row.crate !== filterCrates)) { + if (!row || (filterCrates !== null && filterCrates.indexOf(row.crate) === -1)) { return; } let index = -1, path_dist = 0; @@ -1848,7 +1855,7 @@ function initSearch(rawSearchIndex) { * @param {Object} results */ function handleArgs(row, pos, results) { - if (!row || (filterCrates !== null && row.crate !== filterCrates) || !row.type) { + if (!row || (filterCrates !== null && filterCrates.indexOf(row.crate) === -1) || !row.type) { return; } @@ -2184,6 +2191,17 @@ ${item.displayPath}${name}\ return ""; } + function getCrateFilterText(crateList, filterCrates) { + if (filterCrates.length === crateList.length) { + return "All crates"; + } else if (filterCrates.length === 0) { + return "No crate"; + } else if (filterCrates.length > 1) { + return `${filterCrates.length} crates`; + } + return "1 crate"; + } + /** * @param {ResultsTable} results * @param {boolean} go_to_first @@ -2243,14 +2261,28 @@ ${item.displayPath}${name}\ } let crates = ""; - const crates_list = Object.keys(rawSearchIndex); - if (crates_list.length > 1) { - crates = " in 
\ + ${c}\ + +
`; } - crates += ""; + crates += `\ +
\ + \ +
`; } let output = `

Results${crates}

`; @@ -2303,9 +2335,48 @@ ${item.displayPath}${name}\ resultsElem.appendChild(ret_returned[0]); search.innerHTML = output; - const crateSearch = document.getElementById("crate-search"); - if (crateSearch) { - crateSearch.addEventListener("input", updateCrate); + const crateSearchButton = document.getElementById("crate-search-div"); + if (crateSearchButton) { + function searchBlurHandler(event) { + blurHandler( + event, document.getElementById("crate-search-div"), window.hidePopoverMenus); + } + + const crateListPopover = crateSearchButton.querySelector(".popover"); + const refreshButton = crateListPopover.querySelector("button"); + crateSearchButton.addEventListener("click", event => { + if (elemIsInParent(event.target, crateListPopover)) { + return; + } + if (crateListPopover.style.display !== "block") { + loadCss(getVar("static-root-path") + getVar("settings-css")); + window.hideAllModals(); + crateListPopover.style.display = "block"; + } else { + crateListPopover.style.display = "none"; + } + }); + crateSearchButton.parentElement.onblur = searchBlurHandler; + onEachLazy(crateListPopover.querySelectorAll("input"), el => { + el.onblur = searchBlurHandler; + el.addEventListener("change", () => { + const current = el.getAttribute("data-crate"); + let filter = getFilterCrates(); + filter = filter === null ? cratesList : filter; + const currentFilter = filterCrates === null ? cratesList : filterCrates; + + refreshButton.disabled = currentFilter.join(",") === filter.join(","); + }); + }); + refreshButton.addEventListener("click", () => { + updateCrate(cratesList, getFilterCrates()); + }); + crateListPopover.onblur = searchBlurHandler; + crateListPopover.querySelector("button").addEventListener("click", () => { + const filter = getFilterCrates(); + crateSearchButton.innerText = getCrateFilterText(cratesList, filter); + updateCrate(cratesList, filter); + }); } search.appendChild(resultsElem); // Reset focused elements. @@ -2345,7 +2416,6 @@ ${item.displayPath}${name}\ e.preventDefault(); } const query = parseQuery(searchState.input.value.trim()); - let filterCrates = getFilterCrates(); if (!forced && query.userQuery === currentResults) { if (query.userQuery.length > 0) { @@ -2354,6 +2424,7 @@ ${item.displayPath}${name}\ return; } + let filterCrates = getFilterCrates(); searchState.setLoadingSearch(); const params = searchState.getQueryStringParams(); @@ -2361,7 +2432,7 @@ ${item.displayPath}${name}\ // In case we have no information about the saved crate and there is a URL query parameter, // we override it with the URL query parameter. if (filterCrates === null && params["filter-crate"] !== undefined) { - filterCrates = params["filter-crate"]; + filterCrates = params["filter-crate"].split(","); } // Update document title to maintain a meaningful browser history @@ -2369,7 +2440,8 @@ ${item.displayPath}${name}\ // Because searching is incremental by character, only the most // recent search query is added to the browser history. - updateSearchHistory(buildUrl(query.original, filterCrates)); + updateSearchHistory(buildUrl( + query.original, filterCrates !== null ? filterCrates.join(",") : null)); showResults( execQuery(query, searchWords, filterCrates, window.currentCrate), @@ -2735,7 +2807,7 @@ ${item.displayPath}${name}\ searchState.showResults(); if (browserSupportsHistoryApi()) { history.replaceState(null, "", - buildUrl(search_input.value, getFilterCrates())); + buildUrl(search_input.value, getFilterCrates().join(","))); } document.title = searchState.title; } @@ -2878,8 +2950,8 @@ ${item.displayPath}${name}\ }; } - function updateCrate(ev) { - if (ev.target.value === "all crates") { + function updateCrate(cratesList, filterCrates) { + if (cratesList.length === filterCrates.length) { // If we don't remove it from the URL, it'll be picked up again by the search. const query = searchState.input.value.trim(); updateSearchHistory(buildUrl(query, null));