@docusaurus/mdx-loader 2.4.1 → 3.0.0-beta.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 (85) hide show
  1. package/README.md +13 -0
  2. package/lib/format.d.ts +14 -0
  3. package/lib/format.d.ts.map +1 -0
  4. package/lib/format.js +42 -0
  5. package/lib/format.js.map +1 -0
  6. package/lib/frontMatter.d.ts +13 -0
  7. package/lib/frontMatter.d.ts.map +1 -0
  8. package/lib/frontMatter.js +23 -0
  9. package/lib/frontMatter.js.map +1 -0
  10. package/lib/index.d.ts +7 -7
  11. package/lib/index.d.ts.map +1 -1
  12. package/lib/index.js.map +1 -1
  13. package/lib/loader.d.ts +5 -14
  14. package/lib/loader.d.ts.map +1 -1
  15. package/lib/loader.js +45 -81
  16. package/lib/loader.js.map +1 -1
  17. package/lib/preprocessor.d.ts +18 -0
  18. package/lib/preprocessor.d.ts.map +1 -0
  19. package/lib/preprocessor.js +34 -0
  20. package/lib/preprocessor.js.map +1 -0
  21. package/lib/processor.d.ts +36 -0
  22. package/lib/processor.d.ts.map +1 -0
  23. package/lib/processor.js +160 -0
  24. package/lib/processor.js.map +1 -0
  25. package/lib/remark/admonitions/index.d.ts +4 -9
  26. package/lib/remark/admonitions/index.d.ts.map +1 -1
  27. package/lib/remark/admonitions/index.js +70 -128
  28. package/lib/remark/admonitions/index.js.map +1 -1
  29. package/lib/remark/details/index.d.ts +9 -0
  30. package/lib/remark/details/index.d.ts.map +1 -0
  31. package/lib/remark/details/index.js +23 -0
  32. package/lib/remark/details/index.js.map +1 -0
  33. package/lib/remark/head/index.d.ts +9 -0
  34. package/lib/remark/head/index.d.ts.map +1 -0
  35. package/lib/remark/head/index.js +23 -0
  36. package/lib/remark/head/index.js.map +1 -0
  37. package/lib/remark/headings/index.d.ts.map +1 -1
  38. package/lib/remark/headings/index.js +3 -3
  39. package/lib/remark/headings/index.js.map +1 -1
  40. package/lib/remark/{unwrapMdxCodeBlocks/index.d.ts → mdx1Compat/codeCompatPlugin.d.ts} +2 -2
  41. package/lib/remark/mdx1Compat/codeCompatPlugin.d.ts.map +1 -0
  42. package/lib/remark/mdx1Compat/codeCompatPlugin.js +34 -0
  43. package/lib/remark/mdx1Compat/codeCompatPlugin.js.map +1 -0
  44. package/lib/remark/mermaid/index.d.ts.map +1 -1
  45. package/lib/remark/mermaid/index.js +1 -0
  46. package/lib/remark/mermaid/index.js.map +1 -1
  47. package/lib/remark/toc/index.d.ts +8 -2
  48. package/lib/remark/toc/index.d.ts.map +1 -1
  49. package/lib/remark/toc/index.js +53 -23
  50. package/lib/remark/toc/index.js.map +1 -1
  51. package/lib/remark/transformImage/index.d.ts +2 -1
  52. package/lib/remark/transformImage/index.d.ts.map +1 -1
  53. package/lib/remark/transformImage/index.js +42 -15
  54. package/lib/remark/transformImage/index.js.map +1 -1
  55. package/lib/remark/transformLinks/index.d.ts +2 -1
  56. package/lib/remark/transformLinks/index.d.ts.map +1 -1
  57. package/lib/remark/transformLinks/index.js +30 -11
  58. package/lib/remark/transformLinks/index.js.map +1 -1
  59. package/lib/remark/utils/index.d.ts +4 -2
  60. package/lib/remark/utils/index.d.ts.map +1 -1
  61. package/lib/remark/utils/index.js +82 -11
  62. package/lib/remark/utils/index.js.map +1 -1
  63. package/package.json +28 -20
  64. package/src/format.ts +50 -0
  65. package/src/frontMatter.ts +31 -0
  66. package/src/index.ts +10 -6
  67. package/src/loader.ts +72 -107
  68. package/src/preprocessor.ts +47 -0
  69. package/src/processor.ts +243 -0
  70. package/src/remark/admonitions/README.md +1 -1
  71. package/src/remark/admonitions/index.ts +92 -171
  72. package/src/remark/details/index.ts +25 -0
  73. package/src/remark/head/index.ts +25 -0
  74. package/src/remark/headings/index.ts +5 -3
  75. package/src/remark/mdx1Compat/codeCompatPlugin.ts +36 -0
  76. package/src/remark/mermaid/index.ts +2 -0
  77. package/src/remark/toc/index.ts +80 -26
  78. package/src/remark/transformImage/index.ts +54 -19
  79. package/src/remark/transformLinks/index.ts +44 -17
  80. package/src/remark/utils/index.ts +108 -9
  81. package/lib/remark/unwrapMdxCodeBlocks/index.d.ts.map +0 -1
  82. package/lib/remark/unwrapMdxCodeBlocks/index.js +0 -29
  83. package/lib/remark/unwrapMdxCodeBlocks/index.js.map +0 -1
  84. package/src/deps.d.ts +0 -31
  85. package/src/remark/unwrapMdxCodeBlocks/index.ts +0 -33
