From fe9d16e036030143d86522251f431b85a0d46e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=20Randall=20Havens=20=E2=96=B3=20The=20Empathic=20Tec?= =?UTF-8?q?hnologist=20=E2=9F=81=20Doctor=20Who=2042?= Date: Sat, 8 Nov 2025 12:10:39 -0600 Subject: [PATCH] Update app.js --- public/app.js | 165 ++++++-------------------------------------------- 1 file changed, 18 insertions(+), 147 deletions(-) diff --git a/public/app.js b/public/app.js index 2cd4707..1da26e7 100755 --- a/public/app.js +++ b/public/app.js @@ -9,159 +9,30 @@ const searchBox = document.getElementById("search"); const prevBtn = document.getElementById("prev"); const nextBtn = document.getElementById("next"); const sidebar = document.querySelector(".sidebar"); +const navToggle = document.getElementById("navToggle"); +const overlay = document.querySelector(".overlay"); + +navToggle.addEventListener("click", () => sidebar.classList.toggle("open")); +overlay.addEventListener("click", () => sidebar.classList.remove("open")); + +// ... (rest same as v2.2.1 up to renderMarkdown) -async function loadIndex() { - const res = await fetch("/index.json", { cache: "no-store" }); - INDEX = await res.json(); - populateFilters(); - rebuildTree(); - window.addEventListener("popstate", () => { const hp = location.hash.startsWith("#=") ? location.hash.slice(2) : null; if (hp) openPath(hp); }); - const init = location.hash.startsWith("#=") ? location.hash.slice(2) : INDEX.flat.sort((a, b) => b.mtime - a.mtime)[0]?.path; - openPath(init); -} -function populateFilters() { - filterSel.innerHTML = ''; - for (const cat of INDEX.sections) { - const opt = document.createElement("option"); - opt.value = opt.textContent = cat; - filterSel.appendChild(opt); - } -} -function rebuildTree() { - treeEl.innerHTML = ""; - PATH_TO_EL.clear(); - const filter = filterSel.value; - const sort = sortSel.value; - const query = searchBox.value.trim().toLowerCase(); - const root = { type: "dir", children: INDEX.tree }; - const pruned = filterTree(root, f => (filter === "all" || f.path.split("/")[0] === filter) && (!query || (f.title || f.name).toLowerCase().includes(query))); - sortDir(pruned, sort); - for (const c of pruned.children) treeEl.appendChild(renderNode(c)); - treeEl.querySelectorAll(".dir").forEach(d => d.classList.add("open")); -} -function filterTree(node, keep) { - if (node.type === "file") return keep(node) ? node : null; - const kids = node.children.map(c => filterTree(c, keep)).filter(Boolean); - return kids.length ? { ...node, children: kids } : null; -} -function sortDir(node, sort) { - const cmp = sort === "name" ? (a, b) => a.name.localeCompare(b.name) : - sort === "old" ? (a, b) => a.mtime - b.mtime : (a, b) => b.mtime - a.mtime; - node.children.sort((a, b) => (a.type === "dir" && b.type !== "dir") ? -1 : (a.type !== "dir" && b.type === "dir") ? 1 : cmp(a, b)); - node.children.forEach(c => c.type === "dir" && sortDir(c, sort)); -} -function renderNode(node) { - if (node.type === "dir") { - const div = document.createElement("div"); - div.className = "dir"; - div.setAttribute("aria-expanded", "false"); - const lbl = document.createElement("span"); - lbl.className = "label"; - lbl.textContent = node.name || "/"; - lbl.addEventListener("click", () => { - const idx = node.children.find(c => c.type === "file" && /^index\.(md|html)$/i.test(c.name)); - if (idx) openPath(idx.path); - else div.classList.toggle("open"); - }); - div.appendChild(lbl); - const kids = document.createElement("div"); - kids.className = "children"; - node.children.forEach(c => kids.appendChild(renderNode(c))); - div.appendChild(kids); - return div; - } - const a = document.createElement("a"); - a.className = "file"; - a.innerHTML = `${node.pinned ? '๐Ÿ“Œ' : ''}${iconForExt(node.ext)} ${node.title} (${fmtDate(node.mtime)} ยท ${node.name})`; - a.addEventListener("click", e => { e.preventDefault(); openPath(node.path); }); - PATH_TO_EL.set(node.path, a); - return a; -} -function iconForExt(ext) { return ext === ".md" ? "๐Ÿ“" : "๐Ÿงฉ"; } -function fmtDate(ms) { return new Date(ms).toISOString().slice(0, 10); } -function findDir(path) { - path = path.replace(/\/$/, ''); - function search(node) { - if (node.type === "dir" && node.path === path) return node; - for (const c of node.children || []) { - const found = search(c); - if (found) return found; - } - } - return search({ children: INDEX.tree }); -} -async function openPath(path) { - if (path === CURRENT_PATH) return; - CURRENT_PATH = path; - if (location.hash !== `#=${path}`) history.pushState(null, "", `#=${path}`); - let f = INDEX.flat.find(x => x.path === path); - if (!f) { - const dir = findDir(path); - if (dir) { - const idx = dir.children.find(c => c.type === "file" && /^index\.(md|html)$/i.test(c.name)); - if (idx) return openPath(idx.path); - } - metaLine.textContent = "Path not found: " + path; - return; - } - metaLine.textContent = `${f.pinned ? "๐Ÿ“Œ " : ""}${fmtDate(f.mtime)} โ€ข ${f.name}`; - if (f.ext === ".md") await renderMarkdown(f.path); - else renderHTML(f.path); - setActive(path); - updatePager(); - if (window.innerWidth < 900) sidebar.classList.remove("open"); -} async function renderMarkdown(path) { mdView.style.display = "none"; const res = await fetch("/" + path); if (!res.ok) { mdView.innerHTML = "

