Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(mdx-loader): support nested admonitions #8303

Merged
merged 7 commits into from Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Expand Up @@ -54,6 +54,11 @@ exports[`admonitions remark plugin interpolation 1`] = `
<admonition type="tip"><mdxAdmonitionTitle>My <code>interpolated</code> <strong>title</strong> &#x3C;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`] = `
"<p>The blog feature enables you to deploy in no time a full-featured blog.</p>
<p>:::info Sample Title</p>
Expand Down
Expand Up @@ -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();
});
});
48 changes: 42 additions & 6 deletions packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
Expand Up @@ -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}(?<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
// present and create tags for it
Expand All @@ -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
Expand All @@ -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
Expand Down
22 changes: 22 additions & 0 deletions website/_dogfooding/_pages tests/markdownPageTests.md
Expand Up @@ -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
Expand Up @@ -129,6 +129,54 @@ Some **content** with _Markdown_ `syntax`.
</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}

You can use MDX inside admonitions too!
Expand Down Expand Up @@ -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:

Expand Down