From 6fa735fa1c430d8c060effd414f26a8b14418ec6 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 16:09:05 -0600 Subject: [PATCH] Update app.js --- public/app.js | 93 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 36 deletions(-) diff --git a/public/app.js b/public/app.js index 36cd5a5..9266a68 100755 --- a/public/app.js +++ b/public/app.js @@ -3,19 +3,17 @@ const els = { menuBtn: document.getElementById("menuBtn"), primaryNav: document.getElementById("primaryNav"), sectionSelect: document.getElementById("sectionSelect"), + tagSelect: document.getElementById("tagSelect"), sortSelect: document.getElementById("sortSelect"), + searchMode: document.getElementById("searchMode"), searchBox: document.getElementById("searchBox"), postList: document.getElementById("postList"), viewer: document.getElementById("viewer"), content: document.getElementById("content") }; -const staticPages = new Set(["about", "contact", "legal"]); -const sectionIcons = { // Optional: Add icons per section (e.g., 'essays': '✍️') - essays: '✍️', - fieldnotes: '📓', - pinned: '📌' -}; +const sectionIcons = { essays: '✍️', fieldnotes: '📓', pinned: '📌' }; +const tagIcons = { /* Optional: e.g., 'tech': '🔧' */ }; let indexData = null; let sidebarOpen = false; @@ -25,6 +23,7 @@ async function init() { indexData = await (await fetch("index.json")).json(); populateNav(); populateSections(); + populateTags(); wireUI(); renderList(); handleHash(); @@ -36,33 +35,40 @@ async function init() { function populateNav() { els.primaryNav.innerHTML = 'Home'; - staticPages.forEach(p => { - els.primaryNav.innerHTML += `${p.charAt(0).toUpperCase() + p.slice(1)}`; - }); indexData.sections.forEach(s => { - els.primaryNav.innerHTML += `${s.charAt(0).toUpperCase() + s.slice(1)}`; + els.primaryNav.innerHTML += `${s.name.charAt(0).toUpperCase() + s.name.slice(1)}`; }); } function populateSections() { - els.sectionSelect.innerHTML = ""; - indexData.sections.forEach(s => { - const icon = sectionIcons[s] ? `${sectionIcons[s]} ` : ''; + els.sectionSelect.innerHTML = ''; + indexData.sections.filter(s => !s.isStatic).forEach(s => { // Only dynamic in drop-down + const icon = sectionIcons[s.name] ? `${sectionIcons[s.name]} ` : ''; const opt = document.createElement("option"); - opt.value = s; - opt.textContent = `${icon}${s}`; + opt.value = s.name; + opt.textContent = `${icon}${s.name}`; 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}`; // Tooltip for elegance + els.tagSelect.appendChild(opt); + }); +} + 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.sectionSelect, els.tagSelect, els.sortSelect, els.searchMode, els.searchBox].forEach(el => el.addEventListener("change", renderList) || el.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"); @@ -73,14 +79,23 @@ function wireUI() { function renderList() { const section = els.sectionSelect.value; + const tags = Array.from(els.tagSelect.selectedOptions).map(o => o.value.toLowerCase()); const sort = els.sortSelect.value; + const mode = els.searchMode.value; const query = els.searchBox.value.toLowerCase(); - let posts = indexData.flat.filter(p => p.path.split('/')[0] === section); - if (query) posts = posts.filter(p => p.title.toLowerCase().includes(query)); + let posts = indexData.flat; + 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))); // AND for coherence + if (query) { + posts = posts.filter(p => { + const searchText = mode === "content" ? (p.title + ' ' + p.excerpt).toLowerCase() : p.title.toLowerCase(); + return searchText.includes(query); + }); + } posts.sort((a, b) => sort === "newest" ? b.mtime - a.mtime : a.mtime - b.mtime); - els.postList.innerHTML = ""; + els.postList.innerHTML = posts.length ? "" : "
  • No matching posts found. Try adjusting filters.
  • "; for (const p of posts) { const li = document.createElement("li"); const pin = p.pinned ? "★ " : ""; @@ -90,31 +105,37 @@ function renderList() { } async function handleHash() { + els.viewer.classList.remove("fade-in"); // Reset for animation els.viewer.innerHTML = ""; + void els.viewer.offsetWidth; // Trigger reflow + els.viewer.classList.add("fade-in"); const rel = location.hash.replace(/^#\//, ""); if (!rel) return renderDefault(); if (rel.endsWith('/')) { - const section = rel.replace(/\/$/, ''); - if (staticPages.has(section)) { - renderIframe(`${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 content in this section yet. Check back soon!

    `; + const sectionName = rel.replace(/\/$/, ''); + const section = indexData.sections.find(s => s.name === sectionName); + if (section) { + if (section.isStatic) { + renderIframe(`${sectionName}/index.html`); } else { - const latest = sectionPosts.sort((a, b) => b.mtime - a.mtime)[0]; - location.hash = '#/' + latest.path; + els.sectionSelect.value = sectionName; + renderList(); + const sectionPosts = indexData.flat.filter(p => p.path.split('/')[0] === sectionName); + if (sectionPosts.length === 0) { + els.viewer.innerHTML = `

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

    No content yet. Add files and redeploy!

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

    404: Section Not Found

    The requested section does not exist.

    '; + els.viewer.innerHTML = '

    404: Section Not Found

    Try navigating from the menu.

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

    404: File Not Found

    The requested file could not be located.

    '; + els.viewer.innerHTML = '

    404: File Not Found

    Check the URL or search again.

    '; return; } try { @@ -124,7 +145,7 @@ async function handleHash() { renderIframe(file.path); } } catch (e) { - els.viewer.innerHTML = '

    Error Loading Content

    Unable to load the file. It may be corrupted or inaccessible.

    '; + els.viewer.innerHTML = '

    Error Loading Content

    Unable to load. File may be invalid.

    '; } } } @@ -160,7 +181,7 @@ function renderDefault() { if (latest) { location.hash = "#/" + latest.path; } else { - els.viewer.innerHTML = '

    Welcome

    No content yet. Add files to sections and redeploy!

    '; + els.viewer.innerHTML = '

    Welcome to The Fold Within

    Add content to sections and redeploy to get started.

    '; } }