@@ -0,0 +1,243 @@
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 emoji from 'remark-emoji';
9
+ import headings from './remark/headings';
10
+ import toc from './remark/toc';
11
+ import transformImage from './remark/transformImage';
12
+ import transformLinks from './remark/transformLinks';
13
+ import details from './remark/details';
14
+ import head from './remark/head';
15
+ import mermaid from './remark/mermaid';
16
+ import transformAdmonitions from './remark/admonitions';
17
+ import codeCompatPlugin from './remark/mdx1Compat/codeCompatPlugin';
18
+ import {getFormat} from './format';
19
+ import type {MDXFrontMatter} from './frontMatter';
20
+ import type {Options} from './loader';
21
+ import type {AdmonitionOptions} from './remark/admonitions';
22
+
23
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
24
+ import type {ProcessorOptions} from '@mdx-js/mdx';
25
+
26
+ // TODO as of April 2023, no way to import/re-export this ESM type easily :/
27
+ // This might change soon, likely after TS 5.2
28
+ // See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
29
+ type Pluggable = any; // TODO fix this asap
30
+
31
+ // TODO alt interface because impossible to import type Processor (ESM + TS :/)
32
+ type SimpleProcessor = {
33
+ process: ({
34
+ content,
35
+ filePath,
36
+ }: {
37
+ content: string;
38
+ filePath: string;
39
+ }) => Promise<string>;
40
+ };
41
+
42
+ const DEFAULT_OPTIONS: MDXOptions = {
43
+ admonitions: true,
44
+ rehypePlugins: [],
45
+ remarkPlugins: [headings, emoji, toc],
46
+ beforeDefaultRemarkPlugins: [],
47
+ beforeDefaultRehypePlugins: [],
48
+ };
49
+
50
+ export type MDXPlugin = Pluggable;
51
+
52
+ export type MDXOptions = {
53
+ admonitions: boolean | Partial<AdmonitionOptions>;
54
+ remarkPlugins: MDXPlugin[];
55
+ rehypePlugins: MDXPlugin[];
56
+ beforeDefaultRemarkPlugins: MDXPlugin[];
57
+ beforeDefaultRehypePlugins: MDXPlugin[];
58
+ };
59
+
60
+ function getAdmonitionsPlugins(
61
+ admonitionsOption: MDXOptions['admonitions'],
62
+ ): MDXPlugin[] {
63
+ if (admonitionsOption) {
64
+ const plugin: MDXPlugin =
65
+ admonitionsOption === true
66
+ ? transformAdmonitions
67
+ : [transformAdmonitions, admonitionsOption];
68
+ return [plugin];
69
+ }
70
+
71
+ return [];
72
+ }
73
+
74
+ // Need to be async due to ESM dynamic imports...
75
+ async function createProcessorFactory() {
76
+ const {createProcessor: createMdxProcessor} = await import('@mdx-js/mdx');
77
+ const {default: rehypeRaw} = await import('rehype-raw');
78
+ const {default: gfm} = await import('remark-gfm');
79
+ // TODO using fork until PR merged: https://github.com/leebyron/remark-comment/pull/3
80
+ const {default: comment} = await import('@slorber/remark-comment');
81
+ const {default: directive} = await import('remark-directive');
82
+
83
+ // /!\ this method is synchronous on purpose
84
+ // Using async code here can create cache entry race conditions!
85
+ function createProcessorSync({
86
+ options,
87
+ format,
88
+ }: {
89
+ options: Options;
90
+ format: 'md' | 'mdx';
91
+ }): SimpleProcessor {
92
+ const remarkPlugins: MDXPlugin[] = [
93
+ ...(options.beforeDefaultRemarkPlugins ?? []),
94
+ directive,
95
+ ...getAdmonitionsPlugins(options.admonitions ?? false),
96
+ ...DEFAULT_OPTIONS.remarkPlugins,
97
+ details,
98
+ head,
99
+ ...(options.markdownConfig.mermaid ? [mermaid] : []),
100
+ [
101
+ transformImage,
102
+ {
103
+ staticDirs: options.staticDirs,
104
+ siteDir: options.siteDir,
105
+ },
106
+ ],
107
+ [
108
+ transformLinks,
109
+ {
110
+ staticDirs: options.staticDirs,
111
+ siteDir: options.siteDir,
112
+ },
113
+ ],
114
+ gfm,
115
+ options.markdownConfig.mdx1Compat.comments ? comment : null,
116
+ ...(options.remarkPlugins ?? []),
117
+ ].filter((plugin): plugin is MDXPlugin => Boolean(plugin));
118
+
119
+ // codeCompatPlugin needs to be applied last after user-provided plugins
120
+ // (after npm2yarn for example)
121
+ remarkPlugins.push(codeCompatPlugin);
122
+
123
+ const rehypePlugins: MDXPlugin[] = [
124
+ ...(options.beforeDefaultRehypePlugins ?? []),
125
+ ...DEFAULT_OPTIONS.rehypePlugins,
126
+ ...(options.rehypePlugins ?? []),
127
+ ];
128
+
129
+ if (format === 'md') {
130
+ // This is what permits to embed HTML elements with format 'md'
131
+ // See https://github.com/facebook/docusaurus/pull/8960
132
+ // See https://github.com/mdx-js/mdx/pull/2295#issuecomment-1540085960
133
+ const rehypeRawPlugin: MDXPlugin = [
134
+ rehypeRaw,
135
+ {
136
+ passThrough: [
137
+ 'mdxFlowExpression',
138
+ 'mdxJsxFlowElement',
139
+ 'mdxJsxTextElement',
140
+ 'mdxTextExpression',
141
+ 'mdxjsEsm',
142
+ ],
143
+ },
144
+ ];
145
+ rehypePlugins.unshift(rehypeRawPlugin);
146
+ }
147
+
148
+ const processorOptions: ProcessorOptions & Options = {
149
+ ...options,
150
+ remarkPlugins,
151
+ rehypePlugins,
152
+ providerImportSource: '@mdx-js/react',
153
+ };
154
+
155
+ const mdxProcessor = createMdxProcessor({
156
+ ...processorOptions,
157
+ format,
158
+ });
159
+
160
+ return {
161
+ process: async ({content, filePath}) =>
162
+ mdxProcessor
163
+ .process({
164
+ value: content,
165
+ path: filePath,
166
+ })
167
+ .then((res) => res.toString()),
168
+ };
169
+ }
170
+
171
+ return {createProcessorSync};
172
+ }
173
+
174
+ // Will be useful for tests
175
+ export async function createProcessorUncached(parameters: {
176
+ options: Options;
177
+ format: 'md' | 'mdx';
178
+ }): Promise<SimpleProcessor> {
179
+ const {createProcessorSync} = await createProcessorFactory();
180
+ return createProcessorSync(parameters);
181
+ }
182
+
183
+ // We use different compilers depending on the file type (md vs mdx)
184
+ type ProcessorsCacheEntry = {
185
+ mdProcessor: SimpleProcessor;
186
+ mdxProcessor: SimpleProcessor;
187
+ };
188
+
189
+ // Compilers are cached so that Remark/Rehype plugins can run
190
+ // expensive code during initialization
191
+ const ProcessorsCache = new Map<string | Options, ProcessorsCacheEntry>();
192
+
193
+ async function createProcessorsCacheEntry({
194
+ query,
195
+ reqOptions,
196
+ }: {
197
+ query: string | Options;
198
+ reqOptions: Options;
199
+ }): Promise<ProcessorsCacheEntry> {
200
+ const {createProcessorSync} = await createProcessorFactory();
201
+
202
+ const compilers = ProcessorsCache.get(query);
203
+ if (compilers) {
204
+ return compilers;
205
+ }
206
+
207
+ const compilerCacheEntry: ProcessorsCacheEntry = {
208
+ mdProcessor: createProcessorSync({
209
+ options: reqOptions,
210
+ format: 'md',
211
+ }),
212
+ mdxProcessor: createProcessorSync({
213
+ options: reqOptions,
214
+ format: 'mdx',
215
+ }),
216
+ };
217
+
218
+ ProcessorsCache.set(query, compilerCacheEntry);
219
+
220
+ return compilerCacheEntry;
221
+ }
222
+
223
+ export async function createProcessorCached({
224
+ filePath,
225
+ mdxFrontMatter,
226
+ query,
227
+ reqOptions,
228
+ }: {
229
+ filePath: string;
230
+ mdxFrontMatter: MDXFrontMatter;
231
+ query: string | Options;
232
+ reqOptions: Options;
233
+ }): Promise<SimpleProcessor> {
234
+ const compilers = await createProcessorsCacheEntry({query, reqOptions});
235
+
236
+ const format = getFormat({
237
+ filePath,
238
+ frontMatterFormat: mdxFrontMatter.format,
239
+ markdownConfigFormat: reqOptions.markdownConfig.format,
240
+ });
241
+
242
+ return format === 'md' ? compilers.mdProcessor : compilers.mdxProcessor;
243
+ }
@@ -1,3 +1,3 @@
1
1
  # Docusaurus admonitions
