@azerion/bunosaurus-mdx-loader 0.0.0-stage → 4.0.0
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/README.md +57 -2
- package/lib/createMDXLoader.d.ts +14 -0
- package/lib/createMDXLoader.d.ts.map +1 -0
- package/lib/createMDXLoader.js +52 -0
- package/lib/createMDXLoader.js.map +1 -0
- package/lib/format.d.ts +14 -0
- package/lib/format.d.ts.map +1 -0
- package/lib/format.js +41 -0
- package/lib/format.js.map +1 -0
- package/lib/frontMatter.d.ts +13 -0
- package/lib/frontMatter.d.ts.map +1 -0
- package/lib/frontMatter.js +23 -0
- package/lib/frontMatter.js.map +1 -0
- package/lib/index.d.ts +35 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +15 -0
- package/lib/index.js.map +1 -0
- package/lib/loader.d.ts +11 -0
- package/lib/loader.d.ts.map +1 -0
- package/lib/loader.js +170 -0
- package/lib/loader.js.map +1 -0
- package/lib/options.d.ts +35 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +9 -0
- package/lib/options.js.map +1 -0
- package/lib/preprocessor.d.ts +18 -0
- package/lib/preprocessor.d.ts.map +1 -0
- package/lib/preprocessor.js +34 -0
- package/lib/preprocessor.js.map +1 -0
- package/lib/processor.d.ts +49 -0
- package/lib/processor.d.ts.map +1 -0
- package/lib/processor.js +209 -0
- package/lib/processor.js.map +1 -0
- package/lib/remark/admonitions/index.d.ts +11 -0
- package/lib/remark/admonitions/index.d.ts.map +1 -0
- package/lib/remark/admonitions/index.js +107 -0
- package/lib/remark/admonitions/index.js.map +1 -0
- package/lib/remark/contentTitle/index.d.ts +13 -0
- package/lib/remark/contentTitle/index.d.ts.map +1 -0
- package/lib/remark/contentTitle/index.js +62 -0
- package/lib/remark/contentTitle/index.js.map +1 -0
- package/lib/remark/details/index.d.ts +10 -0
- package/lib/remark/details/index.d.ts.map +1 -0
- package/lib/remark/details/index.js +22 -0
- package/lib/remark/details/index.js.map +1 -0
- package/lib/remark/head/index.d.ts +10 -0
- package/lib/remark/head/index.d.ts.map +1 -0
- package/lib/remark/head/index.js +22 -0
- package/lib/remark/head/index.js.map +1 -0
- package/lib/remark/headings/index.d.ts +14 -0
- package/lib/remark/headings/index.d.ts.map +1 -0
- package/lib/remark/headings/index.js +126 -0
- package/lib/remark/headings/index.js.map +1 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts +11 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts.map +1 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.js +30 -0
- package/lib/remark/mdx1Compat/codeCompatPlugin.js.map +1 -0
- package/lib/remark/mermaid/index.d.ts +10 -0
- package/lib/remark/mermaid/index.d.ts.map +1 -0
- package/lib/remark/mermaid/index.js +34 -0
- package/lib/remark/mermaid/index.js.map +1 -0
- package/lib/remark/resolveMarkdownLinks/index.d.ts +33 -0
- package/lib/remark/resolveMarkdownLinks/index.d.ts.map +1 -0
- package/lib/remark/resolveMarkdownLinks/index.js +87 -0
- package/lib/remark/resolveMarkdownLinks/index.js.map +1 -0
- package/lib/remark/toc/index.d.ts +14 -0
- package/lib/remark/toc/index.d.ts.map +1 -0
- package/lib/remark/toc/index.js +115 -0
- package/lib/remark/toc/index.js.map +1 -0
- package/lib/remark/toc/types.d.ts +22 -0
- package/lib/remark/toc/types.d.ts.map +1 -0
- package/lib/remark/toc/types.js +9 -0
- package/lib/remark/toc/types.js.map +1 -0
- package/lib/remark/toc/utils.d.ts +26 -0
- package/lib/remark/toc/utils.d.ts.map +1 -0
- package/lib/remark/toc/utils.js +184 -0
- package/lib/remark/toc/utils.js.map +1 -0
- package/lib/remark/transformImage/index.d.ts +17 -0
- package/lib/remark/transformImage/index.d.ts.map +1 -0
- package/lib/remark/transformImage/index.js +187 -0
- package/lib/remark/transformImage/index.js.map +1 -0
- package/lib/remark/transformLinks/index.d.ts +17 -0
- package/lib/remark/transformLinks/index.d.ts.map +1 -0
- package/lib/remark/transformLinks/index.js +205 -0
- package/lib/remark/transformLinks/index.js.map +1 -0
- package/lib/remark/unusedDirectives/index.d.ts +9 -0
- package/lib/remark/unusedDirectives/index.d.ts.map +1 -0
- package/lib/remark/unusedDirectives/index.js +125 -0
- package/lib/remark/unusedDirectives/index.js.map +1 -0
- package/lib/remark/utils/index.d.ts +18 -0
- package/lib/remark/utils/index.d.ts.map +1 -0
- package/lib/remark/utils/index.js +93 -0
- package/lib/remark/utils/index.js.map +1 -0
- package/lib/serializeJS.d.ts +17 -0
- package/lib/serializeJS.d.ts.map +1 -0
- package/lib/serializeJS.js +24 -0
- package/lib/serializeJS.js.map +1 -0
- package/lib/utils.d.ts +35 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +97 -0
- package/lib/utils.js.map +1 -0
- package/package.json +65 -4
- package/src/createMDXLoader.ts +65 -0
- package/src/format.ts +50 -0
- package/src/frontMatter.ts +31 -0
- package/src/index.ts +44 -0
- package/src/loader.ts +227 -0
- package/src/options.ts +37 -0
- package/src/preprocessor.ts +47 -0
- package/src/processor.ts +306 -0
- package/src/remark/admonitions/LICENSE +45 -0
- package/src/remark/admonitions/README.md +3 -0
- package/src/remark/admonitions/index.ts +138 -0
- package/src/remark/contentTitle/index.ts +76 -0
- package/src/remark/details/index.ts +23 -0
- package/src/remark/head/index.ts +23 -0
- package/src/remark/headings/index.ts +158 -0
- package/src/remark/mdx1Compat/codeCompatPlugin.ts +35 -0
- package/src/remark/mermaid/index.ts +35 -0
- package/src/remark/resolveMarkdownLinks/index.ts +137 -0
- package/src/remark/toc/index.ts +183 -0
- package/src/remark/toc/types.ts +29 -0
- package/src/remark/toc/utils.ts +243 -0
- package/src/remark/transformImage/index.ts +268 -0
- package/src/remark/transformLinks/index.ts +291 -0
- package/src/remark/unusedDirectives/index.ts +171 -0
- package/src/remark/utils/index.ts +99 -0
- package/src/serializeJS.ts +22 -0
- package/src/types.d.mts +40 -0
- package/src/utils.ts +134 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Elvis Wolcott
|
|
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.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
MIT License
|
|
26
|
+
|
|
27
|
+
Copyright (c) Facebook, Inc. and its affiliates.
|
|
28
|
+
|
|
29
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
30
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
31
|
+
in the Software without restriction, including without limitation the rights
|
|
32
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
33
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
34
|
+
furnished to do so, subject to the following conditions:
|
|
35
|
+
|
|
36
|
+
The above copyright notice and this permission notice shall be included in all
|
|
37
|
+
copies or substantial portions of the Software.
|
|
38
|
+
|
|
39
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
40
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
41
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
42
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
43
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
44
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
45
|
+
SOFTWARE.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import {visit} from 'unist-util-visit';
|
|
8
|
+
import type {Transformer, Plugin} from 'unified';
|
|
9
|
+
import type {ContainerDirective} from 'mdast-util-directive';
|
|
10
|
+
import type {Parent, Root} from 'mdast';
|
|
11
|
+
|
|
12
|
+
export type AdmonitionOptions = {
|
|
13
|
+
keywords: string[];
|
|
14
|
+
extendDefaults: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const DefaultAdmonitionOptions: AdmonitionOptions = {
|
|
18
|
+
keywords: [
|
|
19
|
+
'secondary',
|
|
20
|
+
'info',
|
|
21
|
+
'success',
|
|
22
|
+
'danger',
|
|
23
|
+
'note',
|
|
24
|
+
'tip',
|
|
25
|
+
'warning',
|
|
26
|
+
'important',
|
|
27
|
+
'caution',
|
|
28
|
+
],
|
|
29
|
+
extendDefaults: true,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function normalizeAdmonitionOptions(
|
|
33
|
+
providedOptions: Partial<AdmonitionOptions> | true,
|
|
34
|
+
): AdmonitionOptions {
|
|
35
|
+
if (providedOptions === true) {
|
|
36
|
+
return DefaultAdmonitionOptions;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const options = {...DefaultAdmonitionOptions, ...providedOptions};
|
|
40
|
+
|
|
41
|
+
// By default it makes more sense to append keywords to the default ones
|
|
42
|
+
// Adding custom keywords is more common than disabling existing ones
|
|
43
|
+
if (options.extendDefaults) {
|
|
44
|
+
options.keywords = [
|
|
45
|
+
...DefaultAdmonitionOptions.keywords,
|
|
46
|
+
...options.keywords,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return options;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type DirectiveLabel = Parent;
|
|
54
|
+
type DirectiveContent = ContainerDirective['children'];
|
|
55
|
+
|
|
56
|
+
function parseDirective(directive: ContainerDirective): {
|
|
57
|
+
directiveLabel: DirectiveLabel | undefined;
|
|
58
|
+
contentNodes: DirectiveContent;
|
|
59
|
+
} {
|
|
60
|
+
const hasDirectiveLabel =
|
|
61
|
+
// @ts-expect-error: fine
|
|
62
|
+
directive.children?.[0]?.data?.directiveLabel === true;
|
|
63
|
+
if (hasDirectiveLabel) {
|
|
64
|
+
const [directiveLabel, ...contentNodes] = directive.children;
|
|
65
|
+
return {directiveLabel: directiveLabel as DirectiveLabel, contentNodes};
|
|
66
|
+
}
|
|
67
|
+
return {directiveLabel: undefined, contentNodes: directive.children};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getTextOnlyTitle(directiveLabel: DirectiveLabel): string | undefined {
|
|
71
|
+
const isTextOnlyTitle =
|
|
72
|
+
directiveLabel?.children?.length === 1 &&
|
|
73
|
+
directiveLabel?.children?.[0]?.type === 'text';
|
|
74
|
+
return isTextOnlyTitle
|
|
75
|
+
? // @ts-expect-error: todo type
|
|
76
|
+
(directiveLabel?.children?.[0].value as string)
|
|
77
|
+
: undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const plugin: Plugin<Partial<AdmonitionOptions>[], Root> = function plugin(
|
|
81
|
+
this,
|
|
82
|
+
optionsInput = {},
|
|
83
|
+
): Transformer<Root> {
|
|
84
|
+
const {keywords} = normalizeAdmonitionOptions(optionsInput);
|
|
85
|
+
|
|
86
|
+
return (root) => {
|
|
87
|
+
visit(root, (node) => {
|
|
88
|
+
if (node.type === 'containerDirective') {
|
|
89
|
+
const isAdmonition = keywords.includes(node.name);
|
|
90
|
+
|
|
91
|
+
if (!isAdmonition) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const {directiveLabel, contentNodes} = parseDirective(node);
|
|
96
|
+
|
|
97
|
+
const textOnlyTitle =
|
|
98
|
+
node.attributes?.title ??
|
|
99
|
+
(directiveLabel ? getTextOnlyTitle(directiveLabel) : undefined);
|
|
100
|
+
|
|
101
|
+
// Transform the mdast directive node to a hast admonition node
|
|
102
|
+
// See https://github.com/syntax-tree/mdast-util-to-hast#fields-on-nodes
|
|
103
|
+
// TODO in MDX v2 we should transform the whole directive to
|
|
104
|
+
// mdxJsxFlowElement instead of using hast
|
|
105
|
+
node.data = {
|
|
106
|
+
hName: 'admonition',
|
|
107
|
+
hProperties: {
|
|
108
|
+
...(textOnlyTitle && {title: textOnlyTitle}),
|
|
109
|
+
...(node.attributes?.class && {
|
|
110
|
+
className: node.attributes.class.split(' '),
|
|
111
|
+
}),
|
|
112
|
+
...(node.attributes?.id && {id: node.attributes.id}),
|
|
113
|
+
type: node.name,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
node.children = contentNodes;
|
|
117
|
+
|
|
118
|
+
// TODO legacy MDX v1 <mdxAdmonitionTitle> workaround
|
|
119
|
+
// v1: not possible to inject complex JSX elements as props
|
|
120
|
+
// v2: now possible: use a mdxJsxFlowElement element
|
|
121
|
+
if (directiveLabel && !textOnlyTitle) {
|
|
122
|
+
const complexTitleNode = {
|
|
123
|
+
type: 'mdxAdmonitionTitle',
|
|
124
|
+
data: {
|
|
125
|
+
hName: 'mdxAdmonitionTitle',
|
|
126
|
+
hProperties: {},
|
|
127
|
+
},
|
|
128
|
+
children: directiveLabel.children,
|
|
129
|
+
};
|
|
130
|
+
// @ts-expect-error: invented node type
|
|
131
|
+
node.children.unshift(complexTitleNode);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export default plugin;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import {toString} from 'mdast-util-to-string';
|
|
8
|
+
import {visit, EXIT} from 'unist-util-visit';
|
|
9
|
+
import type {Transformer, Plugin} from 'unified';
|
|
10
|
+
import type {Heading, Parent, Root} from 'mdast';
|
|
11
|
+
import type {MdxJsxFlowElement} from 'mdast-util-mdx';
|
|
12
|
+
|
|
13
|
+
interface PluginOptions {
|
|
14
|
+
removeContentTitle?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function wrapHeadingInJsxHeader(
|
|
18
|
+
headingNode: Heading,
|
|
19
|
+
parent: Parent,
|
|
20
|
+
index: number,
|
|
21
|
+
) {
|
|
22
|
+
const header: MdxJsxFlowElement = {
|
|
23
|
+
type: 'mdxJsxFlowElement',
|
|
24
|
+
name: 'header',
|
|
25
|
+
attributes: [],
|
|
26
|
+
children: [headingNode],
|
|
27
|
+
};
|
|
28
|
+
parent.children[index] = header;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A remark plugin to extract the h1 heading found in Markdown files
|
|
33
|
+
* This is exposed as "data.contentTitle" to the processed vfile
|
|
34
|
+
* Also gives the ability to strip that content title (used for the blog plugin)
|
|
35
|
+
*/
|
|
36
|
+
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
|
37
|
+
options = {},
|
|
38
|
+
): Transformer<Root> {
|
|
39
|
+
// content title is
|
|
40
|
+
const removeContentTitle = options.removeContentTitle ?? false;
|
|
41
|
+
|
|
42
|
+
return (root, vfile) => {
|
|
43
|
+
visit(root, ['heading', 'thematicBreak'], (node, index, parent) => {
|
|
44
|
+
if (!parent || index === undefined) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (node.type === 'heading') {
|
|
48
|
+
// console.log('headingNode:', headingNode);
|
|
49
|
+
|
|
50
|
+
if (node.depth === 1) {
|
|
51
|
+
vfile.data.contentTitle = toString(node);
|
|
52
|
+
if (removeContentTitle) {
|
|
53
|
+
parent.children.splice(index, 1);
|
|
54
|
+
} else {
|
|
55
|
+
// TODO in the future it might be better to export contentTitle as
|
|
56
|
+
// as JSX node to keep this logic a theme concern?
|
|
57
|
+
// See https://github.com/facebook/docusaurus/pull/10335#issuecomment-2250187371
|
|
58
|
+
wrapHeadingInJsxHeader(node, parent, index);
|
|
59
|
+
}
|
|
60
|
+
return EXIT; // We only handle the very first heading
|
|
61
|
+
}
|
|
62
|
+
// We only handle contentTitle if it's the very first heading found
|
|
63
|
+
if (node.depth >= 1) {
|
|
64
|
+
return EXIT;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// We only handle contentTitle when it's above the first thematic break
|
|
68
|
+
if (node.type === 'thematicBreak') {
|
|
69
|
+
return EXIT;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default plugin;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {visit} from 'unist-util-visit';
|
|
9
|
+
import type {Transformer} from 'unified';
|
|
10
|
+
|
|
11
|
+
import type {Root} from 'mdast';
|
|
12
|
+
|
|
13
|
+
// Transform <details> to <Details>
|
|
14
|
+
// MDX 2 doesn't allow to substitute html elements with the provider anymore
|
|
15
|
+
export default function plugin(): Transformer<Root> {
|
|
16
|
+
return (root) => {
|
|
17
|
+
visit(root, 'mdxJsxFlowElement', (node) => {
|
|
18
|
+
if (node.name === 'details') {
|
|
19
|
+
node.name = 'Details';
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {visit} from 'unist-util-visit';
|
|
9
|
+
import type {Transformer} from 'unified';
|
|
10
|
+
|
|
11
|
+
import type {Root} from 'mdast';
|
|
12
|
+
|
|
13
|
+
// Transform <head> to <Head>
|
|
14
|
+
// MDX 2 doesn't allow to substitute html elements with the provider anymore
|
|
15
|
+
export default function plugin(): Transformer<Root> {
|
|
16
|
+
return (root) => {
|
|
17
|
+
visit(root, 'mdxJsxFlowElement', (node) => {
|
|
18
|
+
if (node.name === 'head') {
|
|
19
|
+
node.name = 'Head';
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* Based on remark-slug (https://github.com/remarkjs/remark-slug) and gatsby-remark-autolink-headers (https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-remark-autolink-headers) */
|
|
9
|
+
|
|
10
|
+
import {toString} from 'mdast-util-to-string';
|
|
11
|
+
import {visit} from 'unist-util-visit';
|
|
12
|
+
import {parseMarkdownHeadingId, createSlugger} from '@docusaurus/utils';
|
|
13
|
+
import type {Plugin, Transformer} from 'unified';
|
|
14
|
+
import type {Heading, Root, Text} from 'mdast';
|
|
15
|
+
|
|
16
|
+
export interface PluginOptions {
|
|
17
|
+
anchorsMaintainCase: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getCommentContentHeadingId(comment: string): string | undefined {
|
|
21
|
+
// If the comment has spaces, we only consider the first part
|
|
22
|
+
const firstPart = comment.trim().split(' ')[0];
|
|
23
|
+
// We ignore comments that don't start with # on purpose
|
|
24
|
+
// Forcing users to use a leading # is more explicit
|
|
25
|
+
// In the future it's possible we'd want to allow other types of comments
|
|
26
|
+
// For example class comments like {/* .my-class */}
|
|
27
|
+
if (firstPart?.startsWith('#')) {
|
|
28
|
+
return firstPart.slice(1) || undefined;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getCommentHeadingId(heading: Heading): string | undefined {
|
|
34
|
+
const lastChild = heading.children.at(-1);
|
|
35
|
+
|
|
36
|
+
// MDX comment: {/* my-id */} or {/* #my-id */}
|
|
37
|
+
if (
|
|
38
|
+
lastChild &&
|
|
39
|
+
lastChild.type === 'mdxTextExpression' &&
|
|
40
|
+
lastChild.data?.estree
|
|
41
|
+
) {
|
|
42
|
+
const program = lastChild.data.estree;
|
|
43
|
+
// We only extract the id from single-comment MDX expressions
|
|
44
|
+
// ✅ {/* #my-id */}
|
|
45
|
+
// ❌ {/* #my-id */ /* #my-id2 */}
|
|
46
|
+
// ❌ {someExpression /* #my-id */}
|
|
47
|
+
if (program.body.length === 0 && program.comments?.length === 1) {
|
|
48
|
+
const commentContent = program.comments[0]!.value;
|
|
49
|
+
return getCommentContentHeadingId(commentContent);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// HTML comment: <!-- my-id --> or <!-- #my-id -->
|
|
54
|
+
if (lastChild?.type === 'html') {
|
|
55
|
+
const match = /^<!--(?<comment>[\s\S]*)-->$/.exec(
|
|
56
|
+
(lastChild as unknown as {value: string}).value,
|
|
57
|
+
);
|
|
58
|
+
if (match?.groups?.comment) {
|
|
59
|
+
const commentContent = match.groups.comment;
|
|
60
|
+
return getCommentContentHeadingId(commentContent);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Try to find an explicit id in MD/MDX comments
|
|
68
|
+
function extractCommentId(heading: Heading) {
|
|
69
|
+
const commentId = getCommentHeadingId(heading);
|
|
70
|
+
if (commentId) {
|
|
71
|
+
// Remove the last comment node
|
|
72
|
+
heading.children.pop();
|
|
73
|
+
// Trim the trailing space from the last text node ("text " → "text")
|
|
74
|
+
const newLast = heading.children.at(-1);
|
|
75
|
+
if (newLast?.type === 'text') {
|
|
76
|
+
newLast.value = newLast.value.trimEnd();
|
|
77
|
+
}
|
|
78
|
+
return commentId;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Try to find an explicit id in the heading text (classic {#id} syntax)
|
|
84
|
+
function extractClassicSyntaxHeadingId(heading: Heading, headingText: string) {
|
|
85
|
+
const parsedHeading = parseMarkdownHeadingId(headingText, 'classic');
|
|
86
|
+
// Remove the heading text from its id (legacy syntax)
|
|
87
|
+
if (parsedHeading.id) {
|
|
88
|
+
// When there's an id, it is always in the last child node
|
|
89
|
+
const lastNode = heading.children.at(-1) as Text;
|
|
90
|
+
if (heading.children.length > 1) {
|
|
91
|
+
const lastNodeText = parseMarkdownHeadingId(
|
|
92
|
+
lastNode.value,
|
|
93
|
+
'classic',
|
|
94
|
+
).text;
|
|
95
|
+
// When the last part contains text + id, remove the id
|
|
96
|
+
if (lastNodeText) {
|
|
97
|
+
lastNode.value = lastNodeText;
|
|
98
|
+
}
|
|
99
|
+
// When last part contains only the id: completely remove that node
|
|
100
|
+
else {
|
|
101
|
+
heading.children.pop();
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
lastNode.value = parsedHeading.text;
|
|
105
|
+
}
|
|
106
|
+
return parsedHeading.id;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const plugin: Plugin<PluginOptions[], Root> = function plugin({
|
|
112
|
+
anchorsMaintainCase,
|
|
113
|
+
}): Transformer<Root> {
|
|
114
|
+
return (root) => {
|
|
115
|
+
function getHeadingText(heading: Heading) {
|
|
116
|
+
const headingTextNodes = heading.children.filter(
|
|
117
|
+
({type}) => !['html', 'jsx'].includes(type),
|
|
118
|
+
);
|
|
119
|
+
return toString(headingTextNodes.length > 0 ? headingTextNodes : heading);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const slugs = createSlugger();
|
|
123
|
+
visit(root, 'heading', (heading) => {
|
|
124
|
+
const data = heading.data ?? (heading.data = {});
|
|
125
|
+
const properties = data.hProperties ?? (data.hProperties = {});
|
|
126
|
+
|
|
127
|
+
// Gives the ability to provide/write a remark plugin that sets an id
|
|
128
|
+
// When an id is already set, we use it instead of running our own plugin
|
|
129
|
+
function extractAlreadyExistingId() {
|
|
130
|
+
if (properties.id) {
|
|
131
|
+
// Not sure why we need to slugify here, historical code
|
|
132
|
+
return slugs.slug(properties.id, {maintainCase: true});
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractIdFromText() {
|
|
138
|
+
const headingText = getHeadingText(heading);
|
|
139
|
+
return (
|
|
140
|
+
extractClassicSyntaxHeadingId(heading, headingText) ??
|
|
141
|
+
slugs.slug(headingText, {maintainCase: anchorsMaintainCase})
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// All the ways we can extract an id, ordered by priority
|
|
146
|
+
// /!\ the extraction methods can perform AST cleanup side effects
|
|
147
|
+
const id =
|
|
148
|
+
extractAlreadyExistingId() ??
|
|
149
|
+
extractCommentId(heading) ??
|
|
150
|
+
extractIdFromText();
|
|
151
|
+
|
|
152
|
+
data.id = id;
|
|
153
|
+
properties.id = id;
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export default plugin;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {visit} from 'unist-util-visit';
|
|
9
|
+
import type {Transformer, Plugin} from 'unified';
|
|
10
|
+
import type {Root} from 'mdast';
|
|
11
|
+
|
|
12
|
+
// Solution inspired by https://github.com/pomber/docusaurus-mdx-2/blob/main/packages/mdx-loader/src/remark/codeCompat/index.ts
|
|
13
|
+
// TODO after MDX 2 we probably don't need this - remove soon?
|
|
14
|
+
// Only fenced code blocks are swapped by pre/code MDX components
|
|
15
|
+
// Using <pre><code> in JSX shouldn't use our MDX components anymore
|
|
16
|
+
|
|
17
|
+
// To make theme-classic/src/theme/MDXComponents/Pre work
|
|
18
|
+
// we need to fill two properties that mdx v2 doesn't provide anymore
|
|
19
|
+
const plugin: Plugin<unknown[], Root> = function plugin(): Transformer<Root> {
|
|
20
|
+
return (root) => {
|
|
21
|
+
visit(root, 'code', (node) => {
|
|
22
|
+
node.data = node.data || {};
|
|
23
|
+
|
|
24
|
+
node.data.hProperties = node.data.hProperties || {};
|
|
25
|
+
node.data.hProperties.metastring = node.meta;
|
|
26
|
+
|
|
27
|
+
// TODO Docusaurus v4: remove special case
|
|
28
|
+
// Retrocompatible support for live codeblock metastring
|
|
29
|
+
// Not really the appropriate place to handle that :s
|
|
30
|
+
node.data.hProperties.live = node.meta?.split(' ').includes('live');
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default plugin;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {visit} from 'unist-util-visit';
|
|
9
|
+
import {transformNode} from '../utils';
|
|
10
|
+
|
|
11
|
+
import type {Transformer} from 'unified';
|
|
12
|
+
import type {Root} from 'mdast';
|
|
13
|
+
|
|
14
|
+
// TODO: this plugin shouldn't be in the core MDX loader
|
|
15
|
+
// After we allow plugins to provide Remark/Rehype plugins (see
|
|
16
|
+
// https://github.com/facebook/docusaurus/issues/6370), this should be provided
|
|
17
|
+
// by theme-mermaid itself
|
|
18
|
+
export default function plugin(): Transformer<Root> {
|
|
19
|
+
return (root) => {
|
|
20
|
+
visit(root, 'code', (node) => {
|
|
21
|
+
if (node.lang === 'mermaid') {
|
|
22
|
+
// TODO migrate to mdxJsxFlowElement? cf admonitions
|
|
23
|
+
transformNode(node, {
|
|
24
|
+
type: 'mermaidCodeBlock',
|
|
25
|
+
data: {
|
|
26
|
+
hName: 'mermaid',
|
|
27
|
+
hProperties: {
|
|
28
|
+
value: node.value,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {visit} from 'unist-util-visit';
|
|
9
|
+
import {
|
|
10
|
+
parseLocalURLPath,
|
|
11
|
+
serializeURLPath,
|
|
12
|
+
toMessageRelativeFilePath,
|
|
13
|
+
type URLPath,
|
|
14
|
+
} from '@docusaurus/utils';
|
|
15
|
+
import logger from '@docusaurus/logger';
|
|
16
|
+
|
|
17
|
+
import {formatNodePositionExtraMessage} from '../utils';
|
|
18
|
+
import type {Plugin, Transformer} from 'unified';
|
|
19
|
+
import type {Definition, Link, Root} from 'mdast';
|
|
20
|
+
import type {
|
|
21
|
+
MarkdownConfig,
|
|
22
|
+
OnBrokenMarkdownLinksFunction,
|
|
23
|
+
} from '@docusaurus/types';
|
|
24
|
+
|
|
25
|
+
type ResolveMarkdownLinkParams = {
|
|
26
|
+
/**
|
|
27
|
+
* Absolute path to the source file containing this Markdown link.
|
|
28
|
+
*/
|
|
29
|
+
sourceFilePath: string;
|
|
30
|
+
/**
|
|
31
|
+
* The Markdown link pathname to resolve, as found in the source file.
|
|
32
|
+
* If the link is "./myFile.mdx?qs#hash", this will be "./myFile.mdx"
|
|
33
|
+
*/
|
|
34
|
+
linkPathname: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ResolveMarkdownLink = (
|
|
38
|
+
params: ResolveMarkdownLinkParams,
|
|
39
|
+
) => string | null;
|
|
40
|
+
|
|
41
|
+
export interface PluginOptions {
|
|
42
|
+
resolveMarkdownLink: ResolveMarkdownLink;
|
|
43
|
+
onBrokenMarkdownLinks: MarkdownConfig['hooks']['onBrokenMarkdownLinks'];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function asFunction(
|
|
47
|
+
onBrokenMarkdownLinks: PluginOptions['onBrokenMarkdownLinks'],
|
|
48
|
+
): OnBrokenMarkdownLinksFunction {
|
|
49
|
+
if (typeof onBrokenMarkdownLinks === 'string') {
|
|
50
|
+
const extraHelp =
|
|
51
|
+
onBrokenMarkdownLinks === 'throw'
|
|
52
|
+
? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownLinks'} option, or apply the code=${'pathname://'} protocol to the broken link URLs.`
|
|
53
|
+
: '';
|
|
54
|
+
return ({sourceFilePath, url: linkUrl, node}) => {
|
|
55
|
+
const relativePath = toMessageRelativeFilePath(sourceFilePath);
|
|
56
|
+
logger.report(
|
|
57
|
+
onBrokenMarkdownLinks,
|
|
58
|
+
)`Markdown link with URL code=${linkUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
|
|
59
|
+
node,
|
|
60
|
+
)} couldn't be resolved.
|
|
61
|
+
Make sure it references a local Markdown file that exists within the current plugin.${extraHelp}`;
|
|
62
|
+
};
|
|
63
|
+
} else {
|
|
64
|
+
return (params) =>
|
|
65
|
+
onBrokenMarkdownLinks({
|
|
66
|
+
...params,
|
|
67
|
+
sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const HAS_MARKDOWN_EXTENSION = /\.mdx?$/i;
|
|
73
|
+
|
|
74
|
+
function parseMarkdownLinkURLPath(link: string): URLPath | null {
|
|
75
|
+
const urlPath = parseLocalURLPath(link);
|
|
76
|
+
|
|
77
|
+
// If it's not local, we don't resolve it even if it's a Markdown file
|
|
78
|
+
// Example, we don't resolve https://github.com/project/README.md
|
|
79
|
+
if (!urlPath) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Ignore links without a Markdown file extension (ignoring qs/hash)
|
|
84
|
+
if (!HAS_MARKDOWN_EXTENSION.test(urlPath.pathname)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return urlPath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A remark plugin to extract the h1 heading found in Markdown files
|
|
92
|
+
* This is exposed as "data.contentTitle" to the processed vfile
|
|
93
|
+
* Also gives the ability to strip that content title (used for the blog plugin)
|
|
94
|
+
*/
|
|
95
|
+
// TODO merge this plugin with "transformLinks"
|
|
96
|
+
// in general we'd want to avoid traversing multiple times the same AST
|
|
97
|
+
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
|
98
|
+
options,
|
|
99
|
+
): Transformer<Root> {
|
|
100
|
+
const {resolveMarkdownLink} = options;
|
|
101
|
+
|
|
102
|
+
const onBrokenMarkdownLinks = asFunction(options.onBrokenMarkdownLinks);
|
|
103
|
+
|
|
104
|
+
return (root, file) => {
|
|
105
|
+
visit(root, ['link', 'definition'], (node) => {
|
|
106
|
+
const link = node as unknown as Link | Definition;
|
|
107
|
+
const linkURLPath = parseMarkdownLinkURLPath(link.url);
|
|
108
|
+
if (!linkURLPath) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const sourceFilePath = file.path;
|
|
113
|
+
|
|
114
|
+
const permalink = resolveMarkdownLink({
|
|
115
|
+
sourceFilePath,
|
|
116
|
+
linkPathname: linkURLPath.pathname,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (permalink) {
|
|
120
|
+
// This reapplies the link ?qs#hash part to the resolved pathname
|
|
121
|
+
link.url = serializeURLPath({
|
|
122
|
+
...linkURLPath,
|
|
123
|
+
pathname: permalink,
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
link.url =
|
|
127
|
+
onBrokenMarkdownLinks({
|
|
128
|
+
url: link.url,
|
|
129
|
+
sourceFilePath,
|
|
130
|
+
node: link,
|
|
131
|
+
}) ?? link.url;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export default plugin;
|