File not found: " + path + "

"; requestAnimationFrame(() => mdView.style.display = "block"); return; } const text = await res.text(); - const html = window.marked ? window.marked.parse(text) : text.replace(/&/g, '&').replace(/ { mdView.style.display = "block"; htmlView.style.display = "none"; }); + if (usedFallback) console.warn("Markdown rendered as plain text: marked.js not loaded. Check CDN/SRI."); } -function renderHTML(path) { - htmlView.src = "/" + path; - htmlView.style.display = "block"; - mdView.style.display = "none"; -} -function setActive(path) { - document.querySelectorAll(".file.active").forEach(el => el.classList.remove("active")); - const el = PATH_TO_EL.get(path); - if (el) { - el.classList.add("active"); - let p = el.parentElement; - while (p && p !== treeEl) { - if (p.classList.contains("children")) p.parentElement.classList.add("open"); - p = p.parentElement; - } - } -} -function updatePager() { - const query = searchBox.value.trim().toLowerCase(); - const list = INDEX.flat.filter(f => (filterSel.value === "all" || f.path.split("/")[0] === filterSel.value) && (!query || f.title.toLowerCase().includes(query))); - const cmp = sortSel.value === "name" ? (a, b) => a.name.localeCompare(b.name) : - sortSel.value === "old" ? (a, b) => a.mtime - b.mtime : (a, b) => b.mtime - a.mtime; - list.sort(cmp); - const i = list.findIndex(x => x.path === CURRENT_PATH); - prevBtn.disabled = i <= 0; - nextBtn.disabled = i >= list.length - 1 || i < 0; - prevBtn.onclick = () => i > 0 && openPath(list[i - 1].path); - nextBtn.onclick = () => i < list.length - 1 && openPath(list[i + 1].path); -} -let searchTimer; -searchBox.addEventListener("input", () => { clearTimeout(searchTimer); searchTimer = setTimeout(rebuildTree, 300); }); -sortSel.addEventListener("change", rebuildTree); -filterSel.addEventListener("change", rebuildTree); -document.body.addEventListener("click", e => { - const a = e.target.closest("a[href]"); - if (!a) return; - const href = a.getAttribute("href"); - if (href.startsWith("/") && !href.startsWith("//") && !a.target) { - e.preventDefault(); - openPath(href.replace(/^\//, "")); - } -}); -window.addEventListener("resize", () => { if (window.innerWidth < 900) sidebar.classList.remove("open"); }); -window.addEventListener("DOMContentLoaded", loadIndex); \ No newline at end of file + +// ... (rest same) \ No newline at end of file