diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md
new file mode 100644
index 000000000000..04cb34755c4f
--- /dev/null
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md
@@ -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...
+:::
+
+::::
\ No newline at end of file
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap
index 0b7bdca847d7..b5597e9fb008 100644
--- a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__snapshots__/index.test.ts.snap
@@ -54,6 +54,11 @@ exports[`admonitions remark plugin interpolation 1`] = `
My interpolated
title <button style={{color: "red"}} onClick={() => alert("click")}>testbody
interpolated content
"
`;
+exports[`admonitions remark plugin nesting 1`] = `
+"
Test nested Admonitions
+WeatherOn nice days, you can enjoy skiing in the mountains.
StormsTake care of snowstorms...
"
+`;
+
exports[`admonitions remark plugin replace custom keyword 1`] = `
"The blog feature enables you to deploy in no time a full-featured blog.
:::info Sample Title
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts
index eb950e627e40..7e469ceaaa22 100644
--- a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/index.test.ts
@@ -71,4 +71,9 @@ describe('admonitions remark plugin', () => {
const result = await processFixture('interpolation');
expect(result).toMatchSnapshot();
});
+
+ it('nesting', async () => {
+ const result = await processFixture('nesting');
+ expect(result).toMatchSnapshot();
+ });
});
diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts b/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
index 779ef71873d0..c05a88c1c2f8 100644
--- a/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
@@ -69,9 +69,20 @@ const plugin: Plugin = function plugin(
const options = normalizeOptions(optionsInput);
const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
+ const nestingChar = escapeRegExp(options.tag.slice(0, 1));
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}(?${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
// present and create tags for it
@@ -94,6 +105,11 @@ const plugin: Plugin = function plugin(
];
const food = [];
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;
// 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);
food.push(line);
newValue = newValue.slice(idx + 1);
- // the closing tag is NOT part of the content
- if (line.startsWith(options.tag)) {
- break;
+ const nesting = nestingLevelRegex.exec(line);
+ idx = newValue.indexOf(NEWLINE);
+ 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);
- idx = newValue.indexOf(NEWLINE);
}
// consume the processed tag and replace escape sequences
diff --git a/website/_dogfooding/_pages tests/markdownPageTests.md b/website/_dogfooding/_pages tests/markdownPageTests.md
index 3097fa779235..ce1670413425 100644
--- a/website/_dogfooding/_pages tests/markdownPageTests.md
+++ b/website/_dogfooding/_pages tests/markdownPageTests.md
@@ -245,3 +245,25 @@ Admonition body
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
diff --git a/website/docs/guides/markdown-features/markdown-features-admonitions.mdx b/website/docs/guides/markdown-features/markdown-features-admonitions.mdx
index 9f01004a28ac..c2a6179e7e1d 100644
--- a/website/docs/guides/markdown-features/markdown-features-admonitions.mdx
+++ b/website/docs/guides/markdown-features/markdown-features-admonitions.mdx
@@ -129,6 +129,54 @@ Some **content** with _Markdown_ `syntax`.
```
+## 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
+
+
+:::::info Parent
+
+Parent content
+
+::::danger Child
+
+Child content
+
+:::tip Deep Child
+
+Deep child content
+
+:::
+
+::::
+
+:::::
+
+
+```
+
## Admonitions with MDX {#admonitions-with-mdx}
You can use MDX inside admonitions too!
@@ -308,7 +356,7 @@ const 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: