Headless: Change remarkToc to remarkHeading

This commit is contained in:
SonMooSans
2024-01-01 21:43:46 +08:00
parent 338ea98d9c
commit 49201becd8
11 changed files with 56 additions and 45 deletions
+12
View File
@@ -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"
```
@@ -25,7 +25,7 @@ export function flattenNode(node: RootContent): string {
return all(node);
}
if (node.type === 'text') {
if ('value' in node) {
return node.value;
}
+1 -1
View File
@@ -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';
@@ -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;
@@ -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'], (node) => {
const codeElement = node.children[0] as Element;
visit(tree, ['pre', ...headings], (node) => {
if (headings.includes(node.tagName)) {
if ('id' in node.properties) return;
node.properties.id = slugger.slug(flattenNode(node));
const meta = getMetaFromCode(codeElement);
return;
}
if (node.tagName === 'pre') {
const codeElement = node.children[0] as Element;
const meta = getMetaFromCode(codeElement);
Object.assign(node, {
[metaKey]: parseMeta(meta),
});
}
Object.assign(node, {
[metaKey]: parseMeta(meta),
});
});
const plugin = rehypePrettycode({
@@ -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,
});
@@ -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;
@@ -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';
}
@@ -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 '';
}
+2 -2
View File
@@ -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;
@@ -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;
+1 -1
View File
@@ -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<{
+3 -3
View File
@@ -4,7 +4,7 @@ import {
rehypeNextDocs,
remarkGfm,
remarkStructure,
remarkToc,
remarkHeading,
type RehypeNextDocsOptions,
} from 'next-docs-zeta/mdx-plugins';
import type { PluggableList } from 'unified';
@@ -87,9 +87,9 @@ const createNextDocs =
const remarkPlugins = pluginOption(
(v) => [
remarkGfm,
remarkStructure,
remarkToc,
remarkHeading,
...v,
remarkStructure,
[remarkMdxExport, { values: valueToExport }],
],
mdxOptions.remarkPlugins,