diff --git a/public/app.js b/public/app.js index d4bc223..558e780 100755 --- a/public/app.js +++ b/public/app.js @@ -1,240 +1,9 @@ const els = { menuBtn: document.getElementById("menuBtn"), primaryNav: document.getElementById("primaryNav"), + subNav: document.getElementById("subNav"), 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"), - toggleControls: document.getElementById("toggleControls"), - filterPanel: document.getElementById("filterPanel") -}; - -let indexData = null; -let sidebarOpen = false; - -async function init() { - try { - indexData = await (await fetch("index.json")).json(); - populateNav(); - populateSections(); - populateTags(); - wireUI(); - renderList(); - handleHash(); - window.addEventListener("hashchange", handleHash); - } catch (e) { - els.viewer.innerHTML = "

Error

Failed to load site data.

"; - } -} - -// TOP NAV: Only real section folders with index.* -function populateNav() { - els.primaryNav.innerHTML = 'Home'; - const navSections = [...new Set( - indexData.flat - .filter(f => f.isIndex && f.path.split("/").length > 1) - .map(f => f.path.split("/")[0]) - )].sort(); - navSections.forEach(s => { - els.primaryNav.innerHTML += `${s.charAt(0).toUpperCase() + s.slice(1)}`; - }); -} - -// DROPDOWN: Only sections with NON-index files -function populateSections() { - els.sectionSelect.innerHTML = ''; - indexData.sections.forEach(s => { - const opt = document.createElement("option"); - opt.value = s; opt.textContent = s; - els.sectionSelect.appendChild(opt); - }); - - if (indexData.sections.includes("posts")) { - els.sectionSelect.value = "posts"; - } else if (indexData.sections.length > 0) { - els.sectionSelect.value = indexData.sections[0]; - } -} - -function populateTags() { - indexData.tags.forEach(t => { - const opt = document.createElement("option"); - opt.value = t; opt.textContent = t; - els.tagSelect.appendChild(opt); - }); -} - -function formatTimestamp(ms) { - const d = new Date(ms); - return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`; -} - -function wireUI() { - els.menuBtn.addEventListener("click", () => { - 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", (e) => { - if (window.innerWidth < 1024 && document.body.classList.contains("sidebar-open")) { - if (!e.target.closest("#sidebar")) { - document.body.classList.remove("sidebar-open"); - sidebarOpen = false; - } - } - }); -} - -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.isIndex); - if (section !== "all") posts = posts.filter(p => p.path.split('/')[0] === section); - if (tags.length) posts = posts.filter(p => tags.every(t => p.tags.includes(t))); - if (query) { - posts = posts.filter(p => { - 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 posts found.
  • "; - posts.forEach(p => { - const li = document.createElement("li"); - const pin = p.isPinned ? "Star " : ""; - const time = formatTimestamp(p.ctime); - li.innerHTML = `${pin}${p.title}${time}`; - els.postList.appendChild(li); - }); -} - -function loadDefaultForSection(section) { - const posts = indexData.flat.filter(p => p.path.split('/')[0] === section && !p.isIndex); - if (!posts.length) { - els.viewer.innerHTML = `

    ${section}

    No content yet.

    `; - return; - } - 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.innerHTML = ""; - const rel = location.hash.replace(/^#\//, ""); - if (!rel) return renderDefault(); - - if (rel.endsWith('/')) { - const section = rel.slice(0, -1); - const indexFile = indexData.flat.find(f => - f.path.startsWith(section + "/") && f.isIndex - ); - - if (indexFile) { - try { - if (indexFile.ext === ".md") { - const src = await fetch(indexFile.path).then(r => r.ok ? r.text() : ""); - const html = marked.parse(src || `# ${section}\n\nNo content yet.`); - els.viewer.innerHTML = `
    ${html}
    `; - } else { - const iframe = document.createElement("iframe"); - iframe.src = "/" + indexFile.path; - iframe.loading = "eager"; - iframe.setAttribute("sandbox", "allow-same-origin allow-scripts allow-forms"); - els.viewer.appendChild(iframe); - - iframe.onload = () => { - try { - const doc = iframe.contentDocument; - const body = doc.body; - const hasContent = body && body.innerText.trim().length > 50; - if (!hasContent) { - doc.body.innerHTML = ` -
    -

    ${section}

    -

    No content yet.

    -
    - `; - doc.body.style.background = "#0b0b0b"; - } - } catch (e) {} - }; - } - } catch (e) { - els.viewer.innerHTML = `

    ${section}

    No content yet.

    `; - } - } else { - els.sectionSelect.value = section; - renderList(); - loadDefaultForSection(section); - } - } else { - const file = indexData.flat.find(f => f.path === rel); - if (!file) { - els.viewer.innerHTML = "

    404

    Not found.

    "; - return; - } - file.ext === ".md" ? await renderMarkdown(file.path) : renderIframe(file.path); - } -} - -async function renderMarkdown(rel) { - const src = await fetch(rel).then(r => r.ok ? r.text() : ""); - els.viewer.innerHTML = `
    ${marked.parse(src || "# Untitled")}
    `; -} - -function renderIframe(rel) { - const iframe = document.createElement("iframe"); - iframe.src = "/" + rel; - iframe.loading = "eager"; - iframe.setAttribute("sandbox", "allow-same-origin allow-scripts allow-forms"); - els.viewer.appendChild(iframe); - - iframe.onload = () => { - try { - 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;} - img, video, iframe {max-width:100%;height:auto;} - `; - doc.head.appendChild(style); - } catch (e) {} - }; -} - -function renderDefault() { - const defaultSection = indexData.sections.includes("posts") ? "posts" : (indexData.sections[0] || null); - if (defaultSection) { - els.sectionSelect.value = defaultSection; - renderList(); - loadDefaultForSection(defaultSection); - } else { - els.viewer.innerHTML = "

    Welcome

    Add content to begin.

    "; - } -} - -init(); \ No newline at end of file + searchBox: \ No newline at end of file