Wave changes
This commit is contained in:
168
assets/scripts/sidebar.js
Normal file
168
assets/scripts/sidebar.js
Normal file
@@ -0,0 +1,168 @@
|
||||
/* =========================================================
|
||||
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;
|
||||
}
|
||||
========================================================= */
|
||||
Reference in New Issue
Block a user