diff --git a/public/app.js b/public/app.js index ee2f479..5dc1234 100755 --- a/public/app.js +++ b/public/app.js @@ -9,83 +9,118 @@ const els = { content: document.getElementById("content") }; -let indexData = null; -let state = { section: "posts", sort: "newest", query: "", sidebarOpen: false }; +const staticPages = new Set(["about", "contact", "legal"]); -async function init(){ - indexData = await (await fetch("index.json")).json(); - populateSections(); - wireUI(); - renderList(); - handleHash(); - window.addEventListener("hashchange", handleHash); +let indexData = null; +let sidebarOpen = false; + +async function init() { + try { + indexData = await (await fetch("index.json")).json(); + populateSections(); + wireUI(); + renderList(); + handleHash(); + window.addEventListener("hashchange", handleHash); + } catch (e) { + els.viewer.innerHTML = "

Error Loading Index

Failed to load site data. Please try refreshing.

"; + } } -function populateSections(){ +function populateSections() { els.sectionSelect.innerHTML = ""; - indexData.sections.forEach(s=>{ + indexData.sections.forEach(s => { const opt = document.createElement("option"); - opt.value = s; opt.textContent = s; + opt.value = s; + opt.textContent = s; els.sectionSelect.appendChild(opt); }); } -function wireUI(){ - els.menuBtn.addEventListener("click", ()=>{ - state.sidebarOpen = !state.sidebarOpen; - document.body.classList.toggle("sidebar-open", state.sidebarOpen); +function wireUI() { + els.menuBtn.addEventListener("click", () => { + sidebarOpen = !sidebarOpen; + document.body.classList.toggle("sidebar-open", sidebarOpen); }); - els.sectionSelect.addEventListener("change", ()=>renderList()); - els.sortSelect.addEventListener("change", ()=>renderList()); - els.searchBox.addEventListener("input", ()=>renderList()); - els.content.addEventListener("click", ()=>{ - if (window.matchMedia("(max-width:1024px)").matches && document.body.classList.contains("sidebar-open")){ + els.sectionSelect.addEventListener("change", renderList); + els.sortSelect.addEventListener("change", renderList); + els.searchBox.addEventListener("input", renderList); + els.content.addEventListener("click", () => { + if (window.matchMedia("(max-width:1024px)").matches && document.body.classList.contains("sidebar-open")) { document.body.classList.remove("sidebar-open"); - state.sidebarOpen = false; + sidebarOpen = false; } }); } -function renderList(){ +function renderList() { const section = els.sectionSelect.value; const sort = els.sortSelect.value; const query = els.searchBox.value.toLowerCase(); - let posts = indexData.flat.filter(p=>p.path.startsWith(section)); - if (query) posts = posts.filter(p=>p.title.toLowerCase().includes(query)); - posts.sort((a,b)=> sort==="newest"? b.mtime-a.mtime : a.mtime-b.mtime); + let posts = indexData.flat.filter(p => p.path.split('/')[0] === section); + if (query) posts = posts.filter(p => p.title.toLowerCase().includes(query)); + posts.sort((a, b) => sort === "newest" ? b.mtime - a.mtime : a.mtime - b.mtime); els.postList.innerHTML = ""; - for (const p of posts){ + for (const p of posts) { const li = document.createElement("li"); - li.innerHTML = `${p.title}
${new Date(p.mtime).toISOString().split("T")[0]}`; + const pin = p.pinned ? "★ " : ""; + li.innerHTML = `${pin}${p.title}
${new Date(p.mtime).toISOString().split("T")[0]}`; els.postList.appendChild(li); } } -async function handleHash(){ - const rel = location.hash.replace(/^#\//,""); +async function handleHash() { + els.viewer.innerHTML = ""; // Clear viewer to avoid stale content + const rel = location.hash.replace(/^#\//, ""); if (!rel) return renderDefault(); - const file = indexData.flat.find(f=>f.path===rel); - if (!file) return; - file.ext===".md"? await renderMarkdown(file.path) : renderHTML(file.path); + + if (rel.endsWith('/')) { + const section = rel.replace(/\/$/, ''); + if (staticPages.has(section)) { + renderHTML(section + '/index.html'); + } else if (indexData.sections.includes(section)) { + els.sectionSelect.value = section; + renderList(); + const sectionPosts = indexData.flat.filter(p => p.path.split('/')[0] === section); + if (sectionPosts.length === 0) { + els.viewer.innerHTML = `

${section.charAt(0).toUpperCase() + section.slice(1)}

No posts in this section yet.

`; + } else { + const latest = sectionPosts.sort((a, b) => b.mtime - a.mtime)[0]; + location.hash = '#/' + latest.path; // Triggers hashchange to load + } + } else { + els.viewer.innerHTML = '

404 Not Found

'; + } + } else { + const file = indexData.flat.find(f => f.path === rel); + if (!file) { + els.viewer.innerHTML = '

404 Not Found

'; + return; + } + try { + file.ext === ".md" ? await renderMarkdown(file.path) : renderHTML(file.path); + } catch (e) { + els.viewer.innerHTML = '

Error Loading Content

Failed to load the file. Please try another.

'; + } + } } -async function renderMarkdown(rel){ - const src = await fetch(rel).then(r=>r.text()); +async function renderMarkdown(rel) { + const src = await fetch(rel).then(r => { if (!r.ok) throw new Error(); return r.text(); }); const html = marked.parse(src); els.viewer.innerHTML = `
${html}
`; } -function renderHTML(rel){ +function renderHTML(rel) { const iframe = document.createElement("iframe"); - iframe.setAttribute("sandbox","allow-same-origin allow-scripts allow-forms"); + iframe.setAttribute("sandbox", "allow-same-origin allow-scripts allow-forms"); iframe.loading = "eager"; iframe.src = "/" + rel; - els.viewer.innerHTML = ""; els.viewer.appendChild(iframe); - iframe.addEventListener("load", ()=>{ - try{ + iframe.addEventListener("load", () => { + try { const d = iframe.contentDocument || iframe.contentWindow.document; const s = d.createElement("style"); s.textContent = ` @@ -93,12 +128,12 @@ function renderHTML(rel){ main,article,section{max-width:720px;margin:auto;padding:2rem;} `; d.head.appendChild(s); - }catch{} + } catch {} }); } -function renderDefault(){ - const latest = [...indexData.flat].sort((a,b)=>b.mtime-a.mtime)[0]; +function renderDefault() { + const latest = [...indexData.flat].sort((a, b) => b.mtime - a.mtime)[0]; if (latest) location.hash = "#/" + latest.path; }