2
2
 
3
- Code from [remark-admonitions](https://github.com/remarkjs/remark-directive) (MIT license) has been copied to this folder, and highly customized for Docusaurus needs.
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.
@@ -4,20 +4,26 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
-
8
7
  import visit from 'unist-util-visit';
9
- import type {Transformer, Processor, Plugin} from 'unified';
10
- import type {Literal} from 'mdast';
8
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
9
+ import type {Transformer, Processor} from 'unified';
10
+
11
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
12
+ import type {ContainerDirective} from 'mdast-util-directive';
13
+ import type {Parent} from 'mdast';
11
14
 
12
- const NEWLINE = '\n';
15
+ // TODO as of April 2023, no way to import/re-export this ESM type easily :/
16
+ // This might change soon, likely after TS 5.2
17
+ // See https://github.com/microsoft/TypeScript/issues/49721#issuecomment-1517839391
18
+ // import type {Plugin} from 'unified';
19
+ type Plugin = any; // TODO fix this asap
13
20
 
14
21
  export type AdmonitionOptions = {
15
- tag: string;
16
22
  keywords: string[];
23
+ extendDefaults: boolean;
17
24
  };
18
25
 
19
26
  export const DefaultAdmonitionOptions: AdmonitionOptions = {
20
- tag: ':::',
21
27
  keywords: [
22
28
  'secondary',
23
29
  'info',
@@ -29,193 +35,108 @@ export const DefaultAdmonitionOptions: AdmonitionOptions = {
29
35
  'important',
30
36
  'caution',
31
37
  ],
38
+ extendDefaults: true,
32
39
  };
33
40
 
34
- function escapeRegExp(s: string): string {
35
- return s.replace(/[-[\]{}()*+?.\\^$|/]/g, '\\$&');
41
+ export function normalizeAdmonitionOptions(
42
+ providedOptions: Partial<AdmonitionOptions> | true,
43
+ ): AdmonitionOptions {
44
+ if (providedOptions === true) {
45
+ return DefaultAdmonitionOptions;
46
+ }
47
+
48
+ const options = {...DefaultAdmonitionOptions, ...providedOptions};
49
+
50
+ // By default it makes more sense to append keywords to the default ones
51
+ // Adding custom keywords is more common than disabling existing ones
52
+ if (options.extendDefaults) {
53
+ options.keywords = [
54
+ ...DefaultAdmonitionOptions.keywords,
55
+ ...options.keywords,
56
+ ];
57
+ }
58
+
59
+ return options;
36
60
  }
37
61
 
38
- function normalizeOptions(
39
- options: Partial<AdmonitionOptions>,
40
- ): AdmonitionOptions {
41
- return {...DefaultAdmonitionOptions, ...options};
62
+ type DirectiveLabel = Parent;
63
+ type DirectiveContent = ContainerDirective['children'];
64
+
65
+ function parseDirective(directive: ContainerDirective): {
66
+ directiveLabel: DirectiveLabel | undefined;
67
+ contentNodes: DirectiveContent;
68
+ } {
69
+ const hasDirectiveLabel =
70
+ directive.children?.[0]?.data?.directiveLabel === true;
71
+ if (hasDirectiveLabel) {
72
+ const [directiveLabel, ...contentNodes] = directive.children;
73
+ return {directiveLabel: directiveLabel as DirectiveLabel, contentNodes};
74
+ }
75
+ return {directiveLabel: undefined, contentNodes: directive.children};
42
76
  }
43
77
 
44
- // This string value does not matter much
45
- // It is ignored because nodes are using hName/hProperties coming from HAST
46
- const admonitionNodeType = 'admonitionHTML';
78
+ function getTextOnlyTitle(directiveLabel: DirectiveLabel): string | undefined {
79
+ const isTextOnlyTitle =
80
+ directiveLabel?.children?.length === 1 &&
81
+ directiveLabel?.children?.[0]?.type === 'text';
82
+ return isTextOnlyTitle
83
+ ? // @ts-expect-error: todo type
84
+ (directiveLabel?.children?.[0].value as string)
85
+ : undefined;
86
+ }
47
87
 
48
88
  const plugin: Plugin = function plugin(
49
89
  this: Processor,
50
90
  optionsInput: Partial<AdmonitionOptions> = {},
51
91
  ): Transformer {
52
- const options = normalizeOptions(optionsInput);
53
-
54
- const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
55
- const nestingChar = escapeRegExp(options.tag.slice(0, 1));
56
- const tag = escapeRegExp(options.tag);
57
-
58
- // resolve th nesting level of an opening tag
59
- // ::: -> 0, :::: -> 1, ::::: -> 2 ...
60
- const nestingLevelRegex = new RegExp(
61
- `^${tag}(?<nestingLevel>${nestingChar}*)`,
62
- );
63
-
64
- const regex = new RegExp(`${tag}${nestingChar}*(${keywords})(?: *(.*))?\n`);
65
- const escapeTag = new RegExp(
66
- escapeRegExp(`\\${options.tag}${options.tag.slice(0, 1)}*`),
67
- 'g',
68
- );
69
-
70
- // The tokenizer is called on blocks to determine if there is an admonition
71
- // present and create tags for it
72
- function blockTokenizer(this: any, eat: any, value: string, silent: boolean) {
73
- // Stop if no match or match does not start at beginning of line
74
- const match = regex.exec(value);
75
- if (!match || match.index !== 0) {
76
- return false;
77
- }
78
- // If silent return the match
79
- if (silent) {
80
- return true;
81
- }
82
-
83
- const now = eat.now();
84
- const [opening, keyword, title] = match as string[] as [
85
- string,
86
- string,
87
- string,
88
- ];
89
- const food = [];
90
- const content = [];
91
- // get the nesting level of the opening tag
92
- const openingLevel =
93
- nestingLevelRegex.exec(opening)!.groups!.nestingLevel!.length;
94
- // used as a stack to keep track of nested admonitions
95
- const nestingLevels: number[] = [openingLevel];
96
-
97
- let newValue = value;
98
- // consume lines until a closing tag
99
- let idx = newValue.indexOf(NEWLINE);
100
- while (idx !== -1) {
101
- // grab this line and eat it
102
- const next = newValue.indexOf(NEWLINE, idx + 1);
103
- const line =
104
- next !== -1 ? newValue.slice(idx + 1, next) : newValue.slice(idx + 1);
105
- food.push(line);
106
- newValue = newValue.slice(idx + 1);
107
- const nesting = nestingLevelRegex.exec(line);
108
- idx = newValue.indexOf(NEWLINE);
109
- if (!nesting) {
110
- content.push(line);
111
- continue;
112
- }
113
- const tagLevel = nesting.groups!.nestingLevel!.length;
114
- // first level
115
- if (nestingLevels.length === 0) {
116
- nestingLevels.push(tagLevel);
117
- content.push(line);
118
- continue;
119
- }
120
- const currentLevel = nestingLevels[nestingLevels.length - 1]!;
121
- if (tagLevel < currentLevel) {
122
- // entering a nested admonition block
123
- nestingLevels.push(tagLevel);
124
- } else if (tagLevel === currentLevel) {
125
- // closing a nested admonition block
126
- nestingLevels.pop();
127
- // the closing tag is NOT part of the content
128
- if (nestingLevels.length === 0) {
129
- break;
130
- }
131
- }
132
- content.push(line);
133
- }
92
+ const {keywords} = normalizeAdmonitionOptions(optionsInput);
134
93
 
135
- // consume the processed tag and replace escape sequences
136
- const contentString = content.join(NEWLINE).replace(escapeTag, options.tag);
137
- const add = eat(opening + food.join(NEWLINE));
94
+ return async (root) => {
95
+ visit(root, (node) => {
96
+ if (node.type === 'containerDirective') {
97
+ const directive = node as ContainerDirective;
98
+ const isAdmonition = keywords.includes(directive.name);
138
99
 
139
- // parse the content in block mode
140
- const exit = this.enterBlock();
141
- const contentNodes = this.tokenizeBlock(contentString, now);
142
- exit();
100
+ if (!isAdmonition) {
101
+ return;
102
+ }
143
103
 
144
- const titleNodes = this.tokenizeInline(title, now);
104
+ const {directiveLabel, contentNodes} = parseDirective(directive);
145
105
 
146
- const isSimpleTextTitle =
147
- titleNodes.length === 1 && titleNodes[0].type === 'text';
106
+ const textOnlyTitle =
107
+ directive.attributes?.title ??
108
+ (directiveLabel ? getTextOnlyTitle(directiveLabel) : undefined);
148
109
 
149
- const element = {
150
- type: admonitionNodeType,
151
- data: {
152
- // hName/hProperties come from HAST
110
+ // Transform the mdast directive node to a hast admonition node
153
111
  // See https://github.com/syntax-tree/mdast-util-to-hast#fields-on-nodes
154
- hName: 'admonition',
155
- hProperties: {
156
- ...(title && isSimpleTextTitle && {title}),
157
- type: keyword,
158
- },
159
- },
160
- children: [
161
- // For titles containing MDX syntax: create a custom element. The theme
162
- // component will extract it and render it nicely.
163
- //
164
- // Temporary workaround, because it's complex in MDX v1 to emit
165
- // interpolated JSX prop syntax (title={<>my <code>title</code></>}).
166
- // For this reason, we use children instead of the title prop.
167
- title &&
168
- !isSimpleTextTitle && {
169
- type: admonitionNodeType,
112
+ // TODO in MDX v2 we should transform the whole directive to
113
+ // mdxJsxFlowElement instead of using hast
114
+ directive.data = {
115
+ hName: 'admonition',
116
+ hProperties: {
117
+ ...(textOnlyTitle && {title: textOnlyTitle}),
118
+ type: directive.name,
119
+ },
120
+ };
121
+ directive.children = contentNodes;
122
+
123
+ // TODO legacy MDX v1 <mdxAdmonitionTitle> workaround
124
+ // v1: not possible to inject complex JSX elements as props
125
+ // v2: now possible: use a mdxJsxFlowElement element
126
+ if (directiveLabel && !textOnlyTitle) {
127
+ const complexTitleNode = {
128
+ type: 'mdxAdmonitionTitle',
170
129
  data: {
171
130
  hName: 'mdxAdmonitionTitle',
172
131
  hProperties: {},
173
132
  },
174
- children: titleNodes,
175
- },
176
- ...contentNodes,
177
- ].filter(Boolean),
178
- };
179
-
180
- return add(element);
181
- }
182
-
183
- // add tokenizer to parser after fenced code blocks
184
- const Parser = this.Parser.prototype;
185
- Parser.blockTokenizers.admonition = blockTokenizer;
186
- Parser.blockMethods.splice(
187
- Parser.blockMethods.indexOf('fencedCode') + 1,
188
- 0,
189
- 'admonition',
190
- );
191
- Parser.interruptParagraph.splice(
192
- Parser.interruptParagraph.indexOf('fencedCode') + 1,
193
- 0,
194
- ['admonition'],
195
- );
196
- Parser.interruptList.splice(
197
- Parser.interruptList.indexOf('fencedCode') + 1,
198
- 0,
199
- ['admonition'],
200
- );
201
- Parser.interruptBlockquote.splice(
202
- Parser.interruptBlockquote.indexOf('fencedCode') + 1,
203
- 0,
204
- ['admonition'],
205
- );
206
-
207
- return (root) => {
208
- // escape everything except admonitionHTML nodes
209
- visit(
210
- root,
211
- (node: unknown): node is Literal =>
212
- (node as Literal | undefined)?.type !== admonitionNodeType,
213
- (node: Literal) => {
214
- if (node.value) {
215
- node.value = node.value.replace(escapeTag, options.tag);
133
+ children: directiveLabel.children,
134
+ };
135
+ // @ts-expect-error: invented node type
136
+ directive.children.unshift(complexTitleNode);
216
137
  }
217
- },
218
- );
138
+ }
139
+ });
219
140
  };
220
141
  };
221
142
 
@@ -0,0 +1,25 @@
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
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
10
+ import type {Transformer} from 'unified';
11
+
12
+ // @ts-expect-error: ES support...
13
+ import type {MdxJsxFlowElement} from 'mdast-util-mdx';
14
+
15
+ // Transform <details> to <Details>
16
+ // MDX 2 doesn't allow to substitute html elements with the provider anymore
17
+ export default function plugin(): Transformer {
18
+ return (root) => {
19
+ visit(root, 'mdxJsxFlowElement', (node: MdxJsxFlowElement) => {
20
+ if (node.name === 'details') {
21
+ node.name = 'Details';
22
+ }
23
+ });
24
+ };
25
+ }
@@ -0,0 +1,25 @@
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
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
10
+ import type {Transformer} from 'unified';
11
+
12
+ // @ts-expect-error: ES support...
13
+ import type {MdxJsxFlowElement} from 'mdast-util-mdx';
14
+
15
+ // Transform <head> to <Head>
16
+ // MDX 2 doesn't allow to substitute html elements with the provider anymore
17
+ export default function plugin(): Transformer {
18
+ return (root) => {
19
+ visit(root, 'mdxJsxFlowElement', (node: MdxJsxFlowElement) => {
20
+ if (node.name === 'head') {
21
+ node.name = 'Head';
22
+ }
23
+ });
24
+ };
25
+ }
@@ -9,12 +9,14 @@
9
9
 
10
10
  import {parseMarkdownHeadingId, createSlugger} from '@docusaurus/utils';
11
11
  import visit from 'unist-util-visit';
12
- import mdastToString from 'mdast-util-to-string';
12
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
13
13
  import type {Transformer} from 'unified';
14
14
  import type {Heading, Text} from 'mdast';
15
15
 
16
16
  export default function plugin(): Transformer {
17
- return (root) => {
17
+ return async (root) => {
18
+ const {toString} = await import('mdast-util-to-string');
19
+
18
20
  const slugs = createSlugger();
19
21
  visit(root, 'heading', (headingNode: Heading) => {
20
22
  const data = headingNode.data ?? (headingNode.data = {});
@@ -29,7 +31,7 @@ export default function plugin(): Transformer {
29
31
  const headingTextNodes = headingNode.children.filter(
30
32
  ({type}) => !['html', 'jsx'].includes(type),
31
33
  );
32
- const heading = mdastToString(
34
+ const heading = toString(
33
35
  headingTextNodes.length > 0 ? headingTextNodes : headingNode,
34
36
  );
35
37
 
@@ -0,0 +1,36 @@
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
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
10
+ import type {Transformer, Processor} from 'unified';
11
+ import type {Code} from 'mdast';
12
+
13
+ // Solution inspired by https://github.com/pomber/docusaurus-mdx-2/blob/main/packages/mdx-loader/src/remark/codeCompat/index.ts
14
+ // TODO after MDX 2 we probably don't need this - remove soon?
15
+ // Only fenced code blocks are swapped by pre/code MDX components
16
+ // Using <pre><code> in JSX shouldn't use our MDX components anymore
17
+
18
+ // To make theme-classic/src/theme/MDXComponents/Pre work
19
+ // we need to fill two properties that mdx v2 doesn't provide anymore
20
+ export default function codeCompatPlugin(this: Processor): Transformer {
21
+ return (root) => {
22
+ visit(root, 'code', (node: Code) => {
23
+ node.data = node.data || {};
24
+ node.data.hProperties = node.data.hProperties || {};
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ (node.data.hProperties as any).metastring = node.meta;
27
+
28
+ // Retrocompatible support for live codeblock metastring
29
+ // Not really the appropriate place to handle that :s
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ (node.data.hProperties as any).live = node.meta
32
+ ?.split(' ')
33
+ .includes('live');
34
+ });
35
+ };
36
+ }
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import visit from 'unist-util-visit';
9
+ // @ts-expect-error: TODO see https://github.com/microsoft/TypeScript/issues/49721
9
10
  import type {Transformer} from 'unified';
10
11
  import type {Code} from 'mdast';
11
12
 
@@ -17,6 +18,7 @@ export default function plugin(): Transformer {
17
18
  return (root) => {
18
19
  visit(root, 'code', (node: Code, index, parent) => {
19
20
  if (node.lang === 'mermaid') {
21
+ // TODO migrate to mdxJsxFlowElement? cf admonitions
20
22
  parent!.children.splice(index, 1, {
21
23
  type: 'mermaidCodeBlock',
22
24
  data: {