--- html --- --- style --- self { display: block; /* umc-layout-ok */ box-sizing: border-box; width: 100%; /* umc-layout-ok */ color: inherit; font: inherit; min-width: 0; } .docs-nav-folder { --widget-padding: 0; --widget-border: none; --widget-background: transparent; } .docs-nav-folder-face { --widget-padding: 0.4rem 0.8rem; --widget-radius: 4px; --widget-border: none; --widget-background: transparent; cursor: pointer; } .docs-nav-folder-face:hover { --widget-background: rgba(255, 255, 255, 0.04); background: var(--widget-background); } .docs-nav-folder-label { font-size: 0.875rem; font-weight: 600; } .docs-nav-folder-body { padding-left: 0.5rem; /* umc-layout-ok */ } .docs-nav-link { --widget-padding: 0.4rem 0.8rem; --widget-radius: 4px; --widget-border: none; --widget-background: transparent; cursor: pointer; } .docs-nav-link:hover { --widget-background: rgba(255, 255, 255, 0.04); background: var(--widget-background); } .docs-nav-link[active] { --widget-background: color-mix(in srgb, var(--accent, #2196f3) 28%, transparent); background: var(--widget-background); color: #fff; } .docs-nav-link-label { font-size: 0.875rem; } .docs-nav-link-icon, .docs-nav-folder-icon { color: var(--muted, #a0a0a0); flex: 0 0 auto; /* umc-layout-ok */ } .docs-nav-link[active] .docs-nav-link-icon { color: inherit; } --- script --- /** * Document tree for the left rail. * Uses index.json icons (FA class strings) when present — same as classic Docs-Viewer. * Attrs: selected (slug) * Events: navigate { slug } */ function folderOpen(doc, selected, parents) { if (doc.defaultOpen) return true; if (selected && parents.has(doc.slug)) return true; return false; } function parentSlugSet(documents, selectedSlug) { const parents = new Set(); if (!selectedSlug) return parents; walk(documents, []); function walk(docs, trail) { for (const doc of docs || []) { if (doc.slug === selectedSlug) { for (const s of trail) parents.add(s); return true; } if (doc.type === "folder" && doc.items) { if (walk(doc.items, [...trail, doc.slug])) return true; } } return false; } return parents; } /** FA class list from index.json → slate-icon name (fas:plug, fab:github, …). */ function faToSlateIcon(raw, fallback = "") { const parts = String(raw || "") .split(/\s+/) .filter(Boolean); if (!parts.length) return fallback; const namePart = parts.find( (p) => p.startsWith("fa-") && !["fa-solid", "fa-brands", "fa-regular", "fa-sharp", "fas", "fab", "far", "fa"].includes(p) ); if (!namePart) return fallback; let name = namePart.replace(/^fa-/, ""); // Classic Docs-Viewer used file-alt; FA6 free solid aliases file-lines. if (name === "file-alt") name = "file-lines"; if (parts.some((p) => p.includes("brands") || p === "fab")) return `fab:${name}`; if (parts.some((p) => p.includes("regular") || p === "far")) return `far:${name}`; return `fas:${name}`; } function folderIcon(doc, open) { if (doc.icon) return faToSlateIcon(doc.icon, "fas:folder"); return open ? "fas:folder-open" : "fas:folder"; } function fileIcon(doc) { if (!doc.icon) return ""; return faToSlateIcon(doc.icon, ""); } function linkSpec(doc, selected) { const icon = fileIcon(doc); const row = []; if (icon) { row.push({ tag: "slate-icon", class: "docs-nav-link-icon", name: icon, size: "13", }); } row.push({ tag: "textblock", class: "docs-nav-link-label", text: doc.title || doc.slug, }); return { tag: "border", class: "docs-nav-link", active: doc.slug === selected ? "" : undefined, role: "link", tabindex: "0", "data-slug": doc.slug, children: [ { tag: "horizontalbox", gap: icon ? "8" : "0", valign: "center", children: row, }, ], }; } function folderSpec(doc, selected, parents) { const open = folderOpen(doc, selected, parents); const kids = []; if (doc.path && doc.showfolderpage !== "false") kids.push(linkSpec(doc, selected)); for (const item of doc.items || []) { kids.push(item.type === "folder" ? folderSpec(item, selected, parents) : linkSpec(item, selected)); } const custom = !!doc.icon; return { tag: "verticalbox", class: "docs-nav-folder", gap: "2", "data-folder": doc.slug, "data-folder-custom-icon": custom ? "" : undefined, children: [ { tag: "border", class: "docs-nav-folder-face", role: "button", tabindex: "0", "data-toggle-folder": doc.slug, children: [ { tag: "horizontalbox", gap: "8", valign: "center", children: [ { tag: "slate-icon", class: "docs-nav-folder-icon", name: folderIcon(doc, open), size: "13", "data-folder-icon": "", }, { tag: "textblock", class: "docs-nav-folder-label", text: doc.title || doc.slug, }, ], }, ], }, { tag: "verticalbox", class: "docs-nav-folder-body", gap: "2", hidden: open ? undefined : "", "data-folder-body": doc.slug, children: kids, }, ], }; } function buildTree(documents, selected) { const parents = parentSlugSet(documents, selected); return (documents || []).map((doc) => doc.type === "folder" ? folderSpec(doc, selected, parents) : linkSpec(doc, selected) ); } export default defineUmc({ tag: "slate-docs-nav", attrs: { selected: "", documents: "", }, Construct(el, api) { el.setDocuments = (docs) => { el.__documents = Array.isArray(docs) ? docs : []; el.__navDirty = true; sync(el, api); }; el.addEventListener("click", (e) => { const toggle = e.target.closest?.("[data-toggle-folder]"); if (toggle && el.contains(toggle)) { const slug = toggle.getAttribute("data-toggle-folder"); const folder = el.querySelector(`[data-folder="${CSS.escape(slug)}"]`); const body = el.querySelector(`[data-folder-body="${CSS.escape(slug)}"]`); const icon = toggle.querySelector("[data-folder-icon]"); if (body) { const open = body.hasAttribute("hidden"); if (open) body.removeAttribute("hidden"); else body.setAttribute("hidden", ""); // Classic Docs-Viewer: only default folder icons swap open/closed. if (icon && folder && !folder.hasAttribute("data-folder-custom-icon")) { icon.setAttribute("name", open ? "fas:folder-open" : "fas:folder"); } } return; } const link = e.target.closest?.("[data-slug]"); if (link && el.contains(link)) { api.emit("navigate", { slug: link.getAttribute("data-slug") }); } }); el.addEventListener("keydown", (e) => { if (e.key !== "Enter" && e.key !== " ") return; const link = e.target.closest?.("[data-slug], [data-toggle-folder]"); if (!link || !el.contains(link)) return; e.preventDefault(); link.click(); }); }, SynchronizeProperties(el, api) { sync(el, api); }, }); function sync(el, api) { let docs = el.__documents; if (!docs) { const raw = el.getAttribute("documents") || ""; if (raw) { try { docs = JSON.parse(raw); } catch { docs = []; } } else { docs = []; } } const selected = el.getAttribute("selected") || ""; const key = `${selected}::${docs.length}::${el.__navDirty || false}`; if (el.__navKey === key && !el.__navDirty) return; el.__navKey = key; el.__navDirty = false; el.set(...buildTree(docs, selected)); } --- preview ---