@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.
Files changed (130) hide show
  1. package/README.md +57 -2
  2. package/lib/createMDXLoader.d.ts +14 -0
  3. package/lib/createMDXLoader.d.ts.map +1 -0
  4. package/lib/createMDXLoader.js +52 -0
  5. package/lib/createMDXLoader.js.map +1 -0
  6. package/lib/format.d.ts +14 -0
  7. package/lib/format.d.ts.map +1 -0
  8. package/lib/format.js +41 -0
  9. package/lib/format.js.map +1 -0
  10. package/lib/frontMatter.d.ts +13 -0
  11. package/lib/frontMatter.d.ts.map +1 -0
  12. package/lib/frontMatter.js +23 -0
  13. package/lib/frontMatter.js.map +1 -0
  14. package/lib/index.d.ts +35 -0
  15. package/lib/index.d.ts.map +1 -0
  16. package/lib/index.js +15 -0
  17. package/lib/index.js.map +1 -0
  18. package/lib/loader.d.ts +11 -0
  19. package/lib/loader.d.ts.map +1 -0
  20. package/lib/loader.js +170 -0
  21. package/lib/loader.js.map +1 -0
  22. package/lib/options.d.ts +35 -0
  23. package/lib/options.d.ts.map +1 -0
  24. package/lib/options.js +9 -0
  25. package/lib/options.js.map +1 -0
  26. package/lib/preprocessor.d.ts +18 -0
  27. package/lib/preprocessor.d.ts.map +1 -0
  28. package/lib/preprocessor.js +34 -0
  29. package/lib/preprocessor.js.map +1 -0
  30. package/lib/processor.d.ts +49 -0
  31. package/lib/processor.d.ts.map +1 -0
  32. package/lib/processor.js +209 -0
  33. package/lib/processor.js.map +1 -0
  34. package/lib/remark/admonitions/index.d.ts +11 -0
  35. package/lib/remark/admonitions/index.d.ts.map +1 -0
  36. package/lib/remark/admonitions/index.js +107 -0
  37. package/lib/remark/admonitions/index.js.map +1 -0
  38. package/lib/remark/contentTitle/index.d.ts +13 -0
  39. package/lib/remark/contentTitle/index.d.ts.map +1 -0
  40. package/lib/remark/contentTitle/index.js +62 -0
  41. package/lib/remark/contentTitle/index.js.map +1 -0
  42. package/lib/remark/details/index.d.ts +10 -0
  43. package/lib/remark/details/index.d.ts.map +1 -0
  44. package/lib/remark/details/index.js +22 -0
  45. package/lib/remark/details/index.js.map +1 -0
  46. package/lib/remark/head/index.d.ts +10 -0
  47. package/lib/remark/head/index.d.ts.map +1 -0
  48. package/lib/remark/head/index.js +22 -0
  49. package/lib/remark/head/index.js.map +1 -0
  50. package/lib/remark/headings/index.d.ts +14 -0
  51. package/lib/remark/headings/index.d.ts.map +1 -0
  52. package/lib/remark/headings/index.js +126 -0
  53. package/lib/remark/headings/index.js.map +1 -0
  54. package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts +11 -0
  55. package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts.map +1 -0
  56. package/lib/remark/mdx1Compat/codeCompatPlugin.js +30 -0
  57. package/lib/remark/mdx1Compat/codeCompatPlugin.js.map +1 -0
  58. package/lib/remark/mermaid/index.d.ts +10 -0
  59. package/lib/remark/mermaid/index.d.ts.map +1 -0
  60. package/lib/remark/mermaid/index.js +34 -0
  61. package/lib/remark/mermaid/index.js.map +1 -0
  62. package/lib/remark/resolveMarkdownLinks/index.d.ts +33 -0
  63. package/lib/remark/resolveMarkdownLinks/index.d.ts.map +1 -0
  64. package/lib/remark/resolveMarkdownLinks/index.js +87 -0
  65. package/lib/remark/resolveMarkdownLinks/index.js.map +1 -0
  66. package/lib/remark/toc/index.d.ts +14 -0
  67. package/lib/remark/toc/index.d.ts.map +1 -0
  68. package/lib/remark/toc/index.js +115 -0
  69. package/lib/remark/toc/index.js.map +1 -0
  70. package/lib/remark/toc/types.d.ts +22 -0
  71. package/lib/remark/toc/types.d.ts.map +1 -0
  72. package/lib/remark/toc/types.js +9 -0
  73. package/lib/remark/toc/types.js.map +1 -0
  74. package/lib/remark/toc/utils.d.ts +26 -0
  75. package/lib/remark/toc/utils.d.ts.map +1 -0
  76. package/lib/remark/toc/utils.js +184 -0
  77. package/lib/remark/toc/utils.js.map +1 -0
  78. package/lib/remark/transformImage/index.d.ts +17 -0
  79. package/lib/remark/transformImage/index.d.ts.map +1 -0
  80. package/lib/remark/transformImage/index.js +187 -0
  81. package/lib/remark/transformImage/index.js.map +1 -0
  82. package/lib/remark/transformLinks/index.d.ts +17 -0
  83. package/lib/remark/transformLinks/index.d.ts.map +1 -0
  84. package/lib/remark/transformLinks/index.js +205 -0
  85. package/lib/remark/transformLinks/index.js.map +1 -0
  86. package/lib/remark/unusedDirectives/index.d.ts +9 -0
  87. package/lib/remark/unusedDirectives/index.d.ts.map +1 -0
  88. package/lib/remark/unusedDirectives/index.js +125 -0
  89. package/lib/remark/unusedDirectives/index.js.map +1 -0
  90. package/lib/remark/utils/index.d.ts +18 -0
  91. package/lib/remark/utils/index.d.ts.map +1 -0
  92. package/lib/remark/utils/index.js +93 -0
  93. package/lib/remark/utils/index.js.map +1 -0
  94. package/lib/serializeJS.d.ts +17 -0
  95. package/lib/serializeJS.d.ts.map +1 -0
  96. package/lib/serializeJS.js +24 -0
  97. package/lib/serializeJS.js.map +1 -0
  98. package/lib/utils.d.ts +35 -0
  99. package/lib/utils.d.ts.map +1 -0
  100. package/lib/utils.js +97 -0
  101. package/lib/utils.js.map +1 -0
  102. package/package.json +65 -4
  103. package/src/createMDXLoader.ts +65 -0
  104. package/src/format.ts +50 -0
  105. package/src/frontMatter.ts +31 -0
  106. package/src/index.ts +44 -0
  107. package/src/loader.ts +227 -0
  108. package/src/options.ts +37 -0
  109. package/src/preprocessor.ts +47 -0
  110. package/src/processor.ts +306 -0
  111. package/src/remark/admonitions/LICENSE +45 -0
  112. package/src/remark/admonitions/README.md +3 -0
  113. package/src/remark/admonitions/index.ts +138 -0
  114. package/src/remark/contentTitle/index.ts +76 -0
  115. package/src/remark/details/index.ts +23 -0
  116. package/src/remark/head/index.ts +23 -0
  117. package/src/remark/headings/index.ts +158 -0
  118. package/src/remark/mdx1Compat/codeCompatPlugin.ts +35 -0
  119. package/src/remark/mermaid/index.ts +35 -0
  120. package/src/remark/resolveMarkdownLinks/index.ts +137 -0
  121. package/src/remark/toc/index.ts +183 -0
  122. package/src/remark/toc/types.ts +29 -0
  123. package/src/remark/toc/utils.ts +243 -0
  124. package/src/remark/transformImage/index.ts +268 -0
  125. package/src/remark/transformLinks/index.ts +291 -0
  126. package/src/remark/unusedDirectives/index.ts +171 -0
  127. package/src/remark/utils/index.ts +99 -0
  128. package/src/serializeJS.ts +22 -0
  129. package/src/types.d.mts +40 -0
  130. 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,3 @@
1
+ # Docusaurus admonitions
2
+
3
+ Code from [remark-admonitions](https://github.com/elviswolcott/remark-admonitions) (MIT license) has been copied to this folder, and highly customized for Docusaurus needs.
@@ -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;