/* ========================================================= SIDEBAR — reads /assets/sidebar-tree.json Falls back to search-index.json if tree unavailable. sidebar-tree.json shape: { "groups": [ { "label": "java", "files": [{ "title": "...", "url": "..." }, ...] }, ... { "label": "uncategorised", "files": [...] } ] } ========================================================= */ async function buildSidebar() { const sidebar = document.getElementById("sidebar"); if (!sidebar) return; const currentPath = location.pathname; // --- Files label (kept from original) --- const filesLabel = el("div", { class: "sidebar-section-label" }); filesLabel.textContent = "files"; sidebar.appendChild(filesLabel); const tree = el("div", { id: "file-tree" }); sidebar.appendChild(tree); // --- Try sidebar-tree.json first --- let groups = null; try { const res = await fetch("/assets/sidebar-tree.json"); if (res.ok) { const data = await res.json(); if (Array.isArray(data.groups) && data.groups.length) { groups = data.groups; } } } catch (_) { /* fall through */ } // --- Fallback: build a single flat group from search-index.json --- if (!groups) { groups = await buildGroupsFromSearchIndex(); } if (!groups || !groups.length) { const empty = el("div", { class: "tree-folder" }); empty.style.cssText = "opacity:.4;font-size:.7rem;padding:8px 14px;"; empty.textContent = "no notes found"; tree.appendChild(empty); return; } // --- Render each group as a collapsible folder --- groups.forEach(group => { const { label, files } = group; if (!files || !files.length) return; // Folder row const folderEl = el("div", { class: "tree-folder", "data-folder": label, }); const icon = el("span", { class: "tree-icon" }); icon.textContent = "▾"; const labelSpan = el("span", { class: "tree-folder-label" }); labelSpan.textContent = label; const countBadge = el("span", { class: "tree-folder-count" }); countBadge.textContent = files.length; folderEl.append(icon, labelSpan, countBadge); // Children container const children = el("div", { class: "tree-children" }); files.forEach(f => { const pathname = normalisePathname(f.url); children.appendChild(makeFileItem({ title: f.title, pathname }, currentPath)); }); // Collapse / expand folderEl.addEventListener("click", () => { const isOpen = children.style.display !== "none"; children.style.display = isOpen ? "none" : ""; icon.textContent = isOpen ? "▸" : "▾"; folderEl.classList.toggle("collapsed", isOpen); }); tree.appendChild(folderEl); tree.appendChild(children); }); // --- Pinned section (unchanged from original) --- try { const res = await fetch("/assets/search-index.json"); if (res.ok) { const raw = await res.json(); const all = Array.isArray(raw) ? raw : (raw.entries || raw.notes || []); const pinned = all .filter(e => /\/(inbox|daily[-_]?log|journal|home|index|moc)\.html$/i .test(e.url || e.path || e.pathname || "")) .map(e => ({ title: e.title || filenameToTitle(e.url || e.path || e.pathname), pathname: normalisePathname(e.url || e.path || e.pathname), })); if (pinned.length) { const divider = el("div", { class: "sidebar-divider" }); const pinnedLabel = el("div", { class: "sidebar-section-label" }); pinnedLabel.textContent = "pinned"; const pinnedWrap = el("div", { id: "sidebar-pinned" }); pinned.forEach(f => pinnedWrap.appendChild(makeFileItem(f, currentPath))); sidebar.append(divider, pinnedLabel, pinnedWrap); } } } catch (_) { /* pinned section is optional */ } } /* Fallback: turns search-index.json into a single "all notes" group */ async function buildGroupsFromSearchIndex() { try { const res = await fetch("/assets/search-index.json"); if (!res.ok) return null; const raw = await res.json(); const all = Array.isArray(raw) ? raw : (raw.entries || raw.notes || []); const files = all .filter(e => e && (e.url || e.path || e.pathname)) .map(e => ({ title: e.title || filenameToTitle(e.url || e.path || e.pathname), url: e.url || e.path || e.pathname, })) .sort((a, b) => a.title.localeCompare(b.title)); return files.length ? [{ label: "all notes", files }] : null; } catch (_) { return null; } } /* ========================================================= CSS to paste into style.css (tree-folder-count badge + collapsed state) ========================================================= Add this block anywhere after the existing .tree-folder rules: .tree-folder-count { margin-left: auto; font-size: 0.65rem; font-family: var(--font-mono); color: var(--fg-faint); background: var(--chip-bg); border: 1px solid var(--border); border-radius: 8px; padding: 0 5px; flex-shrink: 0; line-height: 1.6; } .tree-folder.collapsed { opacity: 0.75; } .tree-folder-label { flex: 1; overflow: hidden; text-overflow: ellipsis; } ========================================================= */