import { clientStore, editor, markdown } from "$sb/silverbullet-syscall/mod.ts"; import { renderToText, traverseTree } from "$sb/lib/tree.ts"; import { asset } from "$sb/syscalls.ts"; const hideTOCKey = "hideTOC"; const headerThreshold = 3; type Header = { name: string; pos: number; level: number; }; let cachedTOC: string | undefined; export async function toggleTOC() { cachedTOC = undefined; let hideTOC = await clientStore.get(hideTOCKey); hideTOC = !hideTOC; await clientStore.set(hideTOCKey, hideTOC); await renderTOC(); // This will hide it if needed } export async function renderTOC(reload = false) { if (await clientStore.get(hideTOCKey)) { return editor.hidePanel("preface"); } const page = await editor.getCurrentPage(); const text = await editor.getText(); const tree = await markdown.parseMarkdown(text); const headers: Header[] = []; traverseTree(tree, (n) => { if (n.type?.startsWith("ATXHeading")) { headers.push({ name: n.children!.slice(1).map(renderToText).join("").trim(), pos: n.from!, level: +n.type[n.type.length - 1], }); return true; } return false; }); // console.log("All headers", headers); if (!reload && cachedTOC === JSON.stringify(headers)) { console.log("TOC is the same, not updating"); return; } cachedTOC = JSON.stringify(headers); if (headers.length < headerThreshold) { console.log("Not enough headers, not showing TOC", headers.length); await editor.hidePanel("preface"); return; } let tocMarkdown = ""; for (const header of headers) { tocMarkdown += `${ " ".repeat(3 * (header.level - 1)) }* [${header.name}](/${page}@${header.pos})\n`; } const css = await asset.readAsset("asset/style.css"); const js = await asset.readAsset("asset/toc.js"); await editor.showPanel( "preface", 1, `
Table of Contents
`, js, ); }