Open schickling opened 2 years ago
As an initial "user-land" solution I've come up with the following:
import { defineDocumentType } from 'contentlayer/source-files'
import type * as unified from 'unified'
import { toMarkdown } from 'mdast-util-to-markdown'
import { mdxToMarkdown } from 'mdast-util-mdx'
import { bundleMDX } from 'mdx-bundler'
export type DocHeading = { level: 1 | 2 | 3; title: string }
export const Doc = defineDocumentType(() => ({
name: 'Doc',
filePathPattern: `docs/**/*.mdx`,
contentType: 'mdx',
fields: {
title: {
type: 'string',
description: 'The title of the page',
required: true,
},
},
computedFields: {
headings: {
type: 'json',
resolve: async (doc) => {
const headings: DocHeading[] = []
await bundleMDX({
source: doc.body.raw,
xdmOptions: (opts) => {
opts.remarkPlugins = [...(opts.remarkPlugins ?? []), tocPlugin(headings)]
return opts
},
})
return [{ level: 1, title: doc.title }, ...headings]
},
},
},
}))
const tocPlugin =
(headings: DocHeading[]): unified.Plugin =>
() => {
return (node: any) => {
node.children
.filter((_: any) => _.type === 'heading')
.forEach((heading: any) => {
const title = toMarkdown({ type: 'paragraph', children: heading.children }, { extensions: [mdxToMarkdown()] })
.trim()
// removes MDX in headlines
.replace(/<.*$/g, '')
.trim()
return headings.push({ level: heading.depth, title })
})
}
}
This is definitely not an ideal solution and should be improved further. One approach could be to make this an "out of the box" feature of Contentlayer.
A more elegant foundation for this feature could be #216.
I think it'd be great if the TOC would be available separately, and not as a part of the body. This is a current limitation of Remark-toc, which doesn't work when you want the TOC to be rendered outside of content. I've made a custom solution for this, but would be great if it was built-in into Contentlayer.
My first approach would be to try to use computed fields for this.
Related