From d4247087bf8229bb2b606ed445ee8531b4182aa5 Mon Sep 17 00:00:00 2001 From: Balthasar Hofer Date: Tue, 8 Nov 2022 14:43:49 +0000 Subject: [PATCH 1/6] introduce nested admonitions --- .../__tests__/__fixtures__/nesting.md | 10 +++++++++ .../__snapshots__/index.test.ts.snap | 5 +++++ .../admonitions/__tests__/index.test.ts | 5 +++++ .../src/remark/admonitions/index.ts | 22 ++++++++++++++++--- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md 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..483f155454b4 --- /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")}>test

body interpolated content

" `; +exports[`admonitions remark plugin nesting 1`] = ` +"

Test nested Admonitions

+Weather

On nice days, you can enjoy skiing in the mountains.

Storms

Take 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..d1d2c292ca44 100644 --- a/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts @@ -70,6 +70,13 @@ const plugin: Plugin = function plugin( const keywords = Object.values(options.keywords).map(escapeRegExp).join('|'); const tag = escapeRegExp(options.tag); + + // opening tag is followed by some non-whitespace characters + const openingTagRegex = new RegExp(`^${tag}\\S+`); + + // closing tag is followed at most by whitespace characters + const closingTagRegex = new RegExp(`^${tag}\\s*$`); + const regex = new RegExp(`${tag}(${keywords})(?: *(.*))?\n`); const escapeTag = new RegExp(escapeRegExp(`\\${options.tag}`), 'g'); @@ -98,6 +105,7 @@ const plugin: Plugin = function plugin( let newValue = value; // consume lines until a closing tag let idx = newValue.indexOf(NEWLINE); + let nestingLevel = 0; while (idx !== -1) { // grab this line and eat it const next = newValue.indexOf(NEWLINE, idx + 1); @@ -105,9 +113,17 @@ 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; + // keep track of nested opening tags + if (openingTagRegex.test(line)) { + nestingLevel += 1; + } + if (closingTagRegex.test(line)) { + // the closing tag is NOT part of the content + if (nestingLevel === 0) { + break; + } + // a nested block was closed + nestingLevel -= 1; } content.push(line); idx = newValue.indexOf(NEWLINE); From e8002e98442367166ef87ffcf2d89e2a0cb98dec Mon Sep 17 00:00:00 2001 From: Balthasar Hofer Date: Tue, 8 Nov 2022 15:02:47 +0000 Subject: [PATCH 2/6] add dogfooding example of nested admonition --- website/_dogfooding/_pages tests/markdownPageTests.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/_dogfooding/_pages tests/markdownPageTests.md b/website/_dogfooding/_pages tests/markdownPageTests.md index 3097fa779235..58e984611085 100644 --- a/website/_dogfooding/_pages tests/markdownPageTests.md +++ b/website/_dogfooding/_pages tests/markdownPageTests.md @@ -245,3 +245,9 @@ Admonition body Admonition alias `:::important` should have Important title ::: + +:::info Nested Admonitions Admonitions can be easily nested. + +:::danger Child Admonition Foo Bar ::: + +::: From 99fa2a2c61f653aad2774b35bfa9e0da424cb195 Mon Sep 17 00:00:00 2001 From: Balthasar Hofer Date: Tue, 8 Nov 2022 15:14:40 +0000 Subject: [PATCH 3/6] fix dogfooding typo --- website/_dogfooding/_pages tests/markdownPageTests.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/_dogfooding/_pages tests/markdownPageTests.md b/website/_dogfooding/_pages tests/markdownPageTests.md index 58e984611085..bb72fb28ebcb 100644 --- a/website/_dogfooding/_pages tests/markdownPageTests.md +++ b/website/_dogfooding/_pages tests/markdownPageTests.md @@ -246,8 +246,8 @@ Admonition alias `:::important` should have Important title ::: -:::info Nested Admonitions Admonitions can be easily nested. +:::info Nested Admonitions -:::danger Child Admonition Foo Bar ::: +Admonitions can be easily nested. :::danger Child Admonition Foo Bar ::: ::: From 9f4ab1b226333fd5562cdbb46dca1b8ba7719832 Mon Sep 17 00:00:00 2001 From: Balthasar Hofer Date: Tue, 8 Nov 2022 15:17:10 +0000 Subject: [PATCH 4/6] troubles with the auto formatter --- website/_dogfooding/_pages tests/markdownPageTests.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/website/_dogfooding/_pages tests/markdownPageTests.md b/website/_dogfooding/_pages tests/markdownPageTests.md index bb72fb28ebcb..515ebd0815bc 100644 --- a/website/_dogfooding/_pages tests/markdownPageTests.md +++ b/website/_dogfooding/_pages tests/markdownPageTests.md @@ -248,6 +248,12 @@ Admonition alias `:::important` should have Important title :::info Nested Admonitions -Admonitions can be easily nested. :::danger Child Admonition Foo Bar ::: +Admonitions can be easily nested. + +:::danger Child Admonition + +Foo Bar + +::: ::: From e035911f9a02f0d1ce13e810c377b3466fd325c1 Mon Sep 17 00:00:00 2001 From: Balthasar Hofer Date: Sun, 20 Nov 2022 23:19:08 +0000 Subject: [PATCH 5/6] use remark-directive conventions for nested admonitions --- .../__tests__/__fixtures__/nesting.md | 4 +- .../src/remark/admonitions/index.ts | 52 +++++++++++++------ .../_pages tests/markdownPageTests.md | 24 ++++++++- 3 files changed, 61 insertions(+), 19 deletions(-) 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 index 483f155454b4..04cb34755c4f 100644 --- a/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md +++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/nesting.md @@ -1,10 +1,10 @@ Test nested Admonitions -:::info **Weather** +::::info **Weather** On nice days, you can enjoy skiing in the mountains. :::danger *Storms* Take care of snowstorms... ::: -::: \ No newline at end of file +:::: \ No newline at end of file diff --git a/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts b/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts index d1d2c292ca44..c05a88c1c2f8 100644 --- a/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts +++ b/packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts @@ -69,16 +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); - // opening tag is followed by some non-whitespace characters - const openingTagRegex = new RegExp(`^${tag}\\S+`); - - // closing tag is followed at most by whitespace characters - const closingTagRegex = new RegExp(`^${tag}\\s*$`); + // resolve th nesting level of an opening tag + // ::: -> 0, :::: -> 1, ::::: -> 2 ... + const nestingLevelRegex = new RegExp( + `^${tag}(?${nestingChar}*)`, + ); - const regex = new RegExp(`${tag}(${keywords})(?: *(.*))?\n`); - const escapeTag = new RegExp(escapeRegExp(`\\${options.tag}`), 'g'); + 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 @@ -101,11 +105,15 @@ 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 let idx = newValue.indexOf(NEWLINE); - let nestingLevel = 0; while (idx !== -1) { // grab this line and eat it const next = newValue.indexOf(NEWLINE, idx + 1); @@ -113,20 +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); - // keep track of nested opening tags - if (openingTagRegex.test(line)) { - nestingLevel += 1; + 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; } - if (closingTagRegex.test(line)) { + 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 (nestingLevel === 0) { + if (nestingLevels.length === 0) { break; } - // a nested block was closed - nestingLevel -= 1; } 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 515ebd0815bc..b8f95b33e626 100644 --- a/website/_dogfooding/_pages tests/markdownPageTests.md +++ b/website/_dogfooding/_pages tests/markdownPageTests.md @@ -246,7 +246,7 @@ Admonition alias `:::important` should have Important title ::: -:::info Nested Admonitions +::::info Nested Admonitions Admonitions can be easily nested. @@ -256,4 +256,26 @@ Foo Bar ::: +:::: + +:::::note Your title + +Some **content** with _Markdown_ `syntax`. + +::::note Your nested Title + +:::note Your very nested Title (does not work yet :s) + +Some **content** with _Markdown_ `syntax`. + ::: + +Some **content** with _Markdown_ `syntax`. + +:::: + +hey + +::::: + +hello From 3c9e769dff9a13d6612e11e219f8ba36ee1a7299 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Wed, 23 Nov 2022 20:03:14 +0100 Subject: [PATCH 6/6] add some docs --- .../_pages tests/markdownPageTests.md | 20 ++------ .../markdown-features-admonitions.mdx | 50 ++++++++++++++++++- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/website/_dogfooding/_pages tests/markdownPageTests.md b/website/_dogfooding/_pages tests/markdownPageTests.md index b8f95b33e626..ce1670413425 100644 --- a/website/_dogfooding/_pages tests/markdownPageTests.md +++ b/website/_dogfooding/_pages tests/markdownPageTests.md @@ -246,25 +246,13 @@ Admonition alias `:::important` should have Important title ::: -::::info Nested Admonitions - -Admonitions can be easily nested. - -:::danger Child Admonition - -Foo Bar - -::: - -:::: - -:::::note Your title +:::::note title Some **content** with _Markdown_ `syntax`. -::::note Your nested Title +::::note nested Title -:::note Your very nested Title (does not work yet :s) +:::tip very nested Title Some **content** with _Markdown_ `syntax`. @@ -278,4 +266,4 @@ hey ::::: -hello +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: