Update app.js
This commit is contained in:
parent
e3b2c4638c
commit
1897be3318
1 changed files with 50 additions and 57 deletions
105
public/app.js
105
public/app.js
|
|
@ -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,"&").replace(/</g,"<");}
|
||||||
|
|
||||||
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,"&").replace(/</g,"<");
|
mdView.offsetHeight; // force reflow
|
||||||
}
|
|
||||||
|
|
||||||
const safe = window.DOMPurify ? window.DOMPurify.sanitize(html) : html;
|
|
||||||
|
|
||||||
requestAnimationFrame(()=>{
|
|
||||||
mdView.innerHTML = safe;
|
|
||||||
mdView.scrollTop = 0;
|
|
||||||
mdView.classList.add("fade-in");
|
mdView.classList.add("fade-in");
|
||||||
mdView.style.display = "block";
|
if(usedFallback) mdWarn.style.display="block";
|
||||||
if (usedFallback) mdWarn.style.display = "block";
|
|
||||||
});
|
|
||||||
|
|
||||||
}catch(e){
|
// layout stabilization
|
||||||
mdView.innerHTML=`<p style='color:red;'>${e.message}</p>`;
|
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);
|
||||||
|
|
||||||
|
}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);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue