木を分ける

unified
Interface for parsing, inspecting, transforming, and serializing content through syntax trees
unified favicon https://unifiedjs.com/explore/package/unified/
unified

から process が、parse, run, stringify の3ステップであることを把握。 parse, run まで実行して hast を toc と body に分割することを思いついた。 できた👇

async function renderMarkdown(md: string)
: Promise<{ html: string, toc: string }> {
const processor = unified()
// mdast
.use(remarkParse)
.use(remarkGfm)
.use(remarkLinkCard)
.use(remarkRehype, {
handlers: {
extlink: extLinkHandler
}
})
// rehype
.use(rehypeSlug)
.use(rehypeToc)
.use(rehypePrettyCode)
.use(rehypeStringify)
;
const tree = await processor.run(processor.parse(md));
// split tree
const tocTree = {
type: 'root',
children: [tree.children.shift()],
};
const html = processor.stringify(tree);
const toc = processor.stringify(tocTree);
return { html, toc };
}

普通に 2箇所に分けて描画

<div class="body">
{@html data.html}
</div>
<div class="toc">
{@html data.toc}
</div>

これで css で小細工を弄する必要が無くなりました。 左にグローバルなサイドバー、右にローカルなサイドバーを配置する計画なので toc を右サイドバーに入れやすくしました。