Skip to content

Investigate table of content use case #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
schickling opened this issue Mar 8, 2022 · 3 comments
Open

Investigate table of content use case #137

schickling opened this issue Mar 8, 2022 · 3 comments

Comments

@schickling
Copy link
Collaborator

schickling commented Mar 8, 2022

My first approach would be to try to use computed fields for this.

Related

@schickling
Copy link
Collaborator Author

schickling commented Mar 30, 2022

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.

@schickling
Copy link
Collaborator Author

A more elegant foundation for this feature could be #216.

@Clarity-89
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants