rehype-mdx-toc 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/src/index.d.ts +76 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +58 -0
- package/dist/src/index.js.map +1 -0
- package/dist/test/index.d.ts +2 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +250 -0
- package/dist/test/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +65 -0
- package/src/index.ts +137 -0
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 boning-w
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# rehype-mdx-toc
|
2
|
+
|
3
|
+
[](https://github.com/boning-w/rehype-mdx-toc/actions/workflows/ci.yml)
|
4
|
+
[](https://codecov.io/github/boning-w/rehype-mdx-toc)
|
5
|
+
[](https://github.com/boning-w/rehype-mdx-toc/blob/main/LICENSE)
|
6
|
+
|
7
|
+
A rehype plugin to create the table of contents and convert it into MDX exports.
|
8
|
+
|
9
|
+
## Table of Contents
|
10
|
+
|
11
|
+
- [What is this?](#what-is-this)
|
12
|
+
- [When should I use this?](#when-should-i-use-this)
|
13
|
+
- [Install](#install)
|
14
|
+
- [Usage](#usage)
|
15
|
+
- [✅ For `@mdx-js/mdx`](#-for-mdx-jsmdx)
|
16
|
+
- [✅ For `mdx-bundler`](#-for-mdx-bundler)
|
17
|
+
- [🚫 For `next-mdx-remote`](#-for-next-mdx-remote)
|
18
|
+
- [API](#api)
|
19
|
+
- [Options](#options)
|
20
|
+
- [Types](#types)
|
21
|
+
- [License](#license)
|
22
|
+
|
23
|
+
## What is this?
|
24
|
+
|
25
|
+
This package is a [unified](https://unifiedjs.com/) ([rehype](https://rehypejs.github.io/rehype/)) plugin that generates a table of contents (TOC) from headings in your MDX files and converts it into MDX exports.
|
26
|
+
|
27
|
+
## When should I use this?
|
28
|
+
|
29
|
+
This plugin is useful when you want a table of contents data that is exported from MDX modules, which can then be used everwhere in your application.
|
30
|
+
|
31
|
+
## Install
|
32
|
+
|
33
|
+
You can install this package using npm:
|
34
|
+
|
35
|
+
```shell
|
36
|
+
npm install rehype-mdx-toc
|
37
|
+
```
|
38
|
+
|
39
|
+
## Usage
|
40
|
+
|
41
|
+
Say we have mdx contents like this:
|
42
|
+
|
43
|
+
```mdx
|
44
|
+
# Heading 1
|
45
|
+
|
46
|
+
## Heading 2
|
47
|
+
|
48
|
+
### Heading 3
|
49
|
+
```
|
50
|
+
|
51
|
+
...and our module `example.js` contains:
|
52
|
+
|
53
|
+
```js
|
54
|
+
import { compile, run } from "@mdx-js/mdx";
|
55
|
+
import rehypeMdxToc from "rehype-mdx-toc";
|
56
|
+
import * as runtime from "react/jsx-runtime";
|
57
|
+
|
58
|
+
const mdxContent = `
|
59
|
+
# Heading 1
|
60
|
+
## Heading 2
|
61
|
+
### Heading 3
|
62
|
+
`;
|
63
|
+
|
64
|
+
const compiled = await compile(mdxContent, {
|
65
|
+
outputFormat: "function-body",
|
66
|
+
rehypePlugins: [rehypeMdxToc],
|
67
|
+
});
|
68
|
+
const result = await run(compiled, { ...runtime });
|
69
|
+
const toc = result.toc;
|
70
|
+
console.log(toc);
|
71
|
+
```
|
72
|
+
|
73
|
+
...then running `node example.js` will output:
|
74
|
+
|
75
|
+
```json
|
76
|
+
[
|
77
|
+
{
|
78
|
+
"depth": 1,
|
79
|
+
"value": "Heading 1",
|
80
|
+
"numbering": [1, 0, 0, 0, 0, 0],
|
81
|
+
"id": undefined,
|
82
|
+
"href": undefined
|
83
|
+
},
|
84
|
+
{
|
85
|
+
"depth": 2,
|
86
|
+
"value": "Heading 2",
|
87
|
+
"numbering": [1, 1, 0, 0, 0, 0],
|
88
|
+
"id": undefined,
|
89
|
+
"href": undefined
|
90
|
+
},
|
91
|
+
{
|
92
|
+
"depth": 3,
|
93
|
+
"value": "Heading 3",
|
94
|
+
"numbering": [1, 1, 1, 0, 0, 0],
|
95
|
+
"id": undefined,
|
96
|
+
"href": undefined
|
97
|
+
}
|
98
|
+
]
|
99
|
+
```
|
100
|
+
|
101
|
+
### Use with `rehype-slug`
|
102
|
+
|
103
|
+
To generate `id` and `href` properties for each TOC item, you can use the `rehype-slug` plugin before `rehype-mdx-toc`. This will automatically add `id` attributes to headings based on their text content.
|
104
|
+
|
105
|
+
```js
|
106
|
+
import { compile, run } from "@mdx-js/mdx";
|
107
|
+
import rehypeSlug from "rehype-slug";
|
108
|
+
import rehypeMdxToc from "rehype-mdx-toc";
|
109
|
+
import * as runtime from "react/jsx-runtime";
|
110
|
+
|
111
|
+
const mdxContent = `
|
112
|
+
# Heading 1
|
113
|
+
## Heading 2
|
114
|
+
### Heading 3
|
115
|
+
`;
|
116
|
+
|
117
|
+
const compiled = await compile(mdxContent, {
|
118
|
+
outputFormat: "function-body",
|
119
|
+
rehypePlugins: [rehypeSlug, rehypeMdxToc],
|
120
|
+
});
|
121
|
+
const result = await run(compiled, { ...runtime });
|
122
|
+
const toc = result.toc;
|
123
|
+
console.log(toc);
|
124
|
+
```
|
125
|
+
|
126
|
+
This will output:
|
127
|
+
|
128
|
+
```json
|
129
|
+
[
|
130
|
+
{
|
131
|
+
"depth": 1,
|
132
|
+
"value": "Heading 1",
|
133
|
+
"numbering": [1, 0, 0, 0, 0, 0],
|
134
|
+
"id": "heading-1",
|
135
|
+
"href": "#heading-1"
|
136
|
+
},
|
137
|
+
{
|
138
|
+
"depth": 2,
|
139
|
+
"value": "Heading 2",
|
140
|
+
"numbering": [1, 1, 0, 0, 0, 0],
|
141
|
+
"id": "heading-2",
|
142
|
+
"href": "#heading-2"
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"depth": 3,
|
146
|
+
"value": "Heading 3",
|
147
|
+
"numbering": [1, 1, 1, 0, 0, 0],
|
148
|
+
"id": "heading-3",
|
149
|
+
"href": "#heading-3"
|
150
|
+
}
|
151
|
+
]
|
152
|
+
```
|
153
|
+
|
154
|
+
### ✅ For `@mdx-js/mdx`
|
155
|
+
|
156
|
+
See [Usage](#usage) and [Use with rehype-slug](#use-with-rehype-slug) sections above.
|
157
|
+
|
158
|
+
### ✅ For `mdx-bundler`
|
159
|
+
|
160
|
+
For `mdx-bundler`, you can add this plugin to `bundleMDX()`'s mdxOptions:
|
161
|
+
|
162
|
+
```js
|
163
|
+
bundleMDX({
|
164
|
+
source: mdxSource,
|
165
|
+
mdxOptions(options, frontmatter) {
|
166
|
+
options.rehypePlugins = [...(options.rehypePlugins ?? []), rehypeMdxToc];
|
167
|
+
return options;
|
168
|
+
},
|
169
|
+
});
|
170
|
+
```
|
171
|
+
|
172
|
+
> See [mdx-bundler#Accessing named exports](https://github.com/kentcdodds/mdx-bundler?tab=readme-ov-file#accessing-named-exports)
|
173
|
+
>
|
174
|
+
> To access the `toc` export, You can use `getMDXExport` instead of `getMDXComponent` to treat the mdx file as a module instead of just a component.
|
175
|
+
|
176
|
+
```js
|
177
|
+
import { getMDXExport } from "mdx-bundler/client";
|
178
|
+
|
179
|
+
function MDXPage({ code }: { code: string }) {
|
180
|
+
const mdxExport = getMDXExport(code);
|
181
|
+
console.log(mdxExport.toc); // 👈 get toc export here
|
182
|
+
|
183
|
+
const Component = React.useMemo(() => mdxExport.default, [code]);
|
184
|
+
|
185
|
+
return <Component />;
|
186
|
+
}
|
187
|
+
```
|
188
|
+
|
189
|
+
### 🚫 For `next-mdx-remote`
|
190
|
+
|
191
|
+
This plugin is not compatible with `next-mdx-remote` because `next-mdx-remote` does not support `import` and `export` statements in MDX files.
|
192
|
+
|
193
|
+
See [import/export caveats of next-mdx-remote](https://github.com/hashicorp/next-mdx-remote?tab=readme-ov-file#import--export)
|
194
|
+
|
195
|
+
> `import` and `export` statements cannot be used inside an MDX file.
|
196
|
+
>
|
197
|
+
> ...As for exports, the MDX content is treated as data, not a module, so there is no way for us to access any value which may be exported from the MDX passed to `next-mdx-remote`.
|
198
|
+
|
199
|
+
## API
|
200
|
+
|
201
|
+
The default export is the rehype plugin function.
|
202
|
+
|
203
|
+
### Options
|
204
|
+
|
205
|
+
- `default`: The default value to export if no headings. (Default: undefined).
|
206
|
+
- `name`: The name of the export. (Default: "toc").
|
207
|
+
- `skipDepth`: An array of heading depths to skip. If specified, headings of these depths will not be included in the TOC. (Default: `[]`).
|
208
|
+
|
209
|
+
## Types
|
210
|
+
|
211
|
+
This package is fully typed with [TypeScript](https://www.typescriptlang.org/). The plugin exports the types `HeadingDepth`, `RehypeMdxTocOptions`, `TocItem`.
|
212
|
+
|
213
|
+
Mostly, you only need the `TocItem` type in your project:
|
214
|
+
|
215
|
+
```ts
|
216
|
+
export type TocItem = {
|
217
|
+
depth: HeadingDepth; // The level of the heading (1 for `<h1>`, 2 for `<h2>`, etc.).
|
218
|
+
value: string; // The text content of the heading.
|
219
|
+
numbering: number[]; // An array representing the hierarchical numbering of the heading.
|
220
|
+
id?: string; // The ID of the heading, if available.
|
221
|
+
href?: string; // The link to the heading.
|
222
|
+
data?: Record<string, unknown>;
|
223
|
+
};
|
224
|
+
```
|
225
|
+
|
226
|
+
## License
|
227
|
+
|
228
|
+
[MIT](https://github.com/boning-w/rehype-mdx-toc/blob/main/LICENSE)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import type { Root } from "hast";
|
2
|
+
import type { VFile } from "vfile";
|
3
|
+
/**
|
4
|
+
* Type representing the depth of headings in a table of contents.
|
5
|
+
* This is used to specify which heading levels (h1, h2, etc.) should be included
|
6
|
+
* in the table of contents.
|
7
|
+
*
|
8
|
+
* The values correspond to HTML heading elements:
|
9
|
+
* - 1 for `<h1>`
|
10
|
+
* - 2 for `<h2>`
|
11
|
+
* - 3 for `<h3>`
|
12
|
+
* - 4 for `<h4>`
|
13
|
+
* - 5 for `<h5>`
|
14
|
+
* - 6 for `<h6>`
|
15
|
+
*/
|
16
|
+
export type HeadingDepth = 1 | 2 | 3 | 4 | 5 | 6;
|
17
|
+
/**
|
18
|
+
* Options for the rehype-mdx-toc plugin.
|
19
|
+
*
|
20
|
+
* This type defines the configuration options that can be passed to the plugin.
|
21
|
+
* - `default`: Optional default value for the table of contents when no headings are found.
|
22
|
+
* - `name`: The name of the export variable for the table of contents.
|
23
|
+
*/
|
24
|
+
export type RehypeMdxTocOptions = {
|
25
|
+
/**
|
26
|
+
* Optional default value for the table of contents.
|
27
|
+
* If provided, this will be used when no headings are found.
|
28
|
+
*
|
29
|
+
* @default undefined
|
30
|
+
*/
|
31
|
+
default?: unknown;
|
32
|
+
/**
|
33
|
+
* The name of the export variable for the table of contents.
|
34
|
+
*
|
35
|
+
* @default "toc"
|
36
|
+
*/
|
37
|
+
name?: string;
|
38
|
+
/**
|
39
|
+
* An array of heading depths to skip.
|
40
|
+
* If specified, headings of these depths will not be included in the TOC.
|
41
|
+
*
|
42
|
+
* @default []
|
43
|
+
*/
|
44
|
+
skipDepth?: HeadingDepth[];
|
45
|
+
};
|
46
|
+
/**
|
47
|
+
* Represents a single item in the table of contents (TOC).
|
48
|
+
*
|
49
|
+
* Each item corresponds to a heading in the MDX file and includes:
|
50
|
+
* - `depth`: The level of the heading (1 for `<h1>`, 2 for `<h2>`, etc.).
|
51
|
+
* - `value`: The text content of the heading.
|
52
|
+
* - `numbering`: An array representing the hierarchical numbering of the heading.
|
53
|
+
* - `id`: The ID of the heading, if available.
|
54
|
+
* - `href`: The link to the heading, typically a fragment identifier.
|
55
|
+
* - `data`: Additional data associated with the heading.
|
56
|
+
*/
|
57
|
+
export type TocItem = {
|
58
|
+
depth: HeadingDepth;
|
59
|
+
value: string;
|
60
|
+
numbering: number[];
|
61
|
+
id?: string;
|
62
|
+
href?: string;
|
63
|
+
data?: Record<string, unknown>;
|
64
|
+
};
|
65
|
+
/**
|
66
|
+
* Rehype plugin to generate a table of contents (TOC) for MDX files.
|
67
|
+
*
|
68
|
+
* This plugin processes the HAST tree to find all headings and constructs a TOC
|
69
|
+
* with hierarchical numbering. The TOC is then exported as a variable that can
|
70
|
+
* be used in the MDX module.
|
71
|
+
*
|
72
|
+
* @param options - Optional configuration for the plugin.
|
73
|
+
* @returns A function that transforms the HAST tree.
|
74
|
+
*/
|
75
|
+
export default function rehypeMdxToc({ name, ...options }?: RehypeMdxTocOptions): (tree: Root, file: VFile) => void;
|
76
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAOnC;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,EAAE,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,IAAY,EACZ,GAAG,OAAO,EACX,GAAE,mBAAwB,IAEjB,MAAM,IAAI,EAAE,MAAM,KAAK,UA8ChC"}
|
@@ -0,0 +1,58 @@
|
|
1
|
+
import { define } from "unist-util-mdx-define";
|
2
|
+
import { headingRank } from "hast-util-heading-rank";
|
3
|
+
import { toString } from "hast-util-to-string";
|
4
|
+
import { valueToEstree } from "estree-util-value-to-estree";
|
5
|
+
import { CONTINUE, visit } from "unist-util-visit";
|
6
|
+
/**
|
7
|
+
* Rehype plugin to generate a table of contents (TOC) for MDX files.
|
8
|
+
*
|
9
|
+
* This plugin processes the HAST tree to find all headings and constructs a TOC
|
10
|
+
* with hierarchical numbering. The TOC is then exported as a variable that can
|
11
|
+
* be used in the MDX module.
|
12
|
+
*
|
13
|
+
* @param options - Optional configuration for the plugin.
|
14
|
+
* @returns A function that transforms the HAST tree.
|
15
|
+
*/
|
16
|
+
export default function rehypeMdxToc({ name = "toc", ...options } = {}) {
|
17
|
+
// Return the actual transformer function for rehype.
|
18
|
+
return (tree, file) => {
|
19
|
+
// This will hold the final table of contents data.
|
20
|
+
const toc = [];
|
21
|
+
// Tracks the current numbering for headings.
|
22
|
+
// Each index corresponds to a heading level: [h1, h2, h3, h4, h5, h6].
|
23
|
+
const currentNumbering = [0, 0, 0, 0, 0, 0];
|
24
|
+
// Visit every HTML element in the tree.
|
25
|
+
visit(tree, "element", function (node) {
|
26
|
+
// Check if this element is a heading (<h1>–<h6>).
|
27
|
+
const rank = headingRank(node);
|
28
|
+
// If the rank is undefined, skip this node.
|
29
|
+
if (rank === undefined)
|
30
|
+
return CONTINUE;
|
31
|
+
// If the depth is in the skipDepth array, skip this node.
|
32
|
+
const depth = rank;
|
33
|
+
if (options.skipDepth?.includes(depth))
|
34
|
+
return CONTINUE;
|
35
|
+
// Increment the counter for this heading level.
|
36
|
+
currentNumbering[depth - 1] += 1;
|
37
|
+
// Reset all deeper levels to zero.
|
38
|
+
// This ensures the numbering doesn't accidentally carry over.
|
39
|
+
for (let i = depth; i < currentNumbering.length; i++) {
|
40
|
+
currentNumbering[i] = 0;
|
41
|
+
}
|
42
|
+
// Add this heading to the TOC array.
|
43
|
+
toc.push({
|
44
|
+
depth,
|
45
|
+
value: toString(node),
|
46
|
+
numbering: [...currentNumbering],
|
47
|
+
id: node.properties?.id,
|
48
|
+
href: node.properties?.id ? `#${node.properties.id}` : undefined,
|
49
|
+
});
|
50
|
+
});
|
51
|
+
// Attach the TOC as an exported variable in the MDX file.
|
52
|
+
const data = toc.length ? toc : options.default;
|
53
|
+
define(tree, file, {
|
54
|
+
[name]: valueToEstree(data),
|
55
|
+
});
|
56
|
+
};
|
57
|
+
}
|
58
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAqEnD;;;;;;;;;GASG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,IAAI,GAAG,KAAK,EACZ,GAAG,OAAO,KACa,EAAE;IACzB,qDAAqD;IACrD,OAAO,CAAC,IAAU,EAAE,IAAW,EAAE,EAAE;QACjC,mDAAmD;QACnD,MAAM,GAAG,GAAc,EAAE,CAAC;QAE1B,6CAA6C;QAC7C,uEAAuE;QACvE,MAAM,gBAAgB,GAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAEtD,wCAAwC;QACxC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,IAAI;YACnC,kDAAkD;YAClD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YAE/B,4CAA4C;YAC5C,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,QAAQ,CAAC;YAExC,0DAA0D;YAC1D,MAAM,KAAK,GAAG,IAAoB,CAAC;YACnC,IAAI,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;gBAAE,OAAO,QAAQ,CAAC;YAExD,gDAAgD;YAChD,gBAAgB,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YAEjC,mCAAmC;YACnC,8DAA8D;YAC9D,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,gBAAgB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrD,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;YAED,qCAAqC;YACrC,GAAG,CAAC,IAAI,CAAC;gBACP,KAAK;gBACL,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC;gBACrB,SAAS,EAAE,CAAC,GAAG,gBAAgB,CAAC;gBAChC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,EAAwB;gBAC7C,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS;aACjE,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,0DAA0D;QAC1D,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QAEhD,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE;YACjB,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC;SAC5B,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":""}
|
@@ -0,0 +1,250 @@
|
|
1
|
+
import { describe, it } from "node:test";
|
2
|
+
import assert from "node:assert/strict";
|
3
|
+
import { compile, run } from "@mdx-js/mdx";
|
4
|
+
import rehypeMdxToc, {} from "rehype-mdx-toc";
|
5
|
+
import rehypeSlug from "rehype-slug";
|
6
|
+
import * as runtime from "react/jsx-runtime";
|
7
|
+
const source = `
|
8
|
+
# The Main Heading
|
9
|
+
|
10
|
+
## Section _1_
|
11
|
+
|
12
|
+
### Subheading
|
13
|
+
|
14
|
+
### Mubheading
|
15
|
+
|
16
|
+
## Section _2_
|
17
|
+
|
18
|
+
### \`Subheading\`
|
19
|
+
|
20
|
+
### [Mubheading](#)
|
21
|
+
|
22
|
+
> #### QuoteHeading
|
23
|
+
> Some Content
|
24
|
+
> #### **QuoteHeading**
|
25
|
+
> Some Content
|
26
|
+
|
27
|
+
+ ##### **ListItem** _Heading_
|
28
|
+
+ ##### _ListItem_ **Heading**
|
29
|
+
`;
|
30
|
+
async function getTocFromMdxModule(mdxContent) {
|
31
|
+
const compiled = await compile(mdxContent, {
|
32
|
+
outputFormat: "function-body",
|
33
|
+
rehypePlugins: [rehypeMdxToc],
|
34
|
+
});
|
35
|
+
const result = await run(compiled, { ...runtime });
|
36
|
+
const toc = result.toc;
|
37
|
+
return toc;
|
38
|
+
}
|
39
|
+
async function getTocFromMdxModuleWithRehypeSlug(mdxContent) {
|
40
|
+
const compiled = await compile(mdxContent, {
|
41
|
+
outputFormat: "function-body",
|
42
|
+
rehypePlugins: [rehypeSlug, rehypeMdxToc],
|
43
|
+
});
|
44
|
+
const result = await run(compiled, { ...runtime });
|
45
|
+
const toc = result.toc;
|
46
|
+
return toc;
|
47
|
+
}
|
48
|
+
describe("rehype-mdx-toc", () => {
|
49
|
+
it("should export toc to MDX module", async () => {
|
50
|
+
const mdx = "# Heading 1";
|
51
|
+
const toc = await getTocFromMdxModule(mdx);
|
52
|
+
assert.ok(Array.isArray(toc), "Should export toc");
|
53
|
+
});
|
54
|
+
it("should generate undefined TOC for no headings", async () => {
|
55
|
+
const mdx = "This is a test without headings.";
|
56
|
+
const toc = await getTocFromMdxModule(mdx);
|
57
|
+
assert.ok(toc === undefined, "Should generate undefined TOC for no headings");
|
58
|
+
});
|
59
|
+
it("should generate correct TOC structure without heading IDs", async () => {
|
60
|
+
const toc = await getTocFromMdxModule("# Heading 1\n\n## Heading 2\n\n### Heading 3");
|
61
|
+
assert.deepEqual(toc, [
|
62
|
+
{
|
63
|
+
depth: 1,
|
64
|
+
value: "Heading 1",
|
65
|
+
numbering: [1, 0, 0, 0, 0, 0],
|
66
|
+
id: undefined,
|
67
|
+
href: undefined,
|
68
|
+
},
|
69
|
+
{
|
70
|
+
depth: 2,
|
71
|
+
value: "Heading 2",
|
72
|
+
numbering: [1, 1, 0, 0, 0, 0],
|
73
|
+
id: undefined,
|
74
|
+
href: undefined,
|
75
|
+
},
|
76
|
+
{
|
77
|
+
depth: 3,
|
78
|
+
value: "Heading 3",
|
79
|
+
numbering: [1, 1, 1, 0, 0, 0],
|
80
|
+
id: undefined,
|
81
|
+
href: undefined,
|
82
|
+
},
|
83
|
+
]);
|
84
|
+
});
|
85
|
+
it("should generate correct TOC structure with heading IDs", async () => {
|
86
|
+
const toc = await getTocFromMdxModuleWithRehypeSlug(source);
|
87
|
+
assert.deepEqual(toc, [
|
88
|
+
{
|
89
|
+
depth: 1,
|
90
|
+
value: "The Main Heading",
|
91
|
+
numbering: [1, 0, 0, 0, 0, 0],
|
92
|
+
id: "the-main-heading",
|
93
|
+
href: "#the-main-heading",
|
94
|
+
},
|
95
|
+
{
|
96
|
+
depth: 2,
|
97
|
+
value: "Section 1",
|
98
|
+
numbering: [1, 1, 0, 0, 0, 0],
|
99
|
+
id: "section-1",
|
100
|
+
href: "#section-1",
|
101
|
+
},
|
102
|
+
{
|
103
|
+
depth: 3,
|
104
|
+
value: "Subheading",
|
105
|
+
numbering: [1, 1, 1, 0, 0, 0],
|
106
|
+
id: "subheading",
|
107
|
+
href: "#subheading",
|
108
|
+
},
|
109
|
+
{
|
110
|
+
depth: 3,
|
111
|
+
value: "Mubheading",
|
112
|
+
numbering: [1, 1, 2, 0, 0, 0],
|
113
|
+
id: "mubheading",
|
114
|
+
href: "#mubheading",
|
115
|
+
},
|
116
|
+
{
|
117
|
+
depth: 2,
|
118
|
+
value: "Section 2",
|
119
|
+
numbering: [1, 2, 0, 0, 0, 0],
|
120
|
+
id: "section-2",
|
121
|
+
href: "#section-2",
|
122
|
+
},
|
123
|
+
{
|
124
|
+
depth: 3,
|
125
|
+
value: "Subheading",
|
126
|
+
numbering: [1, 2, 1, 0, 0, 0],
|
127
|
+
id: "subheading-1",
|
128
|
+
href: "#subheading-1",
|
129
|
+
},
|
130
|
+
{
|
131
|
+
depth: 3,
|
132
|
+
value: "Mubheading",
|
133
|
+
numbering: [1, 2, 2, 0, 0, 0],
|
134
|
+
id: "mubheading-1",
|
135
|
+
href: "#mubheading-1",
|
136
|
+
},
|
137
|
+
{
|
138
|
+
depth: 4,
|
139
|
+
value: "QuoteHeading",
|
140
|
+
numbering: [1, 2, 2, 1, 0, 0],
|
141
|
+
id: "quoteheading",
|
142
|
+
href: "#quoteheading",
|
143
|
+
},
|
144
|
+
{
|
145
|
+
depth: 4,
|
146
|
+
value: "QuoteHeading",
|
147
|
+
numbering: [1, 2, 2, 2, 0, 0],
|
148
|
+
id: "quoteheading-1",
|
149
|
+
href: "#quoteheading-1",
|
150
|
+
},
|
151
|
+
{
|
152
|
+
depth: 5,
|
153
|
+
value: "ListItem Heading",
|
154
|
+
numbering: [1, 2, 2, 2, 1, 0],
|
155
|
+
id: "listitem-heading",
|
156
|
+
href: "#listitem-heading",
|
157
|
+
},
|
158
|
+
{
|
159
|
+
depth: 5,
|
160
|
+
value: "ListItem Heading",
|
161
|
+
numbering: [1, 2, 2, 2, 2, 0],
|
162
|
+
id: "listitem-heading-1",
|
163
|
+
href: "#listitem-heading-1",
|
164
|
+
},
|
165
|
+
]);
|
166
|
+
});
|
167
|
+
it("should export toc with default export name", async () => {
|
168
|
+
const toc = await getTocFromMdxModule(source);
|
169
|
+
assert.ok(Array.isArray(toc), "should export toc with default export name");
|
170
|
+
});
|
171
|
+
it("should export toc with custom export name", async () => {
|
172
|
+
const compiled = await compile(source, {
|
173
|
+
outputFormat: "function-body",
|
174
|
+
rehypePlugins: [[rehypeMdxToc, { name: "customToc" }]],
|
175
|
+
});
|
176
|
+
const result = await run(compiled, { ...runtime });
|
177
|
+
assert.ok(Array.isArray(result.customToc), "Should export toc with custom name");
|
178
|
+
});
|
179
|
+
it("should export toc with default value (which is undefined) without headings", async () => {
|
180
|
+
const mdx = "This is a test without headings.";
|
181
|
+
const toc = await getTocFromMdxModule(mdx);
|
182
|
+
assert.ok(toc === undefined, "should export toc with default value (which is undefined) without headings");
|
183
|
+
});
|
184
|
+
it("should export toc with custom default value without headings", async () => {
|
185
|
+
const mdx = "This is a test without headings.";
|
186
|
+
const compiled = await compile(mdx, {
|
187
|
+
outputFormat: "function-body",
|
188
|
+
rehypePlugins: [[rehypeMdxToc, { default: "No headings in this MDX" }]],
|
189
|
+
});
|
190
|
+
const result = await run(compiled, { ...runtime });
|
191
|
+
assert.ok((result.toc = "No headings in this MDX"), "should export toc with custom default value without headings");
|
192
|
+
});
|
193
|
+
it("should skip headings based on skipDepth option", async () => {
|
194
|
+
const mdx = `
|
195
|
+
# Heading 1
|
196
|
+
## Heading 2
|
197
|
+
### Heading 3
|
198
|
+
#### Heading 4
|
199
|
+
#### Heading 4.1
|
200
|
+
##### Heading 5
|
201
|
+
###### Heading 6
|
202
|
+
### Heading 3.1
|
203
|
+
#### Heading 4.2
|
204
|
+
`;
|
205
|
+
const compiled = await compile(mdx, {
|
206
|
+
outputFormat: "function-body",
|
207
|
+
rehypePlugins: [[rehypeMdxToc, { skipDepth: [1, 2, 5, 6] }]],
|
208
|
+
});
|
209
|
+
const result = await run(compiled, { ...runtime });
|
210
|
+
const toc = result.toc;
|
211
|
+
assert.deepEqual(toc, [
|
212
|
+
{
|
213
|
+
depth: 3,
|
214
|
+
value: "Heading 3",
|
215
|
+
numbering: [0, 0, 1, 0, 0, 0],
|
216
|
+
id: undefined,
|
217
|
+
href: undefined,
|
218
|
+
},
|
219
|
+
{
|
220
|
+
depth: 4,
|
221
|
+
value: "Heading 4",
|
222
|
+
numbering: [0, 0, 1, 1, 0, 0],
|
223
|
+
id: undefined,
|
224
|
+
href: undefined,
|
225
|
+
},
|
226
|
+
{
|
227
|
+
depth: 4,
|
228
|
+
value: "Heading 4.1",
|
229
|
+
numbering: [0, 0, 1, 2, 0, 0],
|
230
|
+
id: undefined,
|
231
|
+
href: undefined,
|
232
|
+
},
|
233
|
+
{
|
234
|
+
depth: 3,
|
235
|
+
value: "Heading 3.1",
|
236
|
+
numbering: [0, 0, 2, 0, 0, 0],
|
237
|
+
id: undefined,
|
238
|
+
href: undefined,
|
239
|
+
},
|
240
|
+
{
|
241
|
+
depth: 4,
|
242
|
+
value: "Heading 4.2",
|
243
|
+
numbering: [0, 0, 2, 1, 0, 0],
|
244
|
+
id: undefined,
|
245
|
+
href: undefined,
|
246
|
+
},
|
247
|
+
]);
|
248
|
+
});
|
249
|
+
});
|
250
|
+
//# sourceMappingURL=index.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"root":["../src/index.ts","../test/index.ts"],"version":"5.8.3"}
|
package/package.json
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
{
|
2
|
+
"name": "rehype-mdx-toc",
|
3
|
+
"type": "module",
|
4
|
+
"version": "0.0.1",
|
5
|
+
"description": "A rehype plugin to create the table of contents and convert it into MDX exports",
|
6
|
+
"keywords": [
|
7
|
+
"toc",
|
8
|
+
"table of contents",
|
9
|
+
"markdown",
|
10
|
+
"markdown-toc",
|
11
|
+
"mdx",
|
12
|
+
"rehype",
|
13
|
+
"rehype-plugin",
|
14
|
+
"hast",
|
15
|
+
"unified"
|
16
|
+
],
|
17
|
+
"homepage": "https://github.com/boning-w/rehype-mdx-toc#readme",
|
18
|
+
"bugs": {
|
19
|
+
"url": "https://github.com/boning-w/rehype-mdx-toc/issues"
|
20
|
+
},
|
21
|
+
"license": "MIT",
|
22
|
+
"author": "boning <wang.boning@outlook.com>",
|
23
|
+
"files": [
|
24
|
+
"dist",
|
25
|
+
"src"
|
26
|
+
],
|
27
|
+
"exports": {
|
28
|
+
".": {
|
29
|
+
"types": "./dist/src/index.d.ts",
|
30
|
+
"import": "./dist/src/index.js",
|
31
|
+
"default": "./dist/src/index.js"
|
32
|
+
}
|
33
|
+
},
|
34
|
+
"types": "./dist/src/index.d.ts",
|
35
|
+
"main": "./dist/src/index.js",
|
36
|
+
"repository": {
|
37
|
+
"type": "git",
|
38
|
+
"url": "git+https://github.com/boning-w/rehype-mdx-toc.git"
|
39
|
+
},
|
40
|
+
"scripts": {
|
41
|
+
"build": "tsc --build",
|
42
|
+
"pretest": "npm run build",
|
43
|
+
"test": "node --test",
|
44
|
+
"test-coverage": "c8 --100 node --test && c8 report --reporter=html --reporter=lcov"
|
45
|
+
},
|
46
|
+
"dependencies": {
|
47
|
+
"estree-util-value-to-estree": "^3.4.0",
|
48
|
+
"hast-util-heading-rank": "^3.0.0",
|
49
|
+
"hast-util-to-string": "^3.0.1",
|
50
|
+
"unist-util-mdx-define": "^1.1.2",
|
51
|
+
"unist-util-visit": "^5.0.0"
|
52
|
+
},
|
53
|
+
"devDependencies": {
|
54
|
+
"@mdx-js/mdx": "^3.1.0",
|
55
|
+
"@types/hast": "^3.0.4",
|
56
|
+
"@types/node": "^22.16.0",
|
57
|
+
"@types/react": "^19.1.8",
|
58
|
+
"@types/vfile": "^3.0.2",
|
59
|
+
"c8": "^10.1.3",
|
60
|
+
"react": "^19.1.0",
|
61
|
+
"rehype-slug": "^6.0.0",
|
62
|
+
"typescript": "^5.8.3"
|
63
|
+
},
|
64
|
+
"sideEffects": false
|
65
|
+
}
|
package/src/index.ts
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
import type { Root } from "hast";
|
2
|
+
import type { VFile } from "vfile";
|
3
|
+
import { define } from "unist-util-mdx-define";
|
4
|
+
import { headingRank } from "hast-util-heading-rank";
|
5
|
+
import { toString } from "hast-util-to-string";
|
6
|
+
import { valueToEstree } from "estree-util-value-to-estree";
|
7
|
+
import { CONTINUE, visit } from "unist-util-visit";
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Type representing the depth of headings in a table of contents.
|
11
|
+
* This is used to specify which heading levels (h1, h2, etc.) should be included
|
12
|
+
* in the table of contents.
|
13
|
+
*
|
14
|
+
* The values correspond to HTML heading elements:
|
15
|
+
* - 1 for `<h1>`
|
16
|
+
* - 2 for `<h2>`
|
17
|
+
* - 3 for `<h3>`
|
18
|
+
* - 4 for `<h4>`
|
19
|
+
* - 5 for `<h5>`
|
20
|
+
* - 6 for `<h6>`
|
21
|
+
*/
|
22
|
+
export type HeadingDepth = 1 | 2 | 3 | 4 | 5 | 6;
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Options for the rehype-mdx-toc plugin.
|
26
|
+
*
|
27
|
+
* This type defines the configuration options that can be passed to the plugin.
|
28
|
+
* - `default`: Optional default value for the table of contents when no headings are found.
|
29
|
+
* - `name`: The name of the export variable for the table of contents.
|
30
|
+
*/
|
31
|
+
export type RehypeMdxTocOptions = {
|
32
|
+
/**
|
33
|
+
* Optional default value for the table of contents.
|
34
|
+
* If provided, this will be used when no headings are found.
|
35
|
+
*
|
36
|
+
* @default undefined
|
37
|
+
*/
|
38
|
+
default?: unknown;
|
39
|
+
|
40
|
+
/**
|
41
|
+
* The name of the export variable for the table of contents.
|
42
|
+
*
|
43
|
+
* @default "toc"
|
44
|
+
*/
|
45
|
+
name?: string;
|
46
|
+
|
47
|
+
/**
|
48
|
+
* An array of heading depths to skip.
|
49
|
+
* If specified, headings of these depths will not be included in the TOC.
|
50
|
+
*
|
51
|
+
* @default []
|
52
|
+
*/
|
53
|
+
skipDepth?: HeadingDepth[];
|
54
|
+
};
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Represents a single item in the table of contents (TOC).
|
58
|
+
*
|
59
|
+
* Each item corresponds to a heading in the MDX file and includes:
|
60
|
+
* - `depth`: The level of the heading (1 for `<h1>`, 2 for `<h2>`, etc.).
|
61
|
+
* - `value`: The text content of the heading.
|
62
|
+
* - `numbering`: An array representing the hierarchical numbering of the heading.
|
63
|
+
* - `id`: The ID of the heading, if available.
|
64
|
+
* - `href`: The link to the heading, typically a fragment identifier.
|
65
|
+
* - `data`: Additional data associated with the heading.
|
66
|
+
*/
|
67
|
+
export type TocItem = {
|
68
|
+
depth: HeadingDepth;
|
69
|
+
value: string;
|
70
|
+
numbering: number[];
|
71
|
+
id?: string;
|
72
|
+
href?: string;
|
73
|
+
data?: Record<string, unknown>;
|
74
|
+
};
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Rehype plugin to generate a table of contents (TOC) for MDX files.
|
78
|
+
*
|
79
|
+
* This plugin processes the HAST tree to find all headings and constructs a TOC
|
80
|
+
* with hierarchical numbering. The TOC is then exported as a variable that can
|
81
|
+
* be used in the MDX module.
|
82
|
+
*
|
83
|
+
* @param options - Optional configuration for the plugin.
|
84
|
+
* @returns A function that transforms the HAST tree.
|
85
|
+
*/
|
86
|
+
export default function rehypeMdxToc({
|
87
|
+
name = "toc",
|
88
|
+
...options
|
89
|
+
}: RehypeMdxTocOptions = {}) {
|
90
|
+
// Return the actual transformer function for rehype.
|
91
|
+
return (tree: Root, file: VFile) => {
|
92
|
+
// This will hold the final table of contents data.
|
93
|
+
const toc: TocItem[] = [];
|
94
|
+
|
95
|
+
// Tracks the current numbering for headings.
|
96
|
+
// Each index corresponds to a heading level: [h1, h2, h3, h4, h5, h6].
|
97
|
+
const currentNumbering: number[] = [0, 0, 0, 0, 0, 0];
|
98
|
+
|
99
|
+
// Visit every HTML element in the tree.
|
100
|
+
visit(tree, "element", function (node) {
|
101
|
+
// Check if this element is a heading (<h1>–<h6>).
|
102
|
+
const rank = headingRank(node);
|
103
|
+
|
104
|
+
// If the rank is undefined, skip this node.
|
105
|
+
if (rank === undefined) return CONTINUE;
|
106
|
+
|
107
|
+
// If the depth is in the skipDepth array, skip this node.
|
108
|
+
const depth = rank as HeadingDepth;
|
109
|
+
if (options.skipDepth?.includes(depth)) return CONTINUE;
|
110
|
+
|
111
|
+
// Increment the counter for this heading level.
|
112
|
+
currentNumbering[depth - 1] += 1;
|
113
|
+
|
114
|
+
// Reset all deeper levels to zero.
|
115
|
+
// This ensures the numbering doesn't accidentally carry over.
|
116
|
+
for (let i = depth; i < currentNumbering.length; i++) {
|
117
|
+
currentNumbering[i] = 0;
|
118
|
+
}
|
119
|
+
|
120
|
+
// Add this heading to the TOC array.
|
121
|
+
toc.push({
|
122
|
+
depth,
|
123
|
+
value: toString(node),
|
124
|
+
numbering: [...currentNumbering],
|
125
|
+
id: node.properties?.id as string | undefined,
|
126
|
+
href: node.properties?.id ? `#${node.properties.id}` : undefined,
|
127
|
+
});
|
128
|
+
});
|
129
|
+
|
130
|
+
// Attach the TOC as an exported variable in the MDX file.
|
131
|
+
const data = toc.length ? toc : options.default;
|
132
|
+
|
133
|
+
define(tree, file, {
|
134
|
+
[name]: valueToEstree(data),
|
135
|
+
});
|
136
|
+
};
|
137
|
+
}
|