import { clientStore, editor, markdown, system, } from "$sb/silverbullet-syscall/mod.ts"; import { renderToText, traverseTree, traverseTreeAsync } 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 } async function markdownToHtml(text: string): Promise { return system.invokeFunction("markdown.markdownToHtml", text); } export async function renderTOC(reload = false) { if (await clientStore.get(hideTOCKey)) { return editor.hidePanel("top"); } const page = await editor.getCurrentPage(); const text = await editor.getText(); const tree = await markdown.parseMarkdown(text); const headers: Header[] = []; await traverseTreeAsync(tree, async (n) => { if (n.type?.startsWith("ATXHeading")) { headers.push({ name: await markdownToHtml( 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)) { // 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("top"); return; } const css = await asset.readAsset("asset/style.css"); const js = await asset.readAsset("asset/toc.js"); await editor.showPanel( "top", 1, `
Table of Contents
`, js, ); }