mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-04 01:09:20 +02:00
refactor: handle all admonitions via JSX component (#7152)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com> Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
parent
17fe43ecc8
commit
5746c58f41
39 changed files with 709 additions and 250 deletions
|
@ -44,6 +44,8 @@
|
|||
"@types/unist": "^2.0.6",
|
||||
"remark": "^12.0.1",
|
||||
"remark-mdx": "^1.6.21",
|
||||
"rehype-stringify": "^8.0.0",
|
||||
"remark-rehype": "^8.1.0",
|
||||
"to-vfile": "^6.1.0",
|
||||
"unist-builder": "^2.0.3",
|
||||
"unist-util-remove-position": "^3.0.0"
|
||||
|
|
|
@ -23,8 +23,10 @@ import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
|
|||
import transformImage from './remark/transformImage';
|
||||
import transformLinks from './remark/transformLinks';
|
||||
|
||||
import transformAdmonitions from './remark/admonitions';
|
||||
import type {LoaderContext} from 'webpack';
|
||||
import type {Processor, Plugin} from 'unified';
|
||||
import type {AdmonitionOptions} from './remark/admonitions';
|
||||
|
||||
const {
|
||||
loaders: {inlineMarkdownImageFileLoader},
|
||||
|
@ -37,6 +39,7 @@ const pragma = `
|
|||
`;
|
||||
|
||||
const DEFAULT_OPTIONS: MDXOptions = {
|
||||
admonitions: true,
|
||||
rehypePlugins: [],
|
||||
remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc],
|
||||
beforeDefaultRemarkPlugins: [],
|
||||
|
@ -48,7 +51,9 @@ const compilerCache = new Map<string | Options, [Processor, Options]>();
|
|||
export type MDXPlugin =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[Plugin<any[]>, any] | Plugin<any[]>;
|
||||
|
||||
export type MDXOptions = {
|
||||
admonitions: boolean | AdmonitionOptions;
|
||||
remarkPlugins: MDXPlugin[];
|
||||
rehypePlugins: MDXPlugin[];
|
||||
beforeDefaultRemarkPlugins: MDXPlugin[];
|
||||
|
@ -132,6 +137,19 @@ function createAssetsExportCode(assets: unknown) {
|
|||
return `{\n${codeLines.join('\n')}\n}`;
|
||||
}
|
||||
|
||||
function getAdmonitionsPlugins(
|
||||
admonitionsOption: MDXOptions['admonitions'],
|
||||
): MDXPlugin[] {
|
||||
if (admonitionsOption) {
|
||||
const plugin: MDXPlugin =
|
||||
admonitionsOption === true
|
||||
? transformAdmonitions
|
||||
: [transformAdmonitions, admonitionsOption];
|
||||
return [plugin];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function mdxLoader(
|
||||
this: LoaderContext<Options>,
|
||||
fileString: string,
|
||||
|
@ -149,32 +167,37 @@ export async function mdxLoader(
|
|||
const hasFrontMatter = Object.keys(frontMatter).length > 0;
|
||||
|
||||
if (!compilerCache.has(this.query)) {
|
||||
const remarkPlugins: MDXPlugin[] = [
|
||||
...(reqOptions.beforeDefaultRemarkPlugins ?? []),
|
||||
...getAdmonitionsPlugins(reqOptions.admonitions ?? false),
|
||||
...DEFAULT_OPTIONS.remarkPlugins,
|
||||
[
|
||||
transformImage,
|
||||
{
|
||||
staticDirs: reqOptions.staticDirs,
|
||||
siteDir: reqOptions.siteDir,
|
||||
},
|
||||
],
|
||||
[
|
||||
transformLinks,
|
||||
{
|
||||
staticDirs: reqOptions.staticDirs,
|
||||
siteDir: reqOptions.siteDir,
|
||||
},
|
||||
],
|
||||
...(reqOptions.remarkPlugins ?? []),
|
||||
];
|
||||
|
||||
const rehypePlugins: MDXPlugin[] = [
|
||||
...(reqOptions.beforeDefaultRehypePlugins ?? []),
|
||||
...DEFAULT_OPTIONS.rehypePlugins,
|
||||
...(reqOptions.rehypePlugins ?? []),
|
||||
];
|
||||
|
||||
const options: Options = {
|
||||
...reqOptions,
|
||||
remarkPlugins: [
|
||||
...(reqOptions.beforeDefaultRemarkPlugins ?? []),
|
||||
...DEFAULT_OPTIONS.remarkPlugins,
|
||||
[
|
||||
transformImage,
|
||||
{
|
||||
staticDirs: reqOptions.staticDirs,
|
||||
siteDir: reqOptions.siteDir,
|
||||
},
|
||||
],
|
||||
[
|
||||
transformLinks,
|
||||
{
|
||||
staticDirs: reqOptions.staticDirs,
|
||||
siteDir: reqOptions.siteDir,
|
||||
},
|
||||
],
|
||||
...(reqOptions.remarkPlugins ?? []),
|
||||
],
|
||||
rehypePlugins: [
|
||||
...(reqOptions.beforeDefaultRehypePlugins ?? []),
|
||||
...DEFAULT_OPTIONS.rehypePlugins,
|
||||
...(reqOptions.rehypePlugins ?? []),
|
||||
],
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
};
|
||||
compilerCache.set(this.query, [createCompiler(options), options]);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Elvis Wolcott
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Facebook, Inc. and its affiliates.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
|||
# Docusaurus admonitions
|
||||
|
||||
Code from [remark-admonitions](https://github.com/remarkjs/remark-directive) (MIT license) has been copied to this folder, and highly customized for Docusaurus needs.
|
25
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/base.md
generated
Normal file
25
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/base.md
generated
Normal file
|
@ -0,0 +1,25 @@
|
|||
The blog feature enables you to deploy in no time a full-featured blog.
|
||||
|
||||
:::info Sample Title
|
||||
|
||||
Check the [Blog Plugin API Reference documentation](./api/plugins/plugin-content-blog.md) for an exhaustive list of options.
|
||||
|
||||
:::
|
||||
|
||||
## Initial setup {#initial-setup}
|
||||
|
||||
To set up your site's blog, start by creating a `blog` directory.
|
||||
|
||||
:::tip
|
||||
|
||||
Use the **[Fast Track](introduction.md#fast-track)** to understand Docusaurus in **5 minutes ⏱**!
|
||||
|
||||
Use **[docusaurus.new](https://docusaurus.new)** to test Docusaurus immediately in your browser!
|
||||
|
||||
:::
|
||||
|
||||
++++tip
|
||||
|
||||
Admonition with different syntax
|
||||
|
||||
++++
|
7
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/interpolation.md
generated
Normal file
7
packages/docusaurus-mdx-loader/src/remark/admonitions/__tests__/__fixtures__/interpolation.md
generated
Normal file
|
@ -0,0 +1,7 @@
|
|||
Test admonition with interpolated title/body
|
||||
|
||||
:::tip My `interpolated` **title** <button style={{color: "red"}} onClick={() => alert("click")}>test</button>
|
||||
|
||||
`body` **interpolated** <button>content</button>
|
||||
|
||||
:::
|
|
@ -0,0 +1,44 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`admonitions remark plugin base 1`] = `
|
||||
"<p>The blog feature enables you to deploy in no time a full-featured blog.</p>
|
||||
<admonition title="Sample Title" type="info"><p>Check the <a href="./api/plugins/plugin-content-blog.md">Blog Plugin API Reference documentation</a> for an exhaustive list of options.</p></admonition>
|
||||
<h2>Initial setup {#initial-setup}</h2>
|
||||
<p>To set up your site's blog, start by creating a <code>blog</code> directory.</p>
|
||||
<admonition type="tip"><p>Use the <strong><a href="introduction.md#fast-track">Fast Track</a></strong> to understand Docusaurus in <strong>5 minutes ⏱</strong>!</p><p>Use <strong><a href="https://docusaurus.new">docusaurus.new</a></strong> to test Docusaurus immediately in your browser!</p></admonition>
|
||||
<p>++++tip</p>
|
||||
<p>Admonition with different syntax</p>
|
||||
<p>++++</p>"
|
||||
`;
|
||||
|
||||
exports[`admonitions remark plugin custom keywords 1`] = `
|
||||
"<p>The blog feature enables you to deploy in no time a full-featured blog.</p>
|
||||
<p>:::info Sample Title</p>
|
||||
<p>Check the <a href="./api/plugins/plugin-content-blog.md">Blog Plugin API Reference documentation</a> for an exhaustive list of options.</p>
|
||||
<p>:::</p>
|
||||
<h2>Initial setup {#initial-setup}</h2>
|
||||
<p>To set up your site's blog, start by creating a <code>blog</code> directory.</p>
|
||||
<admonition type="tip"><p>Use the <strong><a href="introduction.md#fast-track">Fast Track</a></strong> to understand Docusaurus in <strong>5 minutes ⏱</strong>!</p><p>Use <strong><a href="https://docusaurus.new">docusaurus.new</a></strong> to test Docusaurus immediately in your browser!</p></admonition>
|
||||
<p>++++tip</p>
|
||||
<p>Admonition with different syntax</p>
|
||||
<p>++++</p>"
|
||||
`;
|
||||
|
||||
exports[`admonitions remark plugin custom tag 1`] = `
|
||||
"<p>The blog feature enables you to deploy in no time a full-featured blog.</p>
|
||||
<p>:::info Sample Title</p>
|
||||
<p>Check the <a href="./api/plugins/plugin-content-blog.md">Blog Plugin API Reference documentation</a> for an exhaustive list of options.</p>
|
||||
<p>:::</p>
|
||||
<h2>Initial setup {#initial-setup}</h2>
|
||||
<p>To set up your site's blog, start by creating a <code>blog</code> directory.</p>
|
||||
<p>:::tip</p>
|
||||
<p>Use the <strong><a href="introduction.md#fast-track">Fast Track</a></strong> to understand Docusaurus in <strong>5 minutes ⏱</strong>!</p>
|
||||
<p>Use <strong><a href="https://docusaurus.new">docusaurus.new</a></strong> to test Docusaurus immediately in your browser!</p>
|
||||
<p>:::</p>
|
||||
<admonition type="tip"><p>Admonition with different syntax</p></admonition>"
|
||||
`;
|
||||
|
||||
exports[`admonitions remark plugin interpolation 1`] = `
|
||||
"<p>Test admonition with interpolated title/body</p>
|
||||
<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>"
|
||||
`;
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import remark from 'remark';
|
||||
import remark2rehype from 'remark-rehype';
|
||||
import stringify from 'rehype-stringify';
|
||||
|
||||
import vfile from 'to-vfile';
|
||||
import plugin from '../index';
|
||||
import type {AdmonitionOptions} from '../index';
|
||||
|
||||
const processFixture = async (
|
||||
name: string,
|
||||
options?: Partial<AdmonitionOptions>,
|
||||
) => {
|
||||
const filePath = path.join(__dirname, '__fixtures__', `${name}.md`);
|
||||
const file = await vfile.read(filePath);
|
||||
|
||||
const result = await remark()
|
||||
.use(plugin, options)
|
||||
.use(remark2rehype)
|
||||
.use(stringify)
|
||||
.process(file);
|
||||
|
||||
return result.toString();
|
||||
};
|
||||
|
||||
describe('admonitions remark plugin', () => {
|
||||
it('base', async () => {
|
||||
const result = await processFixture('base');
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('custom keywords', async () => {
|
||||
const result = await processFixture('base', {keywords: ['tip']});
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('custom tag', async () => {
|
||||
const result = await processFixture('base', {tag: '++++'});
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('interpolation', async () => {
|
||||
const result = await processFixture('interpolation');
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
});
|
182
packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
Normal file
182
packages/docusaurus-mdx-loader/src/remark/admonitions/index.ts
Normal file
|
@ -0,0 +1,182 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import visit from 'unist-util-visit';
|
||||
import type {Transformer, Processor, Plugin} from 'unified';
|
||||
import type {Literal} from 'mdast';
|
||||
|
||||
const NEWLINE = '\n';
|
||||
|
||||
export type AdmonitionOptions = {
|
||||
tag: string;
|
||||
keywords: string[];
|
||||
};
|
||||
|
||||
export const DefaultAdmonitionOptions: AdmonitionOptions = {
|
||||
tag: ':::',
|
||||
keywords: [
|
||||
'secondary',
|
||||
'info',
|
||||
'success',
|
||||
'danger',
|
||||
'note',
|
||||
'tip',
|
||||
'warning',
|
||||
'important',
|
||||
'caution',
|
||||
],
|
||||
};
|
||||
|
||||
function escapeRegExp(s: string): string {
|
||||
return s.replace(/[-[\]{}()*+?.\\^$|/]/g, '\\$&');
|
||||
}
|
||||
|
||||
function normalizeOptions(
|
||||
options: Partial<AdmonitionOptions>,
|
||||
): AdmonitionOptions {
|
||||
return {...DefaultAdmonitionOptions, ...options};
|
||||
}
|
||||
|
||||
// This string value does not matter much
|
||||
// It is ignored because nodes are using hName/hProperties coming from HAST
|
||||
const admonitionNodeType = 'admonitionHTML';
|
||||
|
||||
const plugin: Plugin = function plugin(
|
||||
this: Processor,
|
||||
optionsInput: Partial<AdmonitionOptions> = {},
|
||||
): Transformer {
|
||||
const options = normalizeOptions(optionsInput);
|
||||
|
||||
const keywords = Object.values(options.keywords).map(escapeRegExp).join('|');
|
||||
const tag = escapeRegExp(options.tag);
|
||||
const regex = new RegExp(`${tag}(${keywords})(?: *(.*))?\n`);
|
||||
const escapeTag = new RegExp(escapeRegExp(`\\${options.tag}`), 'g');
|
||||
|
||||
// The tokenizer is called on blocks to determine if there is an admonition
|
||||
// present and create tags for it
|
||||
function blockTokenizer(this: any, eat: any, value: string, silent: boolean) {
|
||||
// Stop if no match or match does not start at beginning of line
|
||||
const match = regex.exec(value);
|
||||
if (!match || match.index !== 0) {
|
||||
return false;
|
||||
}
|
||||
// If silent return the match
|
||||
if (silent) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const now = eat.now();
|
||||
const [opening, keyword, title] = match;
|
||||
const food = [];
|
||||
const content = [];
|
||||
|
||||
let newValue = value;
|
||||
// consume lines until a closing tag
|
||||
let idx = newValue.indexOf(NEWLINE);
|
||||
while (idx !== -1) {
|
||||
// grab this line and eat it
|
||||
const next = newValue.indexOf(NEWLINE, idx + 1);
|
||||
const line =
|
||||
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;
|
||||
}
|
||||
content.push(line);
|
||||
idx = newValue.indexOf(NEWLINE);
|
||||
}
|
||||
|
||||
// consume the processed tag and replace escape sequences
|
||||
const contentString = content.join(NEWLINE).replace(escapeTag, options.tag);
|
||||
const add = eat(opening + food.join(NEWLINE));
|
||||
|
||||
// parse the content in block mode
|
||||
const exit = this.enterBlock();
|
||||
const contentNodes = this.tokenizeBlock(contentString, now);
|
||||
exit();
|
||||
|
||||
const titleNodes = this.tokenizeInline(title, now);
|
||||
|
||||
const isSimpleTextTitle =
|
||||
titleNodes.length === 1 && titleNodes[0].type === 'text';
|
||||
|
||||
const element = {
|
||||
type: admonitionNodeType,
|
||||
data: {
|
||||
// hName/hProperties come from HAST
|
||||
// See https://github.com/syntax-tree/mdast-util-to-hast#fields-on-nodes
|
||||
hName: 'admonition',
|
||||
hProperties: {
|
||||
...(title && isSimpleTextTitle && {title}),
|
||||
type: keyword,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
// For titles containing MDX syntax: create a custom element. The theme
|
||||
// component will extract it and render it nicely.
|
||||
//
|
||||
// Temporary workaround, because it's complex in MDX v1 to emit
|
||||
// interpolated JSX prop syntax (title={<>my <code>title</code></>}).
|
||||
// For this reason, we use children instead of the title prop.
|
||||
title &&
|
||||
!isSimpleTextTitle && {
|
||||
type: admonitionNodeType,
|
||||
data: {
|
||||
hName: 'mdxAdmonitionTitle',
|
||||
hProperties: {},
|
||||
},
|
||||
children: titleNodes,
|
||||
},
|
||||
...contentNodes,
|
||||
].filter(Boolean),
|
||||
};
|
||||
|
||||
return add(element);
|
||||
}
|
||||
|
||||
// add tokenizer to parser after fenced code blocks
|
||||
const Parser = this.Parser.prototype;
|
||||
Parser.blockTokenizers.admonition = blockTokenizer;
|
||||
Parser.blockMethods.splice(
|
||||
Parser.blockMethods.indexOf('fencedCode') + 1,
|
||||
0,
|
||||
'admonition',
|
||||
);
|
||||
Parser.interruptParagraph.splice(
|
||||
Parser.interruptParagraph.indexOf('fencedCode') + 1,
|
||||
0,
|
||||
['admonition'],
|
||||
);
|
||||
Parser.interruptList.splice(
|
||||
Parser.interruptList.indexOf('fencedCode') + 1,
|
||||
0,
|
||||
['admonition'],
|
||||
);
|
||||
Parser.interruptBlockquote.splice(
|
||||
Parser.interruptBlockquote.indexOf('fencedCode') + 1,
|
||||
0,
|
||||
['admonition'],
|
||||
);
|
||||
|
||||
return (root) => {
|
||||
// escape everything except admonitionHTML nodes
|
||||
visit(
|
||||
root,
|
||||
(node: unknown): node is Literal =>
|
||||
(node as Literal)?.type !== admonitionNodeType,
|
||||
(node: Literal) => {
|
||||
if (node.value) {
|
||||
node.value = node.value.replace(escapeTag, options.tag);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default plugin;
|
|
@ -30,7 +30,6 @@
|
|||
"fs-extra": "^10.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"reading-time": "^1.5.0",
|
||||
"remark-admonitions": "^1.2.1",
|
||||
"tslib": "^2.4.0",
|
||||
"unist-util-visit": "^2.0.3",
|
||||
"utility-types": "^3.10.0",
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
declare module 'remark-admonitions' {
|
||||
type Options = {[key: string]: unknown};
|
||||
|
||||
const plugin: (options?: Options) => void;
|
||||
export = plugin;
|
||||
}
|
|
@ -21,7 +21,6 @@ import {
|
|||
type TagsListItem,
|
||||
type TagModule,
|
||||
} from '@docusaurus/utils';
|
||||
import admonitions from 'remark-admonitions';
|
||||
import {
|
||||
generateBlogPosts,
|
||||
getSourceToPermalink,
|
||||
|
@ -49,12 +48,6 @@ export default async function pluginContentBlog(
|
|||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Promise<Plugin<BlogContent>> {
|
||||
if (options.admonitions) {
|
||||
options.remarkPlugins = options.remarkPlugins.concat([
|
||||
[admonitions, options.admonitions],
|
||||
]);
|
||||
}
|
||||
|
||||
const {
|
||||
siteDir,
|
||||
siteConfig,
|
||||
|
@ -381,6 +374,7 @@ export default async function pluginContentBlog(
|
|||
|
||||
configureWebpack(_config, isServer, {getJSLoader}, content) {
|
||||
const {
|
||||
admonitions,
|
||||
rehypePlugins,
|
||||
remarkPlugins,
|
||||
truncateMarker,
|
||||
|
@ -423,6 +417,7 @@ export default async function pluginContentBlog(
|
|||
{
|
||||
loader: require.resolve('@docusaurus/mdx-loader'),
|
||||
options: {
|
||||
admonitions,
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
beforeDefaultRemarkPlugins: [
|
||||
|
|
|
@ -24,7 +24,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|||
feedOptions: {type: ['rss', 'atom'], copyright: ''},
|
||||
beforeDefaultRehypePlugins: [],
|
||||
beforeDefaultRemarkPlugins: [],
|
||||
admonitions: {},
|
||||
admonitions: true,
|
||||
truncateMarker: /<!--\s*truncate\s*-->/,
|
||||
rehypePlugins: [],
|
||||
remarkPlugins: [],
|
||||
|
|
|
@ -377,7 +377,6 @@ declare module '@docusaurus/plugin-content-blog' {
|
|||
* unlocalized file. Ignored when `editUrl` is a function.
|
||||
*/
|
||||
editLocalizedFiles?: boolean;
|
||||
admonitions: {[key: string]: unknown};
|
||||
/** Path to the authors map file, relative to the blog content directory. */
|
||||
authorsMapPath: string;
|
||||
/** A callback to customize the reading time number displayed. */
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
"import-fresh": "^3.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"remark-admonitions": "^1.2.1",
|
||||
"tslib": "^2.4.0",
|
||||
"utility-types": "^3.10.0",
|
||||
"webpack": "^5.72.1"
|
||||
|
|
|
@ -63,7 +63,7 @@ describe('normalizeDocsPluginOptions', () => {
|
|||
breadcrumbs: true,
|
||||
showLastUpdateTime: true,
|
||||
showLastUpdateAuthor: true,
|
||||
admonitions: {},
|
||||
admonitions: false,
|
||||
includeCurrentVersion: false,
|
||||
disableVersioning: true,
|
||||
editCurrentVersion: true,
|
||||
|
@ -84,7 +84,6 @@ describe('normalizeDocsPluginOptions', () => {
|
|||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
remarkPlugins: [...userOptions.remarkPlugins!, expect.any(Array)],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -102,7 +101,6 @@ describe('normalizeDocsPluginOptions', () => {
|
|||
expect(testValidate(userOptions)).toEqual({
|
||||
...defaultOptions,
|
||||
...userOptions,
|
||||
remarkPlugins: [...userOptions.remarkPlugins!, expect.any(Array)],
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -116,14 +114,14 @@ describe('normalizeDocsPluginOptions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('rejects admonitions true', () => {
|
||||
const admonitionsTrue: Options = {
|
||||
admonitions: true,
|
||||
};
|
||||
it('rejects admonitions array', () => {
|
||||
expect(() =>
|
||||
testValidate(admonitionsTrue),
|
||||
testValidate({
|
||||
// @ts-expect-error: rejected value
|
||||
admonitions: [],
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`""admonitions" contains an invalid value"`,
|
||||
`""admonitions" does not look like a valid admonitions config"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
declare module 'remark-admonitions' {
|
||||
type Options = {[key: string]: unknown};
|
||||
|
||||
const plugin: (options?: Options) => void;
|
||||
export = plugin;
|
||||
}
|
|
@ -352,6 +352,7 @@ export default async function pluginContentDocs(
|
|||
{
|
||||
loader: require.resolve('@docusaurus/mdx-loader'),
|
||||
options: {
|
||||
admonitions: options.admonitions,
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
beforeDefaultRehypePlugins,
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
URISchema,
|
||||
} from '@docusaurus/utils-validation';
|
||||
import {GlobExcludeDefault} from '@docusaurus/utils';
|
||||
import admonitions from 'remark-admonitions';
|
||||
import {DefaultSidebarItemsGenerator} from './sidebars/generator';
|
||||
import {
|
||||
DefaultNumberPrefixParser,
|
||||
|
@ -42,7 +41,7 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id' | 'sidebarPath'> = {
|
|||
beforeDefaultRehypePlugins: [],
|
||||
showLastUpdateTime: false,
|
||||
showLastUpdateAuthor: false,
|
||||
admonitions: {},
|
||||
admonitions: true,
|
||||
includeCurrentVersion: true,
|
||||
disableVersioning: false,
|
||||
lastVersion: undefined,
|
||||
|
@ -123,9 +122,7 @@ const OptionsSchema = Joi.object<PluginOptions>({
|
|||
beforeDefaultRehypePlugins: RehypePluginsSchema.default(
|
||||
DEFAULT_OPTIONS.beforeDefaultRehypePlugins,
|
||||
),
|
||||
admonitions: Joi.alternatives()
|
||||
.try(AdmonitionsSchema, Joi.boolean().invalid(true))
|
||||
.default(DEFAULT_OPTIONS.admonitions),
|
||||
admonitions: AdmonitionsSchema.default(DEFAULT_OPTIONS.admonitions),
|
||||
showLastUpdateTime: Joi.bool().default(DEFAULT_OPTIONS.showLastUpdateTime),
|
||||
showLastUpdateAuthor: Joi.bool().default(
|
||||
DEFAULT_OPTIONS.showLastUpdateAuthor,
|
||||
|
@ -167,11 +164,5 @@ export function validateOptions({
|
|||
|
||||
const normalizedOptions = validate(OptionsSchema, options);
|
||||
|
||||
if (normalizedOptions.admonitions) {
|
||||
normalizedOptions.remarkPlugins = normalizedOptions.remarkPlugins.concat([
|
||||
[admonitions, normalizedOptions.admonitions],
|
||||
]);
|
||||
}
|
||||
|
||||
return normalizedOptions;
|
||||
}
|
||||
|
|
|
@ -206,7 +206,6 @@ declare module '@docusaurus/plugin-content-docs' {
|
|||
docTagsListComponent: string;
|
||||
/** Root component of the generated category index page. */
|
||||
docCategoryGeneratedIndexComponent: string;
|
||||
admonitions: {[key: string]: unknown};
|
||||
sidebarItemsGenerator: import('./sidebars/types').SidebarItemsGeneratorOption;
|
||||
/**
|
||||
* URL route for the tags section of your doc version. Will be appended to
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"@docusaurus/utils": "2.0.0-beta.21",
|
||||
"@docusaurus/utils-validation": "2.0.0-beta.21",
|
||||
"fs-extra": "^10.1.0",
|
||||
"remark-admonitions": "^1.2.1",
|
||||
"tslib": "^2.4.0",
|
||||
"webpack": "^5.72.1"
|
||||
},
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
declare module 'remark-admonitions' {
|
||||
type Options = {[key: string]: unknown};
|
||||
|
||||
const plugin: (options?: Options) => void;
|
||||
export = plugin;
|
||||
}
|
|
@ -21,7 +21,6 @@ import {
|
|||
DEFAULT_PLUGIN_ID,
|
||||
parseMarkdownString,
|
||||
} from '@docusaurus/utils';
|
||||
import admonitions from 'remark-admonitions';
|
||||
import {validatePageFrontMatter} from './frontMatter';
|
||||
|
||||
import type {LoadContext, Plugin} from '@docusaurus/types';
|
||||
|
@ -43,11 +42,6 @@ export default function pluginContentPages(
|
|||
context: LoadContext,
|
||||
options: PluginOptions,
|
||||
): Plugin<LoadedContent | null> {
|
||||
if (options.admonitions) {
|
||||
options.remarkPlugins = options.remarkPlugins.concat([
|
||||
[admonitions, options.admonitions],
|
||||
]);
|
||||
}
|
||||
const {siteConfig, siteDir, generatedFilesDir, localizationDir} = context;
|
||||
|
||||
const contentPaths: PagesContentPaths = {
|
||||
|
@ -170,6 +164,7 @@ export default function pluginContentPages(
|
|||
|
||||
configureWebpack(config, isServer, {getJSLoader}) {
|
||||
const {
|
||||
admonitions,
|
||||
rehypePlugins,
|
||||
remarkPlugins,
|
||||
beforeDefaultRehypePlugins,
|
||||
|
@ -194,6 +189,7 @@ export default function pluginContentPages(
|
|||
{
|
||||
loader: require.resolve('@docusaurus/mdx-loader'),
|
||||
options: {
|
||||
admonitions,
|
||||
remarkPlugins,
|
||||
rehypePlugins,
|
||||
beforeDefaultRehypePlugins,
|
||||
|
|
|
@ -25,7 +25,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|||
rehypePlugins: [],
|
||||
beforeDefaultRehypePlugins: [],
|
||||
beforeDefaultRemarkPlugins: [],
|
||||
admonitions: {},
|
||||
admonitions: true,
|
||||
};
|
||||
|
||||
const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||
|
|
|
@ -16,7 +16,6 @@ declare module '@docusaurus/plugin-content-pages' {
|
|||
include: string[];
|
||||
exclude: string[];
|
||||
mdxPageComponent: string;
|
||||
admonitions: {[key: string]: unknown};
|
||||
};
|
||||
|
||||
export type Options = Partial<PluginOptions>;
|
||||
|
|
|
@ -139,7 +139,6 @@ export default function themeClassic(
|
|||
const modules = [
|
||||
require.resolve(getInfimaCSSFile(direction)),
|
||||
'./prism-include-languages',
|
||||
'./admonitions.css',
|
||||
'./nprogress',
|
||||
];
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ declare module '@theme/Admonition' {
|
|||
readonly children: ReactNode;
|
||||
readonly type: 'note' | 'tip' | 'danger' | 'info' | 'caution';
|
||||
readonly icon?: ReactNode;
|
||||
readonly title?: string;
|
||||
readonly title?: ReactNode;
|
||||
}
|
||||
export default function Admonition(props: Props): JSX.Element;
|
||||
}
|
||||
|
@ -675,6 +675,7 @@ declare module '@theme/MDXComponents' {
|
|||
import type MDXDetails from '@theme/MDXComponents/Details';
|
||||
import type MDXUl from '@theme/MDXComponents/Ul';
|
||||
import type MDXImg from '@theme/MDXComponents/Img';
|
||||
import type Admonition from '@theme/Admonition';
|
||||
|
||||
export type MDXComponentsObject = {
|
||||
readonly head: typeof MDXHead;
|
||||
|
@ -690,6 +691,7 @@ declare module '@theme/MDXComponents' {
|
|||
readonly h4: (props: ComponentProps<'h4'>) => JSX.Element;
|
||||
readonly h5: (props: ComponentProps<'h5'>) => JSX.Element;
|
||||
readonly h6: (props: ComponentProps<'h6'>) => JSX.Element;
|
||||
readonly admonition: typeof Admonition;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[tagName: string]: ComponentType<any>;
|
||||
};
|
||||
|
|
|
@ -5,102 +5,161 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, {type ReactNode} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import type {Props} from '@theme/Admonition';
|
||||
|
||||
const icons = {
|
||||
note: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="16"
|
||||
viewBox="0 0 14 16">
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function NoteIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 14 16">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
tip: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="16"
|
||||
viewBox="0 0 12 16">
|
||||
);
|
||||
}
|
||||
|
||||
function TipIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 12 16">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
danger: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="16"
|
||||
viewBox="0 0 12 16">
|
||||
);
|
||||
}
|
||||
|
||||
function DangerIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 12 16">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
info: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="14"
|
||||
height="16"
|
||||
viewBox="0 0 14 16">
|
||||
);
|
||||
}
|
||||
|
||||
function InfoIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 14 16">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
caution: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16">
|
||||
);
|
||||
}
|
||||
|
||||
function CautionIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
type AdmonitionConfig = {
|
||||
iconComponent: React.ComponentType;
|
||||
infimaClassName: string;
|
||||
};
|
||||
|
||||
const ifmClassNames = {
|
||||
note: 'secondary',
|
||||
tip: 'success',
|
||||
danger: 'danger',
|
||||
info: 'info',
|
||||
caution: 'warning',
|
||||
const AdmonitionConfigs: {[key: string]: AdmonitionConfig | string} = {
|
||||
note: {
|
||||
infimaClassName: 'secondary',
|
||||
iconComponent: NoteIcon,
|
||||
},
|
||||
tip: {
|
||||
infimaClassName: 'success',
|
||||
iconComponent: TipIcon,
|
||||
},
|
||||
danger: {
|
||||
infimaClassName: 'danger',
|
||||
iconComponent: DangerIcon,
|
||||
},
|
||||
info: {
|
||||
infimaClassName: 'info',
|
||||
iconComponent: InfoIcon,
|
||||
},
|
||||
caution: {
|
||||
infimaClassName: 'warning',
|
||||
iconComponent: CautionIcon,
|
||||
},
|
||||
secondary: 'note',
|
||||
important: 'info',
|
||||
success: 'tip',
|
||||
warning: 'danger',
|
||||
};
|
||||
|
||||
export default function Admonition({
|
||||
children,
|
||||
type,
|
||||
title = type,
|
||||
icon = icons[type],
|
||||
}: Props): JSX.Element {
|
||||
function getAdmonitionConfig(type: string): AdmonitionConfig {
|
||||
const config = AdmonitionConfigs[type];
|
||||
if (config) {
|
||||
if (typeof config === 'string') {
|
||||
return AdmonitionConfigs[config] as AdmonitionConfig;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
console.warn(
|
||||
`No admonition config found for admonition type "${type}". Using Info as fallback.`,
|
||||
);
|
||||
return AdmonitionConfigs.info as AdmonitionConfig;
|
||||
}
|
||||
|
||||
// Workaround because it's difficult in MDX v1 to provide a MDX title as props
|
||||
// See https://github.com/facebook/docusaurus/pull/7152#issuecomment-1145779682
|
||||
function extractMDXAdmonitionTitle(children: ReactNode): {
|
||||
mdxAdmonitionTitle: ReactNode | undefined;
|
||||
rest: ReactNode;
|
||||
} {
|
||||
const items = React.Children.toArray(children);
|
||||
const mdxAdmonitionTitle = items.find(
|
||||
(item) =>
|
||||
React.isValidElement(item) &&
|
||||
(item.props as {mdxType: string} | null)?.mdxType ===
|
||||
'mdxAdmonitionTitle',
|
||||
);
|
||||
const rest = <>{items.filter((item) => item !== mdxAdmonitionTitle)}</>;
|
||||
return {
|
||||
mdxAdmonitionTitle,
|
||||
rest,
|
||||
};
|
||||
}
|
||||
|
||||
function processAdmonitionProps(props: Props): Props {
|
||||
const {mdxAdmonitionTitle, rest} = extractMDXAdmonitionTitle(props.children);
|
||||
return {
|
||||
...props,
|
||||
title: props.title ?? mdxAdmonitionTitle ?? props.type,
|
||||
children: rest,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Admonition(props: Props): JSX.Element {
|
||||
const {
|
||||
children,
|
||||
type,
|
||||
title = type,
|
||||
icon: iconProp,
|
||||
} = processAdmonitionProps(props);
|
||||
|
||||
const config = getAdmonitionConfig(type);
|
||||
const {infimaClassName, iconComponent: IconComponent} = config;
|
||||
const icon = iconProp ?? <IconComponent />;
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'admonition',
|
||||
`admonition-${type}`,
|
||||
'alert',
|
||||
`alert--${ifmClassNames[type]}`,
|
||||
)}>
|
||||
<div className="admonition-heading">
|
||||
<h5>
|
||||
<span className="admonition-icon">{icon}</span>
|
||||
{title}
|
||||
</h5>
|
||||
className={clsx('alert', `alert--${infimaClassName}`, styles.admonition)}>
|
||||
<div className={styles.admonitionHeading}>
|
||||
<span className={styles.admonitionIcon}>{icon}</span>
|
||||
{title}
|
||||
</div>
|
||||
<div className="admonition-content">{children}</div>
|
||||
<div className={styles.admonitionContent}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,35 +5,34 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
.admonition h5 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
.admonition {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.admonition h5 code {
|
||||
.admonitionHeading {
|
||||
font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) /
|
||||
var(--ifm-heading-line-height) var(--ifm-heading-font-family);
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.admonitionHeading code {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.admonition-icon {
|
||||
.admonitionIcon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
.admonition-icon svg {
|
||||
.admonitionIcon svg {
|
||||
display: inline-block;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
stroke-width: 0;
|
||||
height: 1.6em;
|
||||
width: 1.6em;
|
||||
fill: var(--ifm-alert-foreground-color);
|
||||
stroke: var(--ifm-alert-foreground-color);
|
||||
}
|
||||
|
||||
.admonition-content > :last-child {
|
||||
.admonitionContent > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.admonition {
|
||||
margin-bottom: 1em;
|
||||
}
|
|
@ -14,6 +14,7 @@ import MDXDetails from '@theme/MDXComponents/Details';
|
|||
import MDXHeading from '@theme/MDXComponents/Heading';
|
||||
import MDXUl from '@theme/MDXComponents/Ul';
|
||||
import MDXImg from '@theme/MDXComponents/Img';
|
||||
import Admonition from '@theme/Admonition';
|
||||
|
||||
import type {MDXComponentsObject} from '@theme/MDXComponents';
|
||||
|
||||
|
@ -31,6 +32,7 @@ const MDXComponents: MDXComponentsObject = {
|
|||
h4: (props) => <MDXHeading as="h4" {...props} />,
|
||||
h5: (props) => <MDXHeading as="h5" {...props} />,
|
||||
h6: (props) => <MDXHeading as="h6" {...props} />,
|
||||
admonition: Admonition,
|
||||
};
|
||||
|
||||
export default MDXComponents;
|
||||
|
|
|
@ -1,12 +1,32 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value=[] 1`] = `""value" must be of type object"`;
|
||||
exports[`validation schemas admonitionsSchema: for value=[] 1`] = `""value" does not look like a valid admonitions config"`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value=3 1`] = `""value" must be of type object"`;
|
||||
exports[`validation schemas admonitionsSchema: for value={"customTypes":{"myKeyword":{"keyword":"myKeyword","infima":true,"svg":"<svg width=\\"512px\\" height=\\"512px\\" viewBox=\\"0 0 512 512\\" xmlns=\\"http://www.w3.org/2000/svg\\"></svg>"}}} 1`] = `
|
||||
"The Docusaurus admonitions system has changed, and the option "customTypes" does not exist anymore.
|
||||
You now need to swizzle the admonitions component to provide UI customizations such as icons.
|
||||
Please refer to https://github.com/facebook/docusaurus/pull/7152 for detailed upgrade instructions."
|
||||
`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value=null 1`] = `""value" must be of type object"`;
|
||||
exports[`validation schemas admonitionsSchema: for value={"icons":"emoji"} 1`] = `
|
||||
"The Docusaurus admonitions system has changed, and the option "icons" does not exist anymore.
|
||||
You now need to swizzle the admonitions component to provide UI customizations such as icons.
|
||||
Please refer to https://github.com/facebook/docusaurus/pull/7152 for detailed upgrade instructions."
|
||||
`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value=true 1`] = `""value" must be of type object"`;
|
||||
exports[`validation schemas admonitionsSchema: for value={"infima":true} 1`] = `
|
||||
"The Docusaurus admonitions system has changed, and the option "infima" does not exist anymore.
|
||||
You now need to swizzle the admonitions component to provide UI customizations such as icons.
|
||||
Please refer to https://github.com/facebook/docusaurus/pull/7152 for detailed upgrade instructions."
|
||||
`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value={"keywords":[]} 1`] = `""keywords" does not contain 1 required value(s)"`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value={"tag":""} 1`] = `""tag" is not allowed to be empty"`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value={"unknownAttribute":"val"} 1`] = `""unknownAttribute" is not allowed"`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value=3 1`] = `""value" does not look like a valid admonitions config"`;
|
||||
|
||||
exports[`validation schemas pathnameSchema: for value="foo" 1`] = `""value" is not a valid pathname. Pathname should start with slash and not contain any domain or query string."`;
|
||||
|
||||
|
|
|
@ -87,17 +87,39 @@ describe('validation schemas', () => {
|
|||
it('admonitionsSchema', () => {
|
||||
const {testOK, testFail} = createTestHelpers({
|
||||
schema: AdmonitionsSchema,
|
||||
defaultValue: {},
|
||||
defaultValue: true,
|
||||
});
|
||||
|
||||
testOK(undefined);
|
||||
testOK(true);
|
||||
testOK(false);
|
||||
testOK({});
|
||||
testOK({attr: 'val'});
|
||||
testOK({tag: '+++'});
|
||||
testOK({keywords: ['info', 'tip']});
|
||||
testOK({tag: '+++', keywords: ['info', 'tip']});
|
||||
|
||||
testFail(null);
|
||||
testFail(3);
|
||||
testFail(true);
|
||||
testFail([]);
|
||||
testFail({unknownAttribute: 'val'});
|
||||
testFail({tag: ''});
|
||||
testFail({keywords: []});
|
||||
|
||||
// Legacy types
|
||||
testFail({
|
||||
infima: true,
|
||||
});
|
||||
testFail({
|
||||
icons: 'emoji',
|
||||
});
|
||||
testFail({
|
||||
customTypes: {
|
||||
myKeyword: {
|
||||
keyword: `myKeyword`,
|
||||
infima: true,
|
||||
svg: '<svg width="512px" height="512px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"></svg>',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('remarkPluginsSchema', () => {
|
||||
|
|
|
@ -32,7 +32,31 @@ const MarkdownPluginsSchema = Joi.array()
|
|||
export const RemarkPluginsSchema = MarkdownPluginsSchema;
|
||||
export const RehypePluginsSchema = MarkdownPluginsSchema;
|
||||
|
||||
export const AdmonitionsSchema = Joi.object().default({});
|
||||
const LegacyAdmonitionConfigSchema = Joi.forbidden().messages({
|
||||
'any.unknown': `The Docusaurus admonitions system has changed, and the option {#label} does not exist anymore.
|
||||
You now need to swizzle the admonitions component to provide UI customizations such as icons.
|
||||
Please refer to https://github.com/facebook/docusaurus/pull/7152 for detailed upgrade instructions.`,
|
||||
});
|
||||
|
||||
export const AdmonitionsSchema = JoiFrontMatter.alternatives()
|
||||
.try(
|
||||
JoiFrontMatter.boolean().required(),
|
||||
JoiFrontMatter.object({
|
||||
tag: JoiFrontMatter.string(),
|
||||
keywords: JoiFrontMatter.array().items(
|
||||
JoiFrontMatter.string().required(),
|
||||
),
|
||||
// TODO Remove before 2023
|
||||
customTypes: LegacyAdmonitionConfigSchema,
|
||||
icons: LegacyAdmonitionConfigSchema,
|
||||
infima: LegacyAdmonitionConfigSchema,
|
||||
}).required(),
|
||||
)
|
||||
.default(true)
|
||||
.messages({
|
||||
'alternatives.types':
|
||||
'{{#label}} does not look like a valid admonitions config',
|
||||
});
|
||||
|
||||
// TODO how can we make this emit a custom error message :'(
|
||||
// Joi is such a pain, good luck to annoying trying to improve this
|
||||
|
|
|
@ -90,7 +90,6 @@
|
|||
"react-router": "^5.3.3",
|
||||
"react-router-config": "^5.1.1",
|
||||
"react-router-dom": "^5.3.3",
|
||||
"remark-admonitions": "^1.2.1",
|
||||
"rtl-detect": "^1.0.4",
|
||||
"semver": "^7.3.7",
|
||||
"serve-handler": "^6.1.3",
|
||||
|
|
2
packages/docusaurus/src/deps.d.ts
vendored
2
packages/docusaurus/src/deps.d.ts
vendored
|
@ -5,8 +5,6 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
declare module 'remark-admonitions';
|
||||
|
||||
declare module 'react-loadable-ssr-addon-v5-slorber' {
|
||||
import type {WebpackPluginInstance, Compiler} from 'webpack';
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import admonitions from 'remark-admonitions';
|
||||
import type {RuleSetRule} from 'webpack';
|
||||
import type {HtmlTagObject, LoadedPlugin, LoadContext} from '@docusaurus/types';
|
||||
|
||||
|
@ -97,6 +96,7 @@ export function createMDXFallbackPlugin({
|
|||
});
|
||||
}
|
||||
const mdxLoaderOptions = {
|
||||
admonitions: true,
|
||||
staticDirs: siteConfig.staticDirectories.map((dir) =>
|
||||
path.resolve(siteDir, dir),
|
||||
),
|
||||
|
@ -105,7 +105,6 @@ export function createMDXFallbackPlugin({
|
|||
isMDXPartial: () => true,
|
||||
// External MDX files might have front matter, just disable the warning
|
||||
isMDXPartialFrontMatterWarningDisabled: true,
|
||||
remarkPlugins: [admonitions],
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue