Files
website/static/js/site.js

136 lines
3.6 KiB
JavaScript
Raw Normal View History

2026-07-02 00:28:25 -07:00
(() => {
const $$ = (selector, root = document) => [...root.querySelectorAll(selector)];
function setupToc() {
const headings = $$(".post-content h2[id], .post-content h3[id]");
const links = $$("#TableOfContents a");
if (!headings.length || !links.length) return;
let queued = false;
function update() {
const y = window.scrollY + window.innerHeight * 0.35;
let activeId = headings[0].id;
for (const heading of headings) {
if (heading.offsetTop > y) break;
activeId = heading.id;
}
for (const link of links) {
link.classList.toggle(
"active",
decodeURIComponent(link.hash.slice(1)) === activeId
);
}
queued = false;
}
function queue() {
if (queued) return;
queued = true;
window.requestAnimationFrame(update);
}
window.addEventListener("scroll", queue, { passive: true });
window.addEventListener("resize", queue);
update();
}
function setupPostTags() {
const list = document.querySelector("[data-post-list]");
if (!list) return;
const rows = $$(".post-row", list);
const links = $$("[data-tag]");
const status = document.querySelector("[data-tag-filter-status]");
const clear = status?.querySelector("a");
function setUrl(tag) {
const url = new URL(window.location.href);
if (tag) {
url.searchParams.set("tag", tag);
} else {
url.searchParams.delete("tag");
}
window.history.pushState(null, "", `${url.pathname}${url.search}`);
}
function applyTag(tag) {
for (const link of links) {
link.classList.toggle("active", link.dataset.tag === tag);
}
let visible = 0;
for (const row of rows) {
const tags = JSON.parse(row.dataset.tags || "[]");
const matched = !tag || tags.includes(tag);
row.hidden = !matched;
if (matched) visible += 1;
}
if (status) {
const name = status.querySelector("[data-tag-filter-name]");
if (name) name.textContent = tag ? `[${tag}]` : "";
status.hidden = !tag;
status.classList.toggle("is-empty", visible === 0);
}
}
for (const link of links) {
link.addEventListener("click", (event) => {
event.preventDefault();
const nextTag = link.classList.contains("active") ? null : link.dataset.tag;
applyTag(nextTag);
setUrl(nextTag);
});
}
if (clear) {
clear.addEventListener("click", (event) => {
event.preventDefault();
applyTag(null);
setUrl(null);
});
}
applyTag(new URLSearchParams(window.location.search).get("tag"));
}
function setupClickableRows() {
const rows = $$("[data-row-href]");
function openRow(row) {
const href = row.dataset.rowHref;
if (href) window.location.href = href;
}
for (const row of rows) {
row.addEventListener("click", (event) => {
if (event.target.closest("a")) return;
openRow(row);
});
row.addEventListener("keydown", (event) => {
if (event.key !== "Enter" && event.key !== " ") return;
event.preventDefault();
openRow(row);
});
row.addEventListener("pointerdown", (event) => {
if (event.target.closest(".item-links a")) return;
row.classList.add("is-pressed");
});
for (const eventName of ["pointerup", "pointercancel", "pointerleave", "blur"]) {
row.addEventListener(eventName, () => row.classList.remove("is-pressed"));
}
}
}
setupToc();
setupPostTags();
setupClickableRows();
})();