/* global marked, DOMPurify */ const $ = (s, r=document) => r.querySelector(s); const $$ = (s, r=document) => [...r.querySelectorAll(s)]; const els = { body: document.body, sidebar: $("#sidebar"), content: $("#content"), viewer: $("#viewer"), tree: $("#tree"), navToggle: $("#navToggle"), filterSection: $("#filterSection"), sortOrder: $("#sortOrder"), searchBox: $("#searchBox"), }; const state = { index: null, section: "all", sort: "newest", q: "", sidebarOpen: false, desktopCollapsed: false }; init(); async function init(){ // ensure libs present before any rendering const ok = await waitForLibs(3000); if (!ok){ els.viewer.innerHTML = `
Markdown renderer failed to load. Check /lib/marked.min.js and /lib/purify.min.js.
`; return; } // restore desktop collapse try { state.desktopCollapsed = localStorage.getItem("desktopCollapsed")==="1"; } catch {} wireToggles(); onResizeMode(); // load index try{ const res = await fetch("/index.json", { cache:"no-cache" }); if (!res.ok) throw new Error(res.status); state.index = await res.json(); }catch(e){ console.error("index.json load failed", e); els.viewer.innerHTML = `Could not load index.json
`; return; } buildFilters(); renderTree(); window.addEventListener("hashchange", onRoute); onRoute(); } /* ---------- Lib readiness ---------- */ function waitForLibs(timeoutMs=3000){ const start = performance.now(); return new Promise(resolve=>{ (function tick(){ const ready = !!(window.marked && window.DOMPurify); if (ready) return resolve(true); if (performance.now() - start > timeoutMs) return resolve(false); setTimeout(tick, 60); })(); }); } /* ---------- UI wiring ---------- */ function wireToggles(){ els.navToggle.addEventListener("click", ()=>{ const desktop = window.matchMedia("(min-width:1025px)").matches; if (desktop){ state.desktopCollapsed = !els.body.classList.contains("sidebar-collapsed"); els.body.classList.toggle("sidebar-collapsed"); try{ localStorage.setItem("desktopCollapsed", state.desktopCollapsed ? "1":"0"); }catch{} }else{ state.sidebarOpen = !els.body.classList.contains("sidebar-open"); els.body.classList.toggle("sidebar-open"); } }); window.addEventListener("resize", onResizeMode); } function onResizeMode(){ const desktop = window.matchMedia("(min-width:1025px)").matches; if (desktop){ els.body.classList.remove("sidebar-open"); els.body.classList.toggle("sidebar-collapsed", state.desktopCollapsed); } else { els.body.classList.remove("sidebar-collapsed"); if (!state.sidebarOpen) els.body.classList.remove("sidebar-open"); } } function buildFilters(){ const sections = ["all", ...(state.index?.sections ?? [])]; els.filterSection.innerHTML = sections.map(s=>``).join(""); els.filterSection.value = state.section; els.filterSection.addEventListener("change", e=>{ state.section = e.target.value; renderTree(); }); els.sortOrder.value = state.sort; els.sortOrder.addEventListener("change", e=>{ state.sort = e.target.value; renderTree(); }); els.searchBox.addEventListener("input", e=>{ state.q = e.target.value.trim().toLowerCase(); renderTree(); }); } /* ---------- Tree ---------- */ function renderTree(){ if (!state.index) return; const items = state.index.flat.slice(); const filtered = items.filter(f=>{ const inSection = state.section==="all" || f.path.startsWith(state.section + "/"); const inQuery = !state.q || f.title.toLowerCase().includes(state.q) || f.name.toLowerCase().includes(state.q); return inSection && inQuery; }); filtered.sort((a,b)=>{ if (state.sort==="title") return a.title.localeCompare(b.title, undefined, {sensitivity:"base"}); if (state.sort==="oldest") return (a.mtime??0) - (b.mtime??0); return (b.mtime??0) - (a.mtime??0); }); els.tree.innerHTML = filtered.map(f=>{ const d = new Date(f.mtime || Date.now()); const meta = `${d.toISOString().slice(0,10)} • ${f.name}`; return `Select a note on the left.
Select a note on the left.