Add Tools page search
This commit is contained in:
+32
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user