Add Tools page search

This commit is contained in:
Tanner
2026-09-26 00:48:55 +00:00
parent 03b124f295
commit 7bac8b4011
2 changed files with 111 additions and 46 deletions
+32
View File
@@ -379,6 +379,38 @@ footer p {
} }
} }
.tools-search {
margin: 0 0 32px;
}
.tools-search label {
display: block;
margin-bottom: 8px;
font-weight: 700;
}
.tools-search-controls {
display: flex;
gap: 8px;
}
.tools-search input {
min-width: 0;
flex: 1;
padding: 10px 12px;
border: 1px solid #0005;
border-radius: 4px;
font: inherit;
}
.tools-search button {
padding: 10px 16px;
border: 1px solid #0005;
border-radius: 4px;
background: #eee;
font: inherit;
cursor: pointer;
}
.tools-results-count {
font-size: 14px;
font-weight: 400;
}
.tools-toc { .tools-toc {
margin: 32px 0; margin: 32px 0;
padding: 16px 20px; padding: 16px 20px;
+79 -46
View File
@@ -84,10 +84,19 @@
<h1>Tools</h1> <h1>Tools</h1>
<div class="entry"> <div class="entry">
<p> <p>
Browse the tools and equipment available to Protospace members. This catalog follows the Browse the tools and equipment available to Protospace members. This catalog
categories and ordering on the <a href="https://wiki.protospace.ca/Tools_we_have" class="external">Protospace Wiki</a>. comes from the <a href="https://wiki.protospace.ca/Tools_we_have" class="external">Protospace Wiki</a>.
</p> </p>
<nav class="tools-toc" aria-label="Tool categories"> <h2>Search</h2>
<div class="tools-search">
<label for="tool-search">Search tools <span id="tool-results-count" class="tools-results-count" hidden> - Results: …</span></label>
<div class="tools-search-controls">
<input id="tool-search" type="search" placeholder="Search by tool name…" autocomplete="off" />
<button id="tool-search-clear" type="button" hidden>Clear</button>
</div>
</div>
<h2 id="tools-categories-heading">Categories</h2>
<nav id="tools-toc" class="tools-toc" aria-label="Tool categories">
<strong>Jump to a category</strong> <strong>Jump to a category</strong>
<ul id="tools-toc-list"></ul> <ul id="tools-toc-list"></ul>
</nav> </nav>
@@ -104,50 +113,74 @@
.then((tools) => { .then((tools) => {
const list = document.querySelector("#tools-list"); const list = document.querySelector("#tools-list");
const toc = document.querySelector("#tools-toc-list"); const toc = document.querySelector("#tools-toc-list");
const categories = new Map(); const searchInput = document.querySelector("#tool-search");
tools.forEach((tool) => { const clearButton = document.querySelector("#tool-search-clear");
if (!categories.has(tool.category)) categories.set(tool.category, []); const resultsCount = document.querySelector("#tool-results-count");
categories.get(tool.category).push(tool); const categoriesHeading = document.querySelector("#tools-categories-heading");
}); const tocNav = document.querySelector("#tools-toc");
list.replaceChildren(); const normalize = (value) => value.normalize("NFKD").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
toc.replaceChildren(); const render = () => {
for (const [category, categoryTools] of categories) { const query = normalize(searchInput.value);
const sectionId = `tools-${category.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`; const categories = new Map();
const tocItem = document.createElement("li"); const matchingTools = tools
const tocLink = document.createElement("a"); .filter((tool) => !query || normalize(tool.name).includes(query));
tocLink.href = `#${sectionId}`; resultsCount.hidden = !query;
tocLink.textContent = category; categoriesHeading.hidden = Boolean(query);
tocItem.append(tocLink); tocNav.hidden = Boolean(query);
toc.append(tocItem); resultsCount.textContent = ` - Results: ${matchingTools.length}`;
const heading = document.createElement("h3"); matchingTools
heading.id = sectionId; .forEach((tool) => {
const headingText = document.createElement("span"); if (!categories.has(tool.category)) categories.set(tool.category, []);
headingText.textContent = category; categories.get(tool.category).push(tool);
const backLink = document.createElement("a"); });
backLink.className = "category-back"; list.replaceChildren();
backLink.href = "#top"; toc.replaceChildren();
backLink.textContent = "Back to Top"; clearButton.hidden = !query;
heading.append(headingText, backLink); for (const [category, categoryTools] of categories) {
list.append(heading); const sectionId = `tools-${normalize(category)}`;
const grid = document.createElement("div"); const tocItem = document.createElement("li");
grid.className = "tools-grid"; const tocLink = document.createElement("a");
for (const tool of categoryTools) { tocLink.href = `#${sectionId}`;
const card = document.createElement("a"); tocLink.textContent = category;
card.className = "tool-card external"; tocItem.append(tocLink);
card.href = tool.link; toc.append(tocItem);
card.target = "_blank"; const heading = document.createElement("h3");
card.rel = "noopener noreferrer"; heading.id = sectionId;
const image = document.createElement("img"); const headingText = document.createElement("span");
image.src = tool["photo file"]; headingText.textContent = category;
image.alt = tool.name; const backLink = document.createElement("a");
image.loading = "lazy"; backLink.className = "category-back";
const name = document.createElement("span"); backLink.href = "#top";
name.textContent = tool.name; backLink.textContent = "Back to Top";
card.append(image, name); heading.append(headingText, backLink);
grid.append(card); list.append(heading);
const grid = document.createElement("div");
grid.className = "tools-grid";
for (const tool of categoryTools) {
const card = document.createElement("a");
card.className = "tool-card external";
card.href = tool.link;
card.target = "_blank";
card.rel = "noopener noreferrer";
const image = document.createElement("img");
image.src = tool["photo file"];
image.alt = tool.name;
image.loading = "lazy";
const name = document.createElement("span");
name.textContent = tool.name;
card.append(image, name);
grid.append(card);
}
list.append(grid);
} }
list.append(grid); };
} searchInput.addEventListener("input", render);
clearButton.addEventListener("click", () => {
searchInput.value = "";
searchInput.focus();
render();
});
render();
}) })
.catch((error) => { .catch((error) => {
document.querySelector("#tools-list").textContent = error.message; document.querySelector("#tools-list").textContent = error.message;