diff --git a/public/app.js b/public/app.js index 070d741..d8d4e07 100755 --- a/public/app.js +++ b/public/app.js @@ -1,5 +1,4 @@ const els = { - body: document.body, menuBtn: document.getElementById("menuBtn"), primaryNav: document.getElementById("primaryNav"), sectionSelect: document.getElementById("sectionSelect"), @@ -9,12 +8,11 @@ const els = { searchBox: document.getElementById("searchBox"), postList: document.getElementById("postList"), viewer: document.getElementById("viewer"), - content: document.getElementById("content") + content: document.getElementById("content"), + toggleControls: document.getElementById("toggleControls"), + filterPanel: document.getElementById("filterPanel") }; -const sectionIcons = { essays: '✍️', fieldnotes: '📓', pinned: '📌' }; -const tagIcons = { /* Optional: e.g., 'tech': '🔧' */ }; - let indexData = null; let sidebarOpen = false; @@ -29,36 +27,33 @@ async function init() { handleHash(); window.addEventListener("hashchange", handleHash); } catch (e) { - els.viewer.innerHTML = "

Error Loading Site

Failed to load index data. Please refresh or check connection.

"; + els.viewer.innerHTML = "

Error

Failed to load site data.

"; } } function populateNav() { els.primaryNav.innerHTML = 'Home'; - indexData.sections.filter(s => indexData.flat.some(f => f.path.split('/')[0] === s && f.isIndex)).forEach(s => { - els.primaryNav.innerHTML += `${s.charAt(0).toUpperCase() + s.slice(1)}`; + indexData.sections.forEach(s => { + const hasIndex = indexData.flat.some(f => f.path.startsWith(s + "/") && f.isIndex); + if (hasIndex) { + els.primaryNav.innerHTML += `${s.charAt(0).toUpperCase() + s.slice(1)}`; + } }); } function populateSections() { els.sectionSelect.innerHTML = ''; indexData.sections.forEach(s => { - const icon = sectionIcons[s] ? `${sectionIcons[s]} ` : ''; const opt = document.createElement("option"); - opt.value = s; - opt.textContent = `${icon}${s}`; + opt.value = s; opt.textContent = s; els.sectionSelect.appendChild(opt); }); } function populateTags() { - els.tagSelect.innerHTML = ''; indexData.tags.forEach(t => { - const icon = tagIcons[t] ? `${tagIcons[t]} ` : ''; const opt = document.createElement("option"); - opt.value = t; - opt.textContent = `${icon}${t}`; - opt.title = `Filter by ${t}`; + opt.value = t; opt.textContent = t; els.tagSelect.appendChild(opt); }); } @@ -68,14 +63,23 @@ function wireUI() { sidebarOpen = !sidebarOpen; document.body.classList.toggle("sidebar-open", sidebarOpen); }); + + els.toggleControls.addEventListener("click", () => { + const open = els.filterPanel.open; + els.filterPanel.open = !open; + els.toggleControls.textContent = open ? "Filters" : "Hide"; + }); + els.sectionSelect.addEventListener("change", () => { renderList(); if (els.sectionSelect.value !== "all") loadDefaultForSection(els.sectionSelect.value); }); + [els.tagSelect, els.sortSelect, els.searchMode].forEach(el => el.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")) { + if (window.innerWidth < 1024 && document.body.classList.contains("sidebar-open")) { document.body.classList.remove("sidebar-open"); sidebarOpen = false; } @@ -89,65 +93,47 @@ function renderList() { const mode = els.searchMode.value; const query = els.searchBox.value.toLowerCase(); - let posts = indexData.flat.filter(p => !p.isIndex); // Exclude index from lists + let posts = indexData.flat.filter(p => !p.isIndex); if (section !== "all") posts = posts.filter(p => p.path.split('/')[0] === section); - if (tags.length > 0) posts = posts.filter(p => tags.every(t => p.tags.includes(t))); + if (tags.length) posts = posts.filter(p => tags.every(t => p.tags.includes(t))); if (query) { posts = posts.filter(p => { - const searchText = mode === "content" ? (p.title + ' ' + p.excerpt).toLowerCase() : p.title.toLowerCase(); - return searchText.includes(query); + const text = mode === "content" ? p.title + " " + p.excerpt : p.title; + return text.toLowerCase().includes(query); }); } posts.sort((a, b) => sort === "newest" ? b.mtime - a.mtime : a.mtime - b.mtime); - els.postList.innerHTML = posts.length ? "" : "
  • No matching posts found. Try adjusting filters.
  • "; - for (const p of posts) { + els.postList.innerHTML = posts.length ? "" : "
  • No posts found.
  • "; + posts.forEach(p => { const li = document.createElement("li"); - const pin = p.isPinned ? "★ " : ""; - li.innerHTML = `${pin}${p.title}
    ${new Date(p.mtime).toISOString().split("T")[0]}`; + const pin = p.isPinned ? "Star " : ""; + li.innerHTML = `${pin}${p.title}${new Date(p.mtime).toISOString().split("T")[0]}`; els.postList.appendChild(li); - } + }); } function loadDefaultForSection(section) { - const posts = indexData.flat.filter(p => p.path.split('/')[0] === section && !p.isIndex); // Exclude index + const posts = indexData.flat.filter(p => p.path.split('/')[0] === section && !p.isIndex); if (!posts.length) { - els.viewer.innerHTML = `

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

    No content yet. Add files and redeploy!

    `; + els.viewer.innerHTML = `

    ${section}

    No content yet.

    `; return; } - const pinned = posts.filter(p => p.isPinned).sort((a, b) => b.mtime - a.mtime)[0]; - const toLoad = pinned || posts.sort((a, b) => b.mtime - a.mtime)[0]; - location.hash = '#/' + toLoad.path; + const pinned = posts.find(p => p.isPinned) || posts.sort((a,b) => b.mtime - a.mtime)[0]; + location.hash = `#/${pinned.path}`; } async function handleHash() { - els.viewer.classList.remove("fade-in"); els.viewer.innerHTML = ""; - void els.viewer.offsetWidth; - els.viewer.classList.add("fade-in"); const rel = location.hash.replace(/^#\//, ""); if (!rel) return renderDefault(); if (rel.endsWith('/')) { - const section = rel.replace(/\/$/, ''); - if (!indexData.sections.includes(section)) { - els.viewer.innerHTML = '

    404: Section Not Found

    Try navigating from the menu.

    '; - return; - } - const indexFile = indexData.flat.find(f => f.path.split('/')[0] === section && f.isIndex); + const section = rel.slice(0, -1); + const indexFile = indexData.flat.find(f => f.path.startsWith(section + "/") && f.isIndex); if (indexFile) { - // Load index for top nav - try { - if (indexFile.ext === ".md") { - await renderMarkdown(indexFile.path); - } else { - renderIframe(indexFile.path); - } - } catch (e) { - els.viewer.innerHTML = '

    Error Loading Index

    Unable to load section index.

    '; - } + indexFile.ext === ".md" ? await renderMarkdown(indexFile.path) : renderIframe(indexFile.path); } else { - // Dynamic load for drop-down style els.sectionSelect.value = section; renderList(); loadDefaultForSection(section); @@ -155,54 +141,43 @@ async function handleHash() { } else { const file = indexData.flat.find(f => f.path === rel); if (!file) { - els.viewer.innerHTML = '

    404: File Not Found

    Check the URL or search again.

    '; + els.viewer.innerHTML = "

    404

    Not found.

    "; return; } - try { - if (file.ext === ".md") { - await renderMarkdown(file.path); - } else { - renderIframe(file.path); - } - } catch (e) { - els.viewer.innerHTML = '

    Error Loading Content

    Unable to load. File may be invalid.

    '; - } + file.ext === ".md" ? await renderMarkdown(file.path) : renderIframe(file.path); } } async function renderMarkdown(rel) { - const src = await fetch(rel).then(r => { if (!r.ok) throw new Error('Fetch failed'); return r.text(); }); - const html = marked.parse(src); - els.viewer.innerHTML = `
    ${html}
    `; + const src = await fetch(rel).then(r => r.ok ? r.text() : Promise.reject()); + els.viewer.innerHTML = `
    ${marked.parse(src)}
    `; } function renderIframe(rel) { const iframe = document.createElement("iframe"); - iframe.setAttribute("sandbox", "allow-same-origin allow-scripts allow-forms"); - iframe.loading = "eager"; iframe.src = "/" + rel; + iframe.loading = "eager"; + iframe.setAttribute("sandbox", "allow-same-origin allow-scripts allow-forms"); els.viewer.appendChild(iframe); - iframe.addEventListener("load", () => { + + iframe.onload = () => { if (rel.endsWith('.pdf')) return; try { - const d = iframe.contentDocument || iframe.contentWindow.document; - const s = d.createElement("style"); - s.textContent = ` - html,body{margin:0;padding:0;background:transparent;color:#e6e3d7;font:16px/1.6 Inter,ui-sans-serif;} - main,article,section{max-width:720px;margin:auto;padding:2rem;} + const doc = iframe.contentDocument; + const style = doc.createElement("style"); + style.textContent = ` + html,body{background:#0b0b0b;color:#e6e3d7;font-family:Inter,sans-serif;margin:0;padding:2rem;} + *{max-width:720px;margin:auto;} `; - d.head.appendChild(s); + doc.head.appendChild(style); } catch {} - }); + }; } function renderDefault() { - const latest = [...indexData.flat].sort((a, b) => b.mtime - a.mtime)[0]; - if (latest) { - location.hash = "#/" + latest.path; - } else { - els.viewer.innerHTML = '

    Welcome to The Fold Within

    Add content to sections and redeploy to get started.

    '; - } + const latest = indexData.flat.filter(f => !f.isIndex).sort((a,b) => b.mtime - a.mtime)[0]; + if (latest) location.hash = `#/${latest.path}`; + else els.viewer.innerHTML = "

    Welcome

    Add content to begin.

    "; } init(); \ No newline at end of file