mirror of
https://github.com/facebook/docusaurus.git
synced 2025-07-28 05:58:38 +02:00
fix(mdx-loader): support nested admonitions (#8303)
Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com> closes undefined
This commit is contained in:
parent
a4d935a6f1
commit
b016686c22
6 changed files with 133 additions and 7 deletions
10
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md
generated
Normal file
10
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md
generated
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
Test nested Admonitions
|
||||||
|
|
||||||
|
::::info **Weather**
|
||||||
|
On nice days, you can enjoy skiing in the mountains.
|
||||||
|
|
||||||
|
:::danger *Storms*
|
||||||
|
Take care of snowstorms...
|
||||||
|
:::
|
||||||
|
|
||||||
|
::::
|
|
@ -54,6 +54,11 @@ exports[`admonitions remark plugin interpolation 1`] = `
|
||||||
<admonition type="tip"><mdxAdmonitionTitle>My <code>interpolated</code> <strong>title</strong> <button style={{color: "red"}} onClick={() => alert("click")}>test</mdxAdmonitionTitle><p><code>body</code> <strong>interpolated</strong> content</p></admonition>"
|
<admonition type="tip"><mdxAdmonitionTitle>My <code>interpolated</code> <strong>title</strong> <button style={{color: "red"}} onClick={() => alert("click")}>test</mdxAdmonitionTitle><p><code>body</code> <strong>interpolated</strong> content</p></admonition>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`admonitions remark plugin nesting 1`] = `
|
||||||
|
"<p>Test nested Admonitions</p>
|
||||||
|
<admonition type="info"><mdxAdmonitionTitle><strong>Weather</strong></mdxAdmonitionTitle><p>On nice days, you can enjoy skiing in the mountains.</p><admonition type="danger"><mdxAdmonitionTitle><em>Storms</em></mdxAdmonitionTitle><p>Take care of snowstorms...</p></admonition></admonition>"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`admonitions remark plugin replace custom keyword 1`] = `
|
exports[`admonitions remark plugin replace custom keyword 1`] = `
|
||||||
"<p>The blog feature enables you to deploy in no time a full-featured blog.</p>
|
"<p>The blog feature enables you to deploy in no time a full-featured blog.</p>
|
||||||
<p>:::info Sample Title</p>
|
<p>:::info Sample Title</p>
|
||||||
|
|
|
@ -71,4 +71,9 @@ describe('admonitions remark plugin', () => {
|
||||||
const result = await processFixture('interpolation');
|
const result = await processFixture('interpolation');
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('nesting', async () => {
|
||||||
|
const result = await processFixture('nesting');
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -69,9 +69,20 @@ const plugin: Plugin = function plugin(
|
||||||
const options = normalizeOptions(optionsInput);
|
const options = normalizeOptions(optionsInput);
|
||||||
|
|
||||||
const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
|
const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
|
||||||
|
const nestingChar = escapeRegExp(options.tag.slice(0, 1));
|
||||||
const tag = escapeRegExp(options.tag);
|
const tag = escapeRegExp(options.tag);
|
||||||
const regex = new RegExp(`${tag}(${keywords})(?: *(.*))?\n`);
|
|
||||||
const escapeTag = new RegExp(escapeRegExp(`\\${options.tag}`), 'g');
|
// resolve th nesting level of an opening tag
|
||||||
|
// ::: -> 0, :::: -> 1, ::::: -> 2 ...
|
||||||
|
const nestingLevelRegex = new RegExp(
|
||||||
|
`^${tag}(?<nestingLevel>${nestingChar}*)`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const regex = new RegExp(`${tag}${nestingChar}*(${keywords})(?: *(.*))?\n`);
|
||||||
|
const escapeTag = new RegExp(
|
||||||
|
escapeRegExp(`\\${options.tag}${options.tag.slice(0, 1)}*`),
|
||||||
|
'g',
|
||||||
|
);
|
||||||
|
|
||||||
// The tokenizer is called on blocks to determine if there is an admonition
|
// The tokenizer is called on blocks to determine if there is an admonition
|
||||||
// present and create tags for it
|
// present and create tags for it
|
||||||
|
@ -94,6 +105,11 @@ const plugin: Plugin = function plugin(
|
||||||
];
|
];
|
||||||
const food = [];
|
const food = [];
|
||||||
const content = [];
|
const content = [];
|
||||||
|
// get the nesting level of the opening tag
|
||||||
|
const openingLevel =
|
||||||
|
nestingLevelRegex.exec(opening)!.groups!.nestingLevel!.length;
|
||||||
|
// used as a stack to keep track of nested admonitions
|
||||||
|
const nestingLevels: number[] = [openingLevel];
|
||||||
|
|
||||||
let newValue = value;
|
let newValue = value;
|
||||||
// consume lines until a closing tag
|
// consume lines until a closing tag
|
||||||
|
@ -105,12 +121,32 @@ const plugin: Plugin = function plugin(
|
||||||
next !== -1 ? newValue.slice(idx + 1, next) : newValue.slice(idx + 1);
|
next !== -1 ? newValue.slice(idx + 1, next) : newValue.slice(idx + 1);
|
||||||
food.push(line);
|
food.push(line);
|
||||||
newValue = newValue.slice(idx + 1);
|
newValue = newValue.slice(idx + 1);
|
||||||
// the closing tag is NOT part of the content
|
const nesting = nestingLevelRegex.exec(line);
|
||||||
if (line.startsWith(options.tag)) {
|
idx = newValue.indexOf(NEWLINE);
|
||||||
break;
|
if (!nesting) {
|
||||||
|
content.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const tagLevel = nesting.groups!.nestingLevel!.length;
|
||||||
|
// first level
|
||||||
|
if (nestingLevels.length === 0) {
|
||||||
|
nestingLevels.push(tagLevel);
|
||||||
|
content.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const currentLevel = nestingLevels[nestingLevels.length - 1]!;
|
||||||
|
if (tagLevel < currentLevel) {
|
||||||
|
// entering a nested admonition block
|
||||||
|
nestingLevels.push(tagLevel);
|
||||||
|
} else if (tagLevel === currentLevel) {
|
||||||
|
// closing a nested admonition block
|
||||||
|
nestingLevels.pop();
|
||||||
|
// the closing tag is NOT part of the content
|
||||||
|
if (nestingLevels.length === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
content.push(line);
|
content.push(line);
|
||||||
idx = newValue.indexOf(NEWLINE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume the processed tag and replace escape sequences
|
// consume the processed tag and replace escape sequences
|
||||||
|
|
|
@ -245,3 +245,25 @@ Admonition body
|
||||||
Admonition alias `:::important` should have Important title
|
Admonition alias `:::important` should have Important title
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::::note title
|
||||||
|
|
||||||
|
Some **content** with _Markdown_ `syntax`.
|
||||||
|
|
||||||
|
::::note nested Title
|
||||||
|
|
||||||
|
:::tip very nested Title
|
||||||
|
|
||||||
|
Some **content** with _Markdown_ `syntax`.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
Some **content** with _Markdown_ `syntax`.
|
||||||
|
|
||||||
|
::::
|
||||||
|
|
||||||
|
hey
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
after admonition
|
||||||
|
|
|
@ -129,6 +129,54 @@ Some **content** with _Markdown_ `syntax`.
|
||||||
</BrowserWindow>
|
</BrowserWindow>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Nested admonitions {#nested-admonitions}
|
||||||
|
|
||||||
|
Admonitions can be nested. Use more colons `:` for each parent admonition level.
|
||||||
|
|
||||||
|
```md
|
||||||
|
:::::info Parent
|
||||||
|
|
||||||
|
Parent content
|
||||||
|
|
||||||
|
::::danger Child
|
||||||
|
|
||||||
|
Child content
|
||||||
|
|
||||||
|
:::tip Deep Child
|
||||||
|
|
||||||
|
Deep child content
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::::
|
||||||
|
|
||||||
|
:::::
|
||||||
|
```
|
||||||
|
|
||||||
|
```mdx-code-block
|
||||||
|
<BrowserWindow>
|
||||||
|
|
||||||
|
:::::info Parent
|
||||||
|
|
||||||
|
Parent content
|
||||||
|
|
||||||
|
::::danger Child
|
||||||
|
|
||||||
|
Child content
|
||||||
|
|
||||||
|
:::tip Deep Child
|
||||||
|
|
||||||
|
Deep child content
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
::::
|
||||||
|
|
||||||
|
:::::
|
||||||
|
|
||||||
|
</BrowserWindow>
|
||||||
|
```
|
||||||
|
|
||||||
## Admonitions with MDX {#admonitions-with-mdx}
|
## Admonitions with MDX {#admonitions-with-mdx}
|
||||||
|
|
||||||
You can use MDX inside admonitions too!
|
You can use MDX inside admonitions too!
|
||||||
|
@ -308,7 +356,7 @@ const AdmonitionTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AdmonitionTypes;
|
export default AdmonitionTypes;
|
||||||
````
|
```
|
||||||
|
|
||||||
Now you can use your new admonition keyword in a Markdown file, and it will be parsed and rendered with your custom logic:
|
Now you can use your new admonition keyword in a Markdown file, and it will be parsed and rendered with your custom logic:
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue