tagliatelle

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

commit 0c50f105fcf0a895f43115cf024c0094c94ea446
parent 57c84094b4fb57d63aa8ec95856c4d15aaefedca
Author: breadcat <breadcat@users.noreply.github.com>
Date:   Tue, 28 Apr 2026 19:23:28 +0100

Add property and tag list filter script

Diffstat:
Astatic/list-filter.js | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstatic/style.css | 6+++++-
Mtemplates/properties.html | 2++
Mtemplates/tags.html | 2++
4 files changed, 75 insertions(+), 1 deletion(-)

diff --git a/static/list-filter.js b/static/list-filter.js @@ -0,0 +1,66 @@ +(() => { + // filter input HTML + const input = document.createElement("input"); + input.type = "search"; + input.id = "list-filter"; + input.placeholder = "Filter..."; + input.autocomplete = "off"; + input.className = "list-filter-input"; + const firstDetails = document.querySelector("details"); + if (!firstDetails) return; + firstDetails.parentNode.insertBefore(input, firstDetails); + + // filter logic + function filter(query) { + const q = query.trim().toLowerCase(); + const allDetails = document.querySelectorAll("details"); + + allDetails.forEach((details) => { + const summary = details.querySelector("summary"); + const items = details.querySelectorAll("li"); + const categoryText = summary ? summary.textContent.toLowerCase() : ""; + + if (!q) { + // reset + details.removeAttribute("open"); + details.style.display = ""; + items.forEach((li) => (li.style.display = "")); + return; + } + + const categoryMatches = categoryText.includes(q); + + let anyItemVisible = false; + items.forEach((li) => { + const matches = categoryMatches || li.textContent.toLowerCase().includes(q); + li.style.display = matches ? "" : "none"; + if (matches) anyItemVisible = true; + }); + + const visible = categoryMatches || anyItemVisible; + details.style.display = visible ? "" : "none"; + + // open on match + if (visible) { + details.setAttribute("open", ""); + } else { + details.removeAttribute("open"); + } + }); + } + + // debounce timer + let debounceTimer; + input.addEventListener("input", () => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => filter(input.value), 150); + }); + + // escape to clear + input.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + input.value = ""; + filter(""); + } + }); +})(); diff --git a/static/style.css b/static/style.css @@ -3,7 +3,7 @@ body,button{background:#1a1a1a;color:#cfcfcf;font-family:sans-serif;margin:0;pos body details {text-transform: capitalize} body details div.file-item a {text-transform: none} a{color:#add8e6;text-decoration:none} -input[type="url"], input[type="text"] {background:#1a1a1a;color:#cfcfcf;border:1px solid gray;margin: 8px;padding:8px;outline: none; box-sizing: border-box} +input[type="url"], input[type="text"], .list-filter-input {background:#1a1a1a;color:#cfcfcf;border:1px solid gray;margin: 8px;padding:8px;outline: none; box-sizing: border-box} input[type="url"]:focus, input[type="text"]:focus{border:1px solid white;background-color:#3a3a3a} div#search-container {border-left:1px solid gray; display: flex; align-items: center} span.required {color: red} @@ -186,3 +186,6 @@ div.config-split {flex: 1} .dropdown-empty{padding:10px;color:#666;font-size:14px} .operation-name{font-weight:700} .operation-desc{font-size:12px;color:#666} + +/* list filter input box */ +.list-filter-input {display: block; margin: 1rem 0; padding: 0.4rem 0.6rem; font-size: 1rem; width: 100%; max-width: 400px} +\ No newline at end of file diff --git a/templates/properties.html b/templates/properties.html @@ -13,4 +13,6 @@ <p>No properties have been computed yet. Visit the <a href="/admin">Admin</a> page to compute them.</p> {{end}} +<script src="/static/list-filter.js" defer></script> + {{template "_footer"}} diff --git a/templates/tags.html b/templates/tags.html @@ -11,4 +11,6 @@ </details> {{end}} +<script src="/static/list-filter.js" defer></script> + {{template "_footer"}} \ No newline at end of file