From 37dc96575d3b9960df8b3abef16ede47832f9ba4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 9 Dec 2024 10:27:01 +0300 Subject: [PATCH 1/4] docs: add search widget to options page --- .editorconfig | 2 +- docs/manual.nix | 4 +++- docs/static/script/search.js | 40 ++++++++++++++++++++++++++++++++++++ docs/static/style.scss | 18 ++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 docs/static/script/search.js diff --git a/.editorconfig b/.editorconfig index 5f4be94c..43456223 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,7 +14,7 @@ indent_style = space indent_size = 2 trim_trailing_whitespace = false -[*.{nix,yml,yaml}] +[*.{js,nix,yml,yaml}] indent_style = space indent_size = 2 tab_width = 2 diff --git a/docs/manual.nix b/docs/manual.nix index 113fb789..4becdf2d 100644 --- a/docs/manual.nix +++ b/docs/manual.nix @@ -62,7 +62,8 @@ in # Copy anchor scripts to the script directory in document root. cp -vt "$dest"/script \ ${./static/script}/anchor-min.js \ - ${./static/script}/anchor-use.js + ${./static/script}/anchor-use.js \ + ${./static/script}/search.js substituteInPlace ./options.md \ --subst-var-by OPTIONS_JSON ./config-options.json @@ -100,6 +101,7 @@ in --script highlightjs/loader.js \ --script script/anchor-use.js \ --script script/anchor-min.js \ + --script script/search.js \ --toc-depth 2 \ --section-toc-depth 1 \ manual.md \ diff --git a/docs/static/script/search.js b/docs/static/script/search.js new file mode 100644 index 00000000..34ce28bd --- /dev/null +++ b/docs/static/script/search.js @@ -0,0 +1,40 @@ +document.addEventListener("DOMContentLoaded", () => { + if (!window.location.pathname.endsWith("options.html")) return; + + const searchBar = document.createDocumentFragment(); + const searchDiv = document.createElement("div"); + searchDiv.id = "search-bar"; + searchDiv.innerHTML = ` + +
+ `; + searchBar.appendChild(searchDiv); + document.body.prepend(searchDiv); + + const dtElements = Array.from(document.querySelectorAll("dt")); + const ddElements = Array.from(document.querySelectorAll("dd")); + const dtOptionIds = dtElements.map( + (dt) => dt.querySelector("a")?.id.toLowerCase() || "", + ); + + if (dtElements.length === 0 || ddElements.length === 0) { + console.warn("Something went wrong, page may be loaded incorrectly."); + return; + } + + let debounceTimeout; + document.getElementById("search-input").addEventListener("input", (event) => { + clearTimeout(debounceTimeout); + debounceTimeout = setTimeout(() => { + const query = event.target.value.toLowerCase(); + dtElements.forEach((dt, index) => { + const isMatch = dtOptionIds[index].includes(query); + + if (dt.classList.contains("hidden") !== !isMatch) { + dt.classList.toggle("hidden", !isMatch); + ddElements[index]?.classList.toggle("hidden", !isMatch); + } + }); + }, 200); + }); +}); diff --git a/docs/static/style.scss b/docs/static/style.scss index 718302f3..3b148739 100644 --- a/docs/static/style.scss +++ b/docs/static/style.scss @@ -233,6 +233,24 @@ li { } } +#search-bar { + position: sticky; + top: 0; + background: white; + padding: 10px; + border-bottom: 1px solid #ccc; + z-index: 1000; +} +#search-input { + width: 100%; + padding: 8px; + border: 1px solid #ccc; + border-radius: 4px; +} +.hidden { + display: none; +} + div.titlepage { margin: 40px 0; From 0a3855cdc277b3cb402a22850f943c31f9f8f700 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Tue, 10 Dec 2024 12:46:50 +0300 Subject: [PATCH 2/4] docs: properly theme searchbar on prefers-dark --- docs/static/style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/static/style.scss b/docs/static/style.scss index 3b148739..1c0b57e5 100644 --- a/docs/static/style.scss +++ b/docs/static/style.scss @@ -240,13 +240,19 @@ li { padding: 10px; border-bottom: 1px solid #ccc; z-index: 1000; + @media (prefers-color-scheme: dark) { + background: $color-gray-900; + color: $color-gray-50; + } } + #search-input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; } + .hidden { display: none; } From 9fad42374f3f4cdaa67ec13483824896ba55f174 Mon Sep 17 00:00:00 2001 From: Ching Pei Yang Date: Tue, 10 Dec 2024 11:34:18 +0100 Subject: [PATCH 3/4] docs: fix search bar style in dark mode --- docs/static/style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/static/style.scss b/docs/static/style.scss index 1c0b57e5..d5f739e5 100644 --- a/docs/static/style.scss +++ b/docs/static/style.scss @@ -251,6 +251,8 @@ li { padding: 8px; border: 1px solid #ccc; border-radius: 4px; + background: inherit; + color: inherit; } .hidden { From 781385f991f26a36102fe289f0e44770ff581ec5 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Tue, 10 Dec 2024 17:15:23 +0300 Subject: [PATCH 4/4] docs: perform DOM manipulation in batches; preprocess data --- docs/static/script/search.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/static/script/search.js b/docs/static/script/search.js index 34ce28bd..1537b235 100644 --- a/docs/static/script/search.js +++ b/docs/static/script/search.js @@ -1,14 +1,12 @@ document.addEventListener("DOMContentLoaded", () => { if (!window.location.pathname.endsWith("options.html")) return; - const searchBar = document.createDocumentFragment(); const searchDiv = document.createElement("div"); searchDiv.id = "search-bar"; searchDiv.innerHTML = `
`; - searchBar.appendChild(searchDiv); document.body.prepend(searchDiv); const dtElements = Array.from(document.querySelectorAll("dt")); @@ -22,18 +20,27 @@ document.addEventListener("DOMContentLoaded", () => { return; } + const dtElementsData = dtElements.map((dt, index) => ({ + element: dt, + id: dtOptionIds[index], + ddElement: ddElements[index], + })); + let debounceTimeout; document.getElementById("search-input").addEventListener("input", (event) => { clearTimeout(debounceTimeout); debounceTimeout = setTimeout(() => { const query = event.target.value.toLowerCase(); - dtElements.forEach((dt, index) => { - const isMatch = dtOptionIds[index].includes(query); - if (dt.classList.contains("hidden") !== !isMatch) { - dt.classList.toggle("hidden", !isMatch); - ddElements[index]?.classList.toggle("hidden", !isMatch); - } + requestAnimationFrame(() => { + const fragment = document.createDocumentFragment(); + dtElementsData.forEach(({ element, id, ddElement }) => { + const isMatch = id.includes(query); + if (element.classList.contains("hidden") !== !isMatch) { + element.classList.toggle("hidden", !isMatch); + ddElement?.classList.toggle("hidden", !isMatch); + } + }); }); }, 200); });