Skip to content

Commit

Permalink
Headless: Change remarkToc to remarkHeading
Browse files Browse the repository at this point in the history
  • Loading branch information
fuma-nama committed Jan 1, 2024
1 parent 338ea98 commit 49201be
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 45 deletions.
12 changes: 12 additions & 0 deletions .changeset/slimy-swans-decide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'next-docs-zeta': major
---

**Change `remarkToc` to `remarkHeading`**

The previous `remarkToc` plugin only extracts table of contents from documents, now it also adds the `id` property to all heading elements.

```diff
- import { remarkToc } from "next-docs-zeta/mdx-plugins"
+ import { remarkHeading } from "next-docs-zeta/mdx-plugins"
```
2 changes: 1 addition & 1 deletion packages/headless/src/mdx-plugins/hast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function flattenNode(node: RootContent): string {
return all(node);
}

if (node.type === 'text') {
if ('value' in node) {
return node.value;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/headless/src/mdx-plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export {
export * from './rehype-next-docs';
export * from './remark-dynamic-content';
export * from './remark-structure';
export * from './remark-toc';
export * from './remark-heading';
export * from './remark-install';
31 changes: 8 additions & 23 deletions packages/headless/src/mdx-plugins/rehype-next-docs.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import Slugger from 'github-slugger';
import type { Element, Root } from 'hast';
import rehypePrettycode, {
type Options as RehypePrettyCodeOptions,
} from 'rehype-pretty-code';
import type { Transformer } from 'unified';
import { flattenNode, visit } from './hast-utils';

const slugger = new Slugger();
const headings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
import { visit } from './hast-utils';

interface MetaValue {
name: string;
Expand Down Expand Up @@ -81,32 +77,21 @@ function parseMeta(meta: string): Record<string, string> {
}

/**
* Handle codeblocks and heading slugs
* Handle codeblocks
*/
export function rehypeNextDocs({
codeOptions,
}: RehypeNextDocsOptions = {}): Transformer<Root, Root> {
// TODO: Migrate to rehype-shikiji
return async (tree, vfile) => {
slugger.reset();

visit(tree, ['pre', ...headings], (node) => {
if (headings.includes(node.tagName)) {
if ('id' in node.properties) return;
node.properties.id = slugger.slug(flattenNode(node));

return;
}

if (node.tagName === 'pre') {
const codeElement = node.children[0] as Element;
visit(tree, ['pre'], (node) => {
const codeElement = node.children[0] as Element;

const meta = getMetaFromCode(codeElement);
const meta = getMetaFromCode(codeElement);

Object.assign(node, {
[metaKey]: parseMeta(meta),
});
}
Object.assign(node, {
[metaKey]: parseMeta(meta),
});
});

const plugin = rehypePrettycode({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,33 @@ import { flattenNode } from './remark-utils';

const slugger = new Slugger();

export interface HProperties {
id?: string;
}

/**
* Add heading ids and extract TOC
*
* Attach an array of `TOCItemType` to `vfile.data.toc`
*/
export function remarkToc(): Transformer<Root, Root> {
export function remarkHeading(): Transformer<Root, Root> {
return (node, file) => {
const toc: TOCItemType[] = [];
slugger.reset();

visit(node, ['heading'], (heading: Heading) => {
heading.data ||= {};
heading.data.hProperties ||= {};

const text = flattenNode(heading);
const properties = heading.data.hProperties as HProperties;
const id = slugger.slug(properties.id || text);

properties.id = id;

toc.push({
title: text,
url: `#${slugger.slug(text)}`,
url: `#${id}`,
depth: heading.depth,
});

Expand Down
11 changes: 8 additions & 3 deletions packages/headless/src/mdx-plugins/remark-structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import remarkMdx from 'remark-mdx';
import type { PluggableList, Transformer } from 'unified';
import { visit } from './unist-visit';
import { flattenNode } from './remark-utils';
import type { HProperties } from './remark-heading';

interface Heading {
id: string;
Expand Down Expand Up @@ -47,15 +48,19 @@ export function remarkStructure({

visit(node, types, (element: MdastContent) => {
const content = flattenNode(element).trim();

if (element.type === 'heading') {
const slug = slugger.slug(content);
element.data ||= {};
element.data.hProperties ||= {};
const propeties = element.data.hProperties as HProperties;
const id = propeties.id ?? slugger.slug(content);

data.headings.push({
id: slug,
id,
content,
});

lastHeading = slug;
lastHeading = id;
return 'skip';
}

Expand Down
14 changes: 5 additions & 9 deletions packages/headless/src/mdx-plugins/remark-utils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import type { Literal, RootContent } from 'mdast';
import { visit } from './unist-visit';

const textTypes = ['text', 'inlineCode', 'code'];
import type { RootContent } from 'mdast';

export function flattenNode(node: RootContent): string {
const p: string[] = [];
if ('children' in node)
return node.children.map((child) => flattenNode(child)).join('');

visit(node, textTypes, (child: Literal) => {
p.push(child.value);
});
if ('value' in node) return node.value;

return p.join('');
return '';
}
4 changes: 2 additions & 2 deletions packages/headless/src/server/get-toc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { remark } from 'remark';
import { remarkToc } from '@/mdx-plugins/remark-toc';
import { remarkHeading } from '@/mdx-plugins/remark-heading';

export interface TOCItemType {
title: string;
Expand All @@ -17,7 +17,7 @@ export type TableOfContents = TOCItemType[];
export async function getTableOfContents(
content: string,
): Promise<TableOfContents> {
const result = await remark().use(remarkToc).process(content);
const result = await remark().use(remarkHeading).process(content);

if ('toc' in result.data) return result.data.toc as TableOfContents;

Expand Down
2 changes: 1 addition & 1 deletion packages/headless/src/toc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import type { TableOfContents, TOCItemType } from '@/server/get-toc';
import { mergeRefs } from '@/merge-refs';
import { mergeRefs } from '@/utils/merge-refs';
import { useAnchorObserver } from './utils/use-anchor-observer';

const ActiveAnchorContext = createContext<{
Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions packages/mdx/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
rehypeNextDocs,
remarkGfm,
remarkStructure,
remarkToc,
remarkHeading,
type RehypeNextDocsOptions,
} from 'next-docs-zeta/mdx-plugins';
import type { PluggableList } from 'unified';
Expand Down Expand Up @@ -87,9 +87,9 @@ const createNextDocs =
const remarkPlugins = pluginOption(
(v) => [
remarkGfm,
remarkStructure,
remarkToc,
remarkHeading,
...v,
remarkStructure,
[remarkMdxExport, { values: valueToExport }],
],
mdxOptions.remarkPlugins,
Expand Down

0 comments on commit 49201be

Please sign in to comment.