169 lines
5.3 KiB
JavaScript
169 lines
5.3 KiB
JavaScript
/* =========================================================
|
|
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;
|
|
}
|
|
========================================================= */
|