const navEl = document.getElementById("nav"); const contentEl = document.getElementById("content"); const themeToggle = document.querySelector(".theme-toggle"); // Theme toggle function getPreferredTheme() { const stored = localStorage.getItem("theme"); if (stored) return stored; return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark"; } function applyTheme(theme) { document.documentElement.setAttribute("data-theme", theme); localStorage.setItem("theme", theme); } applyTheme(getPreferredTheme()); themeToggle?.addEventListener("click", () => { const current = document.documentElement.getAttribute("data-theme") || getPreferredTheme(); applyTheme(current === "dark" ? "light" : "dark"); }); const buildId = new URL(import.meta.url).searchParams.get("v") || ""; function withBuild(url) { if (!buildId) return url; const sep = url.includes("?") ? "&" : "?"; return `${url}${sep}v=${encodeURIComponent(buildId)}`; } function escapeHtml(text) { return text .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function normalizeDocPath(docPath) { let p = String(docPath || "").trim(); p = p.replaceAll("\\", "/"); p = p.replace(/^\/+/, ""); p = p.replace(/\.\.\//g, ""); if (!p.endsWith(".md")) p += ".md"; return p; } function getSelectedPath() { const hash = (location.hash || "").replace(/^#/, ""); if (!hash) return null; return normalizeDocPath(hash); } function setSelectedPath(docPath) { location.hash = normalizeDocPath(docPath); } async function fetchJson(path) { const res = await fetch(withBuild(path), { cache: "no-store" }); if (!res.ok) throw new Error(`Failed to fetch ${path}: ${res.status}`); return res.json(); } async function fetchText(path) { const res = await fetch(withBuild(path), { cache: "no-store" }); if (!res.ok) throw new Error(`Failed to fetch ${path}: ${res.status}`); return res.text(); } function renderNav(pages, activePath) { if (!pages.length) { navEl.innerHTML = ""; return; } navEl.innerHTML = pages .filter((p) => normalizeDocPath(p.path) !== "index.md") .map((p) => { const path = normalizeDocPath(p.path); const title = escapeHtml(p.title || path); const current = activePath === path ? ` aria-current="page"` : ""; return `${title}`; }) .join(""); } function installContentLinkHandler() { contentEl.addEventListener("click", (e) => { const a = e.target?.closest?.("a"); if (!a) return; const href = a.getAttribute("href") || ""; if ( href.startsWith("http://") || href.startsWith("https://") || href.startsWith("mailto:") || href.startsWith("#") ) { return; } // Route relative markdown links through the SPA. if (href.endsWith(".md")) { e.preventDefault(); setSelectedPath(href); return; } }); } async function main() { // SPA fallback: convert pathname to hash route so deep links work if (location.pathname !== "/" && !location.hash) { const path = location.pathname.replace(/^\/+/, ""); if (path) { location.replace("#" + normalizeDocPath(path)); return; } } if (!globalThis.marked) { contentEl.innerHTML = `
Markdown renderer failed to load.
`; return; } installContentLinkHandler(); let manifest; try { manifest = await fetchJson("./manifest.json"); } catch (e) { contentEl.innerHTML = `Missing manifest.json. Deploy the site via CI.
No docs yet.
`; return; } try { const md = await fetchText(`./docs/${activePath}`); const html = globalThis.marked.parse(md); contentEl.innerHTML = html; for (const a of navEl.querySelectorAll("a")) { const href = decodeURIComponent((a.getAttribute("href") || "").slice(1)); a.toggleAttribute("aria-current", normalizeDocPath(href) === activePath); } } catch (e) { contentEl.innerHTML = `Failed to load ${escapeHtml(
activePath,
)}.