feat(blog): live in-page filter via data-search attributes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8423759f03
commit
468d4e460d
1 changed files with 62 additions and 0 deletions
|
|
@ -80,3 +80,65 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Lives on /blog. No-ops gracefully on any other page (selectors return empty).
|
||||||
|
const input = document.querySelector<HTMLInputElement>(".binput");
|
||||||
|
if (input) {
|
||||||
|
const rows = Array.from(
|
||||||
|
document.querySelectorAll<HTMLElement>("[data-search]")
|
||||||
|
);
|
||||||
|
const featured = document.querySelector<HTMLElement>("[data-featured]");
|
||||||
|
const yearHeads = Array.from(
|
||||||
|
document.querySelectorAll<HTMLElement>("[data-year-head]")
|
||||||
|
);
|
||||||
|
const empty = document.querySelector<HTMLElement>("[data-empty]");
|
||||||
|
const emptyQuery = empty?.querySelector<HTMLElement>("[data-empty-query]");
|
||||||
|
|
||||||
|
function applyFilter() {
|
||||||
|
const raw = input!.value.toLowerCase().trim();
|
||||||
|
const tokens = raw.length ? raw.split(/\s+/) : [];
|
||||||
|
const isFiltering = tokens.length > 0;
|
||||||
|
|
||||||
|
let visibleCount = 0;
|
||||||
|
for (const row of rows) {
|
||||||
|
const haystack = row.dataset.search ?? "";
|
||||||
|
const isFeatured = row === featured;
|
||||||
|
let visible: boolean;
|
||||||
|
if (!isFiltering) {
|
||||||
|
visible = true;
|
||||||
|
} else if (isFeatured) {
|
||||||
|
// Featured hides whenever there's a query, regardless of match.
|
||||||
|
visible = false;
|
||||||
|
} else {
|
||||||
|
visible = tokens.every((t) => haystack.includes(t));
|
||||||
|
}
|
||||||
|
row.classList.toggle("is-hidden", !visible);
|
||||||
|
if (visible && !isFeatured) visibleCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide year heads whose following rows are all hidden.
|
||||||
|
for (const head of yearHeads) {
|
||||||
|
let anyVisible = false;
|
||||||
|
let node: Element | null = head.nextElementSibling;
|
||||||
|
while (node && !node.matches("[data-year-head]")) {
|
||||||
|
if (node.matches("[data-search]") && !node.classList.contains("is-hidden")) {
|
||||||
|
anyVisible = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.nextElementSibling;
|
||||||
|
}
|
||||||
|
head.classList.toggle("is-hidden", !anyVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle empty-state message.
|
||||||
|
if (empty) {
|
||||||
|
const showEmpty = isFiltering && visibleCount === 0;
|
||||||
|
empty.hidden = !showEmpty;
|
||||||
|
if (showEmpty && emptyQuery) emptyQuery.textContent = input!.value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener("input", applyFilter);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue