Update app.js

This commit is contained in:
Mark Randall Havens △ The Empathic Technologist ⟁ Doctor Who 42 2025-11-08 13:21:57 -06:00 committed by GitHub
parent e3b2c4638c
commit 1897be3318
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,5 @@
/* ============================================================ /* ============================================================
The Fold Within Static Framework v2.4.1 (Layout fix) The Fold Within Framework v2.5 Stable Render Build
============================================================ */ ============================================================ */
let INDEX, CURRENT_PATH = null, PATH_TO_EL = new Map(); let INDEX, CURRENT_PATH = null, PATH_TO_EL = new Map();
@ -22,11 +22,8 @@ const overlay = document.querySelector(".overlay");
navToggle.addEventListener("click", () => sidebar.classList.toggle("open")); navToggle.addEventListener("click", () => sidebar.classList.toggle("open"));
overlay.addEventListener("click", () => sidebar.classList.remove("open")); overlay.addEventListener("click", () => sidebar.classList.remove("open"));
/* Load index */ /* Load index and init */
async function loadIndex() { async function loadIndex() {
if (!window.marked) console.warn("⚠️ marked.js not detected.");
if (!window.DOMPurify) console.warn("⚠️ DOMPurify not detected.");
const res = await fetch("/index.json", { cache: "no-store" }); const res = await fetch("/index.json", { cache: "no-store" });
INDEX = await res.json(); INDEX = await res.json();
populateFilters(); populateFilters();
@ -53,7 +50,7 @@ function populateFilters() {
} }
} }
/* Tree rendering */ /* Tree */
function rebuildTree() { function rebuildTree() {
treeEl.innerHTML = ""; treeEl.innerHTML = "";
PATH_TO_EL.clear(); PATH_TO_EL.clear();
@ -87,7 +84,7 @@ function sortDir(node, sort) {
function renderNode(n) { function renderNode(n) {
if (n.type==="dir") { if (n.type==="dir") {
const d = document.createElement("div"); const d = document.createElement("div");
d.className="dir"; d.setAttribute("aria-expanded","false"); d.className="dir";
const lbl=document.createElement("span"); const lbl=document.createElement("span");
lbl.className="label"; lbl.textContent=n.name||"/"; lbl.className="label"; lbl.textContent=n.name||"/";
lbl.addEventListener("click",()=>{ lbl.addEventListener("click",()=>{
@ -112,30 +109,14 @@ function renderNode(n) {
function iconForExt(ext){return ext===".md"?"📝":"🧩";} function iconForExt(ext){return ext===".md"?"📝":"🧩";}
function fmtDate(ms){return new Date(ms).toISOString().slice(0,10);} function fmtDate(ms){return new Date(ms).toISOString().slice(0,10);}
function findDir(p){ /* Open file */
p=p.replace(/\/$/,'');
function search(n){
if(n.type==="dir"&&n.path===p) return n;
for(const c of n.children||[]){const f=search(c);if(f)return f;}
}
return search({children:INDEX.tree});
}
async function openPath(path){ async function openPath(path){
if(path===CURRENT_PATH) return; if(path===CURRENT_PATH) return;
CURRENT_PATH=path; CURRENT_PATH=path;
if(location.hash!==`#=${path}`) history.pushState(null,"",`#=${path}`); if(location.hash!==`#=${path}`) history.pushState(null,"",`#=${path}`);
let f=INDEX.flat.find(x=>x.path===path); const f=INDEX.flat.find(x=>x.path===path);
if(!f){ if(!f){ metaLine.textContent="Path not found: "+path; return; }
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}`; metaLine.textContent=`${f.pinned?"📌 ":""}${fmtDate(f.mtime)}${f.name}`;
@ -147,7 +128,7 @@ async function openPath(path){
if(window.innerWidth<900) sidebar.classList.remove("open"); if(window.innerWidth<900) sidebar.classList.remove("open");
} }
/* Markdown */ /* Markdown render */
async function renderMarkdown(path){ async function renderMarkdown(path){
mdWarn.style.display = "none"; mdWarn.style.display = "none";
mdView.innerHTML="<p class='loading-note'>Loading…</p>"; mdView.innerHTML="<p class='loading-note'>Loading…</p>";
@ -155,43 +136,55 @@ async function renderMarkdown(path){
mdView.style.display="block"; mdView.style.display="block";
try{ try{
const res=await fetch("/"+path, { cache: "no-store" }); const res=await fetch("/"+path,{cache:"no-store"});
if(!res.ok) throw new Error("File not found: "+path);
const text=await res.text(); const text=await res.text();
let usedFallback = false; let html, usedFallback=false;
let html; if(window.marked) html=window.marked.parse(text);
else {usedFallback=true; html=text.replace(/&/g,"&amp;").replace(/</g,"&lt;");}
if (window.marked) { const safe=window.DOMPurify?window.DOMPurify.sanitize(html):html;
html = window.marked.parse(text); mdView.classList.remove("fade-in");
} else { mdView.innerHTML=safe;
usedFallback = true; mdView.scrollTop=0;
html = text.replace(/&/g,"&amp;").replace(/</g,"&lt;"); mdView.offsetHeight; // force reflow
} mdView.classList.add("fade-in");
if(usedFallback) mdWarn.style.display="block";
const safe = window.DOMPurify ? window.DOMPurify.sanitize(html) : html; // layout stabilization
setTimeout(()=>{
const content=document.querySelector(".content");
if(content){
const vh=window.innerHeight;
content.style.minHeight=`${vh-48}px`;
content.style.paddingBottom="40px";
}
mdView.scrollIntoView({behavior:"instant",block:"start"});
},80);
requestAnimationFrame(()=>{ }catch(e){ mdView.innerHTML=`<p style='color:red;'>${e.message}</p>`; }
mdView.innerHTML = safe;
mdView.scrollTop = 0;
mdView.classList.add("fade-in");
mdView.style.display = "block";
if (usedFallback) mdWarn.style.display = "block";
});
}catch(e){
mdView.innerHTML=`<p style='color:red;'>${e.message}</p>`;
}
} }
/* HTML */ /* HTML render */
function renderHTML(path){ function renderHTML(path){
htmlView.src="/"+path; htmlView.src="/"+path;
htmlView.style.display="block"; htmlView.style.display="block";
mdView.style.display="none"; mdView.style.display="none";
htmlView.classList.remove("fade-in");
htmlView.offsetHeight;
htmlView.classList.add("fade-in");
setTimeout(()=>{
const content=document.querySelector(".content");
if(content){
const vh=window.innerHeight;
content.style.minHeight=`${vh-48}px`;
}
htmlView.scrollIntoView({behavior:"instant",block:"start"});
},120);
} }
/* Active + Pager */ /* Active & Pager */
function setActive(path){ function setActive(path){
document.querySelectorAll(".file.active").forEach(el=>el.classList.remove("active")); document.querySelectorAll(".file.active").forEach(el=>el.classList.remove("active"));
const el=PATH_TO_EL.get(path); const el=PATH_TO_EL.get(path);
@ -221,7 +214,7 @@ function updatePager(){
nextBtn.onclick=()=>i<list.length-1&&openPath(list[i+1].path); nextBtn.onclick=()=>i<list.length-1&&openPath(list[i+1].path);
} }
/* Controls */ /* Search/filter */
let searchTimer; let searchTimer;
searchBox.addEventListener("input",()=>{ searchBox.addEventListener("input",()=>{
clearTimeout(searchTimer); clearTimeout(searchTimer);
@ -241,11 +234,11 @@ document.body.addEventListener("click",e=>{
} }
}); });
/* Resize listener (mobile full screen fix) */ /* Resize listener */
window.addEventListener("resize", () => { window.addEventListener("resize",()=>{
const vh = window.innerHeight; const vh=window.innerHeight;
const content = document.querySelector(".content"); const c=document.querySelector(".content");
if (content) content.style.minHeight = `${vh - 48}px`; if(c) c.style.minHeight=`${vh-48}px`;
}); });
window.addEventListener("DOMContentLoaded",loadIndex); window.addEventListener("DOMContentLoaded",loadIndex);