mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 23:49:51 +00:00
Headless: Change remarkToc to remarkHeading
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
+15
-2
@@ -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 '';
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user