mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 23:57:22 +02:00
refactor(theme): split admonitions, make swizzle easier, better retrocompatibility (#7945)
Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
parent
f1415525c0
commit
6f63ffe0a3
32 changed files with 914 additions and 236 deletions
|
@ -53,7 +53,7 @@ export type MDXPlugin =
|
|||
[Plugin<any[]>, any] | Plugin<any[]>;
|
||||
|
||||
export type MDXOptions = {
|
||||
admonitions: boolean | AdmonitionOptions;
|
||||
admonitions: boolean | Partial<AdmonitionOptions>;
|
||||
remarkPlugins: MDXPlugin[];
|
||||
rehypePlugins: MDXPlugin[];
|
||||
beforeDefaultRemarkPlugins: MDXPlugin[];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`admonitions remark plugin base 1`] = `
|
||||
exports[`admonitions remark plugin add custom keyword 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>
|
||||
|
@ -11,11 +11,9 @@ exports[`admonitions remark plugin base 1`] = `
|
|||
<p>++++</p>"
|
||||
`;
|
||||
|
||||
exports[`admonitions remark plugin custom keywords 1`] = `
|
||||
exports[`admonitions remark plugin base 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>
|
||||
<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>
|
||||
|
@ -38,7 +36,33 @@ exports[`admonitions remark plugin custom tag 1`] = `
|
|||
<admonition type="tip"><p>Admonition with different syntax</p></admonition>"
|
||||
`;
|
||||
|
||||
exports[`admonitions remark plugin default behavior for custom keyword 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 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>"
|
||||
`;
|
||||
|
||||
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>
|
||||
<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>"
|
||||
`;
|
||||
|
|
|
@ -36,13 +36,34 @@ describe('admonitions remark plugin', () => {
|
|||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('custom keywords', async () => {
|
||||
const result = await processFixture('base', {keywords: ['tip']});
|
||||
it('default behavior for custom keyword', async () => {
|
||||
const result = await processFixture('base', {
|
||||
keywords: ['tip'],
|
||||
// extendDefaults: false, // By default we don't extend
|
||||
});
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('add custom keyword', async () => {
|
||||
const result = await processFixture('base', {
|
||||
keywords: ['tip'],
|
||||
extendDefaults: true,
|
||||
});
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('replace custom keyword', async () => {
|
||||
const result = await processFixture('base', {
|
||||
keywords: ['tip'],
|
||||
extendDefaults: false,
|
||||
});
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('custom tag', async () => {
|
||||
const result = await processFixture('base', {tag: '++++'});
|
||||
const result = await processFixture('base', {
|
||||
tag: '++++',
|
||||
});
|
||||
expect(result).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -11,9 +11,14 @@ import type {Literal} from 'mdast';
|
|||
|
||||
const NEWLINE = '\n';
|
||||
|
||||
// TODO not ideal option shape
|
||||
// First let upgrade to MDX 2.0
|
||||
// Maybe we'll want to provide different tags for different admonition types?
|
||||
// Also maybe rename "keywords" to "types"?
|
||||
export type AdmonitionOptions = {
|
||||
tag: string;
|
||||
keywords: string[];
|
||||
extendDefaults: boolean;
|
||||
};
|
||||
|
||||
export const DefaultAdmonitionOptions: AdmonitionOptions = {
|
||||
|
@ -29,6 +34,7 @@ export const DefaultAdmonitionOptions: AdmonitionOptions = {
|
|||
'important',
|
||||
'caution',
|
||||
],
|
||||
extendDefaults: false, // TODO make it true by default: breaking change
|
||||
};
|
||||
|
||||
function escapeRegExp(s: string): string {
|
||||
|
@ -36,9 +42,20 @@ function escapeRegExp(s: string): string {
|
|||
}
|
||||
|
||||
function normalizeOptions(
|
||||
options: Partial<AdmonitionOptions>,
|
||||
providedOptions: Partial<AdmonitionOptions>,
|
||||
): AdmonitionOptions {
|
||||
return {...DefaultAdmonitionOptions, ...options};
|
||||
const options = {...DefaultAdmonitionOptions, ...providedOptions};
|
||||
|
||||
// By default it makes more sense to append keywords to the default ones
|
||||
// Adding custom keywords is more common than disabling existing ones
|
||||
if (options.extendDefaults) {
|
||||
options.keywords = [
|
||||
...DefaultAdmonitionOptions.keywords,
|
||||
...options.keywords,
|
||||
];
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// This string value does not matter much
|
||||
|
|
|
@ -12,6 +12,113 @@ import type {SwizzleConfig} from '@docusaurus/types';
|
|||
export default function getSwizzleConfig(): SwizzleConfig {
|
||||
return {
|
||||
components: {
|
||||
'Admonition/Icon': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'forbidden', // Can't wrap a folder
|
||||
},
|
||||
description: 'The folder containing all admonition icons',
|
||||
},
|
||||
'Admonition/Icon/Caution': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The admonition caution icon',
|
||||
},
|
||||
'Admonition/Icon/Danger': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The admonition danger icon',
|
||||
},
|
||||
'Admonition/Icon/Info': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The admonition info icon',
|
||||
},
|
||||
'Admonition/Icon/Note': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The admonition note icon',
|
||||
},
|
||||
'Admonition/Icon/Tip': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The admonition tip icon',
|
||||
},
|
||||
'Admonition/Layout': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The standard admonition layout applied to all default admonition types',
|
||||
},
|
||||
'Admonition/Type': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'forbidden',
|
||||
},
|
||||
description:
|
||||
'The folder containing all the admonition type components.',
|
||||
},
|
||||
'Admonition/Type/Caution': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component responsible for rendering a :::caution admonition type',
|
||||
},
|
||||
'Admonition/Type/Danger': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component responsible for rendering a :::danger admonition type',
|
||||
},
|
||||
'Admonition/Type/Info': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component responsible for rendering a :::info admonition type',
|
||||
},
|
||||
'Admonition/Type/Note': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component responsible for rendering a :::note admonition type',
|
||||
},
|
||||
'Admonition/Type/Tip': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component responsible for rendering a :::tip admonition type',
|
||||
},
|
||||
'Admonition/Types': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
// TODO the swizzle CLI should provide a way to wrap such objects
|
||||
wrap: 'forbidden',
|
||||
},
|
||||
description:
|
||||
'The object mapping admonition type to a React component.\nUse it to add custom admonition type components, or replace existing ones.\nCan be ejected or wrapped (only manually, see our documentation).',
|
||||
},
|
||||
CodeBlock: {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
|
@ -20,6 +127,14 @@ export default function getSwizzleConfig(): SwizzleConfig {
|
|||
description:
|
||||
'The component used to render multi-line code blocks, generally used in Markdown files.',
|
||||
},
|
||||
'CodeBlock/Content': {
|
||||
actions: {
|
||||
eject: 'unsafe',
|
||||
wrap: 'forbidden',
|
||||
},
|
||||
description:
|
||||
'The folder containing components responsible for rendering different types of CodeBlock content.',
|
||||
},
|
||||
ColorModeToggle: {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
|
@ -36,6 +151,17 @@ export default function getSwizzleConfig(): SwizzleConfig {
|
|||
description:
|
||||
'The component responsible for rendering a list of sidebar items cards.\nNotable used on the category generated-index pages.',
|
||||
},
|
||||
'DocItem/TOC': {
|
||||
actions: {
|
||||
// Forbidden because it's a parent folder, makes the CLI crash atm
|
||||
// TODO the CLI should rather support --eject
|
||||
// Subfolders can be swizzled
|
||||
eject: 'forbidden',
|
||||
wrap: 'forbidden',
|
||||
},
|
||||
description:
|
||||
'The DocItem TOC is not directly swizzle-able, but you can swizzle its sub-components.',
|
||||
},
|
||||
DocSidebar: {
|
||||
actions: {
|
||||
eject: 'unsafe', // Too much technical code in sidebar, not very safe atm
|
||||
|
@ -101,6 +227,17 @@ export default function getSwizzleConfig(): SwizzleConfig {
|
|||
},
|
||||
description: 'The footer logo',
|
||||
},
|
||||
Icon: {
|
||||
actions: {
|
||||
// Forbidden because it's a parent folder, makes the CLI crash atm
|
||||
// TODO the CLI should rather support --eject
|
||||
// Subfolders can be swizzled
|
||||
eject: 'forbidden',
|
||||
wrap: 'forbidden',
|
||||
},
|
||||
description:
|
||||
'The Icon folder is not directly swizzle-able, but you can swizzle its sub-components.',
|
||||
},
|
||||
'Icon/Arrow': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
|
@ -220,7 +357,7 @@ export default function getSwizzleConfig(): SwizzleConfig {
|
|||
wrap: 'forbidden',
|
||||
},
|
||||
description:
|
||||
'The Navbar item components mapping. Can be ejected to add custom navbar item types. See https://github.com/facebook/docusaurus/issues/7227.',
|
||||
'The Navbar item components mapping. Can be ejected to add custom navbar item types.\nSee https://github.com/facebook/docusaurus/issues/7227.',
|
||||
},
|
||||
NotFound: {
|
||||
actions: {
|
||||
|
|
|
@ -44,13 +44,114 @@ declare module '@theme/Admonition' {
|
|||
|
||||
export interface Props {
|
||||
readonly children: ReactNode;
|
||||
readonly type: 'note' | 'tip' | 'danger' | 'info' | 'caution';
|
||||
readonly type: string;
|
||||
readonly icon?: ReactNode;
|
||||
readonly title?: ReactNode;
|
||||
readonly className?: string;
|
||||
}
|
||||
|
||||
export default function Admonition(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Type/Note' {
|
||||
import type {Props as AdmonitionProps} from '@theme/Admonition';
|
||||
|
||||
export interface Props extends AdmonitionProps {}
|
||||
export default function AdmonitionTypeNote(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Type/Info' {
|
||||
import type {Props as AdmonitionProps} from '@theme/Admonition';
|
||||
|
||||
export interface Props extends AdmonitionProps {}
|
||||
export default function AdmonitionTypeInfo(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Type/Tip' {
|
||||
import type {Props as AdmonitionProps} from '@theme/Admonition';
|
||||
|
||||
export interface Props extends AdmonitionProps {}
|
||||
export default function AdmonitionTypeTip(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Type/Caution' {
|
||||
import type {Props as AdmonitionProps} from '@theme/Admonition';
|
||||
|
||||
export interface Props extends AdmonitionProps {}
|
||||
export default function AdmonitionTypeCaution(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Type/Danger' {
|
||||
import type {Props as AdmonitionProps} from '@theme/Admonition';
|
||||
|
||||
export interface Props extends AdmonitionProps {}
|
||||
export default function AdmonitionTypeDanger(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Types' {
|
||||
import type {ComponentType} from 'react';
|
||||
import type {Props} from '@theme/Admonition';
|
||||
|
||||
const AdmonitionTypes: {
|
||||
[admonitionType: string]: ComponentType<Props>;
|
||||
};
|
||||
|
||||
export default AdmonitionTypes;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Layout' {
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
export interface Props {
|
||||
readonly children: ReactNode;
|
||||
readonly type: string;
|
||||
readonly icon?: ReactNode;
|
||||
readonly title?: ReactNode;
|
||||
readonly className?: string;
|
||||
}
|
||||
export default function AdmonitionLayout(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Icon/Note' {
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
export interface Props extends ComponentProps<'svg'> {}
|
||||
|
||||
export default function AdmonitionIconNote(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Icon/Tip' {
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
export interface Props extends ComponentProps<'svg'> {}
|
||||
|
||||
export default function AdmonitionIconTip(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Icon/Caution' {
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
export interface Props extends ComponentProps<'svg'> {}
|
||||
|
||||
export default function AdmonitionIconCaution(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Icon/Danger' {
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
export interface Props extends ComponentProps<'svg'> {}
|
||||
|
||||
export default function AdmonitionIconDanger(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/Admonition/Icon/Info' {
|
||||
import type {ComponentProps} from 'react';
|
||||
|
||||
export interface Props extends ComponentProps<'svg'> {}
|
||||
|
||||
export default function AdmonitionIconInfo(props: Props): JSX.Element;
|
||||
}
|
||||
|
||||
declare module '@theme/AnnouncementBar' {
|
||||
export default function AnnouncementBar(): JSX.Element | null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import type {Props} from '@theme/Admonition/Icon/Caution';
|
||||
|
||||
export default function AdmonitionIconCaution(props: Props): JSX.Element {
|
||||
return (
|
||||
<svg viewBox="0 0 16 16" {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import type {Props} from '@theme/Admonition/Icon/Danger';
|
||||
|
||||
export default function AdmonitionIconDanger(props: Props): JSX.Element {
|
||||
return (
|
||||
<svg viewBox="0 0 12 16" {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import type {Props} from '@theme/Admonition/Icon/Info';
|
||||
|
||||
export default function AdmonitionIconInfo(props: Props): JSX.Element {
|
||||
return (
|
||||
<svg viewBox="0 0 14 16" {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import type {Props} from '@theme/Admonition/Icon/Note';
|
||||
|
||||
export default function AdmonitionIconNote(props: Props): JSX.Element {
|
||||
return (
|
||||
<svg viewBox="0 0 14 16" {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import type {Props} from '@theme/Admonition/Icon/Tip';
|
||||
|
||||
export default function AdmonitionIconTip(props: Props): JSX.Element {
|
||||
return (
|
||||
<svg viewBox="0 0 12 16" {...props}>
|
||||
<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>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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 React, {type ReactNode} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {ThemeClassNames} from '@docusaurus/theme-common';
|
||||
|
||||
import type {Props} from '@theme/Admonition/Layout';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
function AdmonitionContainer({
|
||||
type,
|
||||
className,
|
||||
children,
|
||||
}: Pick<Props, 'type' | 'className'> & {children: ReactNode}) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
ThemeClassNames.common.admonition,
|
||||
ThemeClassNames.common.admonitionType(type),
|
||||
styles.admonition,
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdmonitionHeading({icon, title}: Pick<Props, 'icon' | 'title'>) {
|
||||
return (
|
||||
<div className={styles.admonitionHeading}>
|
||||
<span className={styles.admonitionIcon}>{icon}</span>
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdmonitionContent({children}: Pick<Props, 'children'>) {
|
||||
return <div className={styles.admonitionContent}>{children}</div>;
|
||||
}
|
||||
|
||||
export default function AdmonitionLayout(props: Props): JSX.Element {
|
||||
const {type, icon, title, children, className} = props;
|
||||
return (
|
||||
<AdmonitionContainer type={type} className={className}>
|
||||
<AdmonitionHeading title={title} icon={icon} />
|
||||
<AdmonitionContent>{children}</AdmonitionContent>
|
||||
</AdmonitionContainer>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import type {Props} from '@theme/Admonition/Type/Caution';
|
||||
import AdmonitionLayout from '@theme/Admonition/Layout';
|
||||
import IconCaution from '@theme/Admonition/Icon/Caution';
|
||||
|
||||
const infimaClassName = 'alert alert--warning';
|
||||
|
||||
const defaultProps = {
|
||||
icon: <IconCaution />,
|
||||
title: (
|
||||
<Translate
|
||||
id="theme.admonition.caution"
|
||||
description="The default label used for the Caution admonition (:::caution)">
|
||||
caution
|
||||
</Translate>
|
||||
),
|
||||
};
|
||||
|
||||
export default function AdmonitionTypeCaution(props: Props): JSX.Element {
|
||||
return (
|
||||
<AdmonitionLayout
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
className={clsx(infimaClassName, props.className)}>
|
||||
{props.children}
|
||||
</AdmonitionLayout>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import type {Props} from '@theme/Admonition/Type/Danger';
|
||||
import AdmonitionLayout from '@theme/Admonition/Layout';
|
||||
import IconDanger from '@theme/Admonition/Icon/Danger';
|
||||
|
||||
const infimaClassName = 'alert alert--danger';
|
||||
|
||||
const defaultProps = {
|
||||
icon: <IconDanger />,
|
||||
title: (
|
||||
<Translate
|
||||
id="theme.admonition.danger"
|
||||
description="The default label used for the Danger admonition (:::danger)">
|
||||
danger
|
||||
</Translate>
|
||||
),
|
||||
};
|
||||
|
||||
export default function AdmonitionTypeDanger(props: Props): JSX.Element {
|
||||
return (
|
||||
<AdmonitionLayout
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
className={clsx(infimaClassName, props.className)}>
|
||||
{props.children}
|
||||
</AdmonitionLayout>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import type {Props} from '@theme/Admonition/Type/Info';
|
||||
import AdmonitionLayout from '@theme/Admonition/Layout';
|
||||
import IconInfo from '@theme/Admonition/Icon/Info';
|
||||
|
||||
const infimaClassName = 'alert alert--info';
|
||||
|
||||
const defaultProps = {
|
||||
icon: <IconInfo />,
|
||||
title: (
|
||||
<Translate
|
||||
id="theme.admonition.info"
|
||||
description="The default label used for the Info admonition (:::info)">
|
||||
info
|
||||
</Translate>
|
||||
),
|
||||
};
|
||||
|
||||
export default function AdmonitionTypeInfo(props: Props): JSX.Element {
|
||||
return (
|
||||
<AdmonitionLayout
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
className={clsx(infimaClassName, props.className)}>
|
||||
{props.children}
|
||||
</AdmonitionLayout>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import type {Props} from '@theme/Admonition/Type/Note';
|
||||
import AdmonitionLayout from '@theme/Admonition/Layout';
|
||||
import IconNote from '@theme/Admonition/Icon/Note';
|
||||
|
||||
const infimaClassName = 'alert alert--secondary';
|
||||
|
||||
const defaultProps = {
|
||||
icon: <IconNote />,
|
||||
title: (
|
||||
<Translate
|
||||
id="theme.admonition.note"
|
||||
description="The default label used for the Note admonition (:::note)">
|
||||
note
|
||||
</Translate>
|
||||
),
|
||||
};
|
||||
|
||||
export default function AdmonitionTypeNote(props: Props): JSX.Element {
|
||||
return (
|
||||
<AdmonitionLayout
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
className={clsx(infimaClassName, props.className)}>
|
||||
{props.children}
|
||||
</AdmonitionLayout>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import type {Props} from '@theme/Admonition/Type/Tip';
|
||||
import AdmonitionLayout from '@theme/Admonition/Layout';
|
||||
import IconTip from '@theme/Admonition/Icon/Tip';
|
||||
|
||||
const infimaClassName = 'alert alert--success';
|
||||
|
||||
const defaultProps = {
|
||||
icon: <IconTip />,
|
||||
title: (
|
||||
<Translate
|
||||
id="theme.admonition.tip"
|
||||
description="The default label used for the Tip admonition (:::tip)">
|
||||
tip
|
||||
</Translate>
|
||||
),
|
||||
};
|
||||
|
||||
export default function AdmonitionTypeTip(props: Props): JSX.Element {
|
||||
return (
|
||||
<AdmonitionLayout
|
||||
{...defaultProps}
|
||||
{...props}
|
||||
className={clsx(infimaClassName, props.className)}>
|
||||
{props.children}
|
||||
</AdmonitionLayout>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import AdmonitionTypeNote from '@theme/Admonition/Type/Note';
|
||||
import AdmonitionTypeTip from '@theme/Admonition/Type/Tip';
|
||||
import AdmonitionTypeInfo from '@theme/Admonition/Type/Info';
|
||||
import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution';
|
||||
import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger';
|
||||
import type AdmonitionTypes from '@theme/Admonition/Types';
|
||||
|
||||
const admonitionTypes: typeof AdmonitionTypes = {
|
||||
note: AdmonitionTypeNote,
|
||||
tip: AdmonitionTypeTip,
|
||||
info: AdmonitionTypeInfo,
|
||||
caution: AdmonitionTypeCaution,
|
||||
danger: AdmonitionTypeDanger,
|
||||
};
|
||||
|
||||
// Undocumented legacy admonition type aliases
|
||||
// Provide hardcoded/untranslated retrocompatible label
|
||||
// See also https://github.com/facebook/docusaurus/issues/7767
|
||||
const admonitionAliases: typeof AdmonitionTypes = {
|
||||
secondary: (props) => <AdmonitionTypeNote title="secondary" {...props} />,
|
||||
important: (props) => <AdmonitionTypeInfo title="important" {...props} />,
|
||||
success: (props) => <AdmonitionTypeTip title="success" {...props} />,
|
||||
// TODO bad legacy mapping, warning is usually yellow, not red...
|
||||
warning: (props) => <AdmonitionTypeDanger title="warning" {...props} />,
|
||||
};
|
||||
|
||||
export default {
|
||||
...admonitionTypes,
|
||||
...admonitionAliases,
|
||||
};
|
|
@ -5,205 +5,24 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import React, {type ReactNode} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {ThemeClassNames} from '@docusaurus/theme-common';
|
||||
import Translate from '@docusaurus/Translate';
|
||||
import React, {type ComponentType} from 'react';
|
||||
import {processAdmonitionProps} from '@docusaurus/theme-common';
|
||||
import type {Props} from '@theme/Admonition';
|
||||
import AdmonitionTypes from '@theme/Admonition/Types';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
label: ReactNode;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
|
||||
const AdmonitionConfigs: Record<Props['type'], AdmonitionConfig> = {
|
||||
note: {
|
||||
infimaClassName: 'secondary',
|
||||
iconComponent: NoteIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.note"
|
||||
description="The default label used for the Note admonition (:::note)">
|
||||
note
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
tip: {
|
||||
infimaClassName: 'success',
|
||||
iconComponent: TipIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.tip"
|
||||
description="The default label used for the Tip admonition (:::tip)">
|
||||
tip
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
danger: {
|
||||
infimaClassName: 'danger',
|
||||
iconComponent: DangerIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.danger"
|
||||
description="The default label used for the Danger admonition (:::danger)">
|
||||
danger
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
info: {
|
||||
infimaClassName: 'info',
|
||||
iconComponent: InfoIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.info"
|
||||
description="The default label used for the Info admonition (:::info)">
|
||||
info
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
caution: {
|
||||
infimaClassName: 'warning',
|
||||
iconComponent: CautionIcon,
|
||||
label: (
|
||||
<Translate
|
||||
id="theme.admonition.caution"
|
||||
description="The default label used for the Caution admonition (:::caution)">
|
||||
caution
|
||||
</Translate>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
// Legacy aliases, undocumented but kept for retro-compatibility
|
||||
const aliases = {
|
||||
secondary: 'note',
|
||||
important: 'info',
|
||||
success: 'tip',
|
||||
warning: 'danger',
|
||||
} as const;
|
||||
|
||||
function getAdmonitionConfig(unsafeType: string): AdmonitionConfig {
|
||||
const type =
|
||||
(aliases as {[key: string]: Props['type']})[unsafeType] ?? unsafeType;
|
||||
const config = (AdmonitionConfigs as {[key: string]: AdmonitionConfig})[type];
|
||||
if (config) {
|
||||
return config;
|
||||
function getAdmonitionTypeComponent(type: string): ComponentType<Props> {
|
||||
const component = AdmonitionTypes[type];
|
||||
if (component) {
|
||||
return component;
|
||||
}
|
||||
console.warn(
|
||||
`No admonition config found for admonition type "${type}". Using Info as fallback.`,
|
||||
`No admonition component found for admonition type "${type}". Using Info as fallback.`,
|
||||
);
|
||||
return AdmonitionConfigs.info;
|
||||
return AdmonitionTypes.info!;
|
||||
}
|
||||
|
||||
// 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,
|
||||
children: rest,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Admonition(props: Props): JSX.Element {
|
||||
const {children, type, title, icon: iconProp} = processAdmonitionProps(props);
|
||||
|
||||
const typeConfig = getAdmonitionConfig(type);
|
||||
const titleLabel = title ?? typeConfig.label;
|
||||
const {iconComponent: IconComponent} = typeConfig;
|
||||
const icon = iconProp ?? <IconComponent />;
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
ThemeClassNames.common.admonition,
|
||||
ThemeClassNames.common.admonitionType(props.type),
|
||||
'alert',
|
||||
`alert--${typeConfig.infimaClassName}`,
|
||||
styles.admonition,
|
||||
)}>
|
||||
<div className={styles.admonitionHeading}>
|
||||
<span className={styles.admonitionIcon}>{icon}</span>
|
||||
{titleLabel}
|
||||
</div>
|
||||
<div className={styles.admonitionContent}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
export default function Admonition(unprocessedProps: Props): JSX.Element {
|
||||
const props = processAdmonitionProps(unprocessedProps);
|
||||
const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type);
|
||||
return <AdmonitionTypeComponent {...props} />;
|
||||
}
|
||||
|
|
|
@ -78,3 +78,5 @@ export {duplicates, uniq} from './utils/jsUtils';
|
|||
export {usePrismTheme} from './hooks/usePrismTheme';
|
||||
|
||||
export {useDocsPreferredVersion} from './contexts/docsPreferredVersion';
|
||||
|
||||
export {processAdmonitionProps} from './utils/admonitionUtils';
|
||||
|
|
|
@ -40,8 +40,7 @@ export const ThemeClassNames = {
|
|||
backToTopButton: 'theme-back-to-top-button',
|
||||
codeBlock: 'theme-code-block',
|
||||
admonition: 'theme-admonition',
|
||||
admonitionType: (type: 'note' | 'tip' | 'danger' | 'info' | 'caution') =>
|
||||
`theme-admonition-${type}`,
|
||||
admonitionType: (type: string) => `theme-admonition-${type}`,
|
||||
},
|
||||
layout: {
|
||||
// TODO add other stable classNames here
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* 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 React, {type ReactNode} from 'react';
|
||||
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
|
||||
export function processAdmonitionProps<
|
||||
Props extends {readonly children: ReactNode; readonly title?: ReactNode},
|
||||
>(props: Props): Props {
|
||||
const {mdxAdmonitionTitle, rest} = extractMDXAdmonitionTitle(props.children);
|
||||
const title = props.title ?? mdxAdmonitionTitle;
|
||||
return {
|
||||
...props,
|
||||
// Do not return "title: undefined" prop
|
||||
// this might create unwanted props overrides when merging props
|
||||
// For example: {...default,...props}
|
||||
...(title && {title}),
|
||||
children: rest,
|
||||
};
|
||||
}
|
|
@ -20,7 +20,7 @@ You now need to swizzle the admonitions component to provide UI customizations s
|
|||
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={"keywords":["custom-keyword"],"extendDefaults":42} 1`] = `""extendDefaults" must be a boolean"`;
|
||||
|
||||
exports[`validation schemas admonitionsSchema: for value={"tag":""} 1`] = `""tag" is not allowed to be empty"`;
|
||||
|
||||
|
|
|
@ -96,13 +96,20 @@ describe('validation schemas', () => {
|
|||
testOK({});
|
||||
testOK({tag: '+++'});
|
||||
testOK({keywords: ['info', 'tip']});
|
||||
testOK({keywords: ['info', 'tip'], extendDefaults: true});
|
||||
testOK({keywords: ['info', 'tip'], extendDefaults: false});
|
||||
testOK({keywords: []});
|
||||
testOK({keywords: [], extendDefaults: true}); // noop
|
||||
testOK({keywords: [], extendDefaults: false}); // disable admonitions
|
||||
testOK({tag: '+++', keywords: ['info', 'tip']});
|
||||
testOK({tag: '+++', keywords: ['custom-keyword'], extendDefaults: true});
|
||||
testOK({tag: '+++', keywords: ['custom-keyword'], extendDefaults: false});
|
||||
|
||||
testFail(3);
|
||||
testFail([]);
|
||||
testFail({unknownAttribute: 'val'});
|
||||
testFail({tag: ''});
|
||||
testFail({keywords: []});
|
||||
testFail({keywords: ['custom-keyword'], extendDefaults: 42});
|
||||
|
||||
// Legacy types
|
||||
testFail({
|
||||
|
|
|
@ -44,8 +44,12 @@ export const AdmonitionsSchema = JoiFrontMatter.alternatives()
|
|||
JoiFrontMatter.object({
|
||||
tag: JoiFrontMatter.string(),
|
||||
keywords: JoiFrontMatter.array().items(
|
||||
JoiFrontMatter.string().required(),
|
||||
JoiFrontMatter.string(),
|
||||
// Apparently this is how we tell job to accept empty arrays...
|
||||
// .required(),
|
||||
),
|
||||
extendDefaults: JoiFrontMatter.boolean(),
|
||||
|
||||
// TODO Remove before 2023
|
||||
customTypes: LegacyAdmonitionConfigSchema,
|
||||
icons: LegacyAdmonitionConfigSchema,
|
||||
|
|
|
@ -50,7 +50,7 @@ function sortComponentNames(componentNames: string[]): string[] {
|
|||
*
|
||||
* @param componentNames the original list of component names
|
||||
*/
|
||||
function getMissingIntermediateComponentFolderNames(
|
||||
export function getMissingIntermediateComponentFolderNames(
|
||||
componentNames: string[],
|
||||
): string[] {
|
||||
function getAllIntermediatePaths(componentName: string): string[] {
|
||||
|
|
|
@ -239,3 +239,9 @@ Can be arbitrarily nested:
|
|||
Admonition body
|
||||
|
||||
:::
|
||||
|
||||
:::important
|
||||
|
||||
Admonition alias `:::important` should have Important title
|
||||
|
||||
:::
|
||||
|
|
|
@ -13,7 +13,10 @@ import logger from '@docusaurus/logger';
|
|||
import classicTheme from '@docusaurus/theme-classic';
|
||||
|
||||
// Unsafe imports
|
||||
import {readComponentNames} from '@docusaurus/core/lib/commands/swizzle/components.js';
|
||||
import {
|
||||
readComponentNames,
|
||||
getMissingIntermediateComponentFolderNames,
|
||||
} from '@docusaurus/core/lib/commands/swizzle/components.js';
|
||||
import {normalizeSwizzleConfig} from '@docusaurus/core/lib/commands/swizzle/config.js';
|
||||
import {wrap, eject} from '@docusaurus/core/lib/commands/swizzle/actions.js';
|
||||
|
||||
|
@ -50,7 +53,33 @@ console.log('\n');
|
|||
|
||||
await fs.remove(toPath);
|
||||
|
||||
let componentNames = await readComponentNames(themePath);
|
||||
function filterComponentNames(componentNames) {
|
||||
// TODO temp workaround: non-comps should be forbidden to wrap
|
||||
if (action === 'wrap') {
|
||||
const WrapBlocklist = [
|
||||
'Layout', // Due to theme-fallback?
|
||||
];
|
||||
|
||||
return componentNames.filter((componentName) => {
|
||||
const blocked = WrapBlocklist.includes(componentName);
|
||||
if (blocked) {
|
||||
logger.warn(`${componentName} is blocked and will not be wrapped`);
|
||||
}
|
||||
return !blocked;
|
||||
});
|
||||
}
|
||||
return componentNames;
|
||||
}
|
||||
|
||||
async function getAllComponentNames() {
|
||||
const names = await readComponentNames(themePath);
|
||||
const allNames = names.concat(
|
||||
await getMissingIntermediateComponentFolderNames(names),
|
||||
);
|
||||
return filterComponentNames(allNames);
|
||||
}
|
||||
|
||||
const componentNames = await getAllComponentNames();
|
||||
|
||||
const componentsNotFound = Object.keys(swizzleConfig.components).filter(
|
||||
(componentName) => !componentNames.includes(componentName),
|
||||
|
@ -67,21 +96,6 @@ Please double-check or clean up these components from the config:
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
// TODO temp workaround: non-comps should be forbidden to wrap
|
||||
if (action === 'wrap') {
|
||||
const WrapBlocklist = [
|
||||
'Layout', // Due to theme-fallback?
|
||||
];
|
||||
|
||||
componentNames = componentNames.filter((componentName) => {
|
||||
const blocked = WrapBlocklist.includes(componentName);
|
||||
if (blocked) {
|
||||
logger.warn(`${componentName} is blocked and will not be wrapped`);
|
||||
}
|
||||
return !blocked;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} componentName
|
||||
*/
|
||||
|
|
|
@ -216,13 +216,13 @@ You can customize how each individual admonition type is rendered through [swizz
|
|||
```jsx title="src/theme/Admonition.js"
|
||||
import React from 'react';
|
||||
import Admonition from '@theme-original/Admonition';
|
||||
import MyIcon from '@site/static/img/info.svg';
|
||||
import MyCustomNoteIcon from '@site/static/img/info.svg';
|
||||
|
||||
export default function AdmonitionWrapper(props) {
|
||||
if (props.type !== 'info') {
|
||||
return <Admonition {...props} />;
|
||||
return <Admonition title="My Custom Admonition Title" {...props} />;
|
||||
}
|
||||
return <Admonition icon={<MyIcon />} {...props} />;
|
||||
return <Admonition icon={<MyCustomNoteIcon />} {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -240,6 +240,7 @@ module.exports = {
|
|||
admonitions: {
|
||||
tag: ':::',
|
||||
keywords: ['note', 'tip', 'info', 'caution', 'danger'],
|
||||
extendDefaults: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -248,9 +249,82 @@ module.exports = {
|
|||
};
|
||||
```
|
||||
|
||||
The plugin accepts two options:
|
||||
The plugin accepts the following options:
|
||||
|
||||
- `tag`: The tag that encloses the admonition. Defaults to `:::`.
|
||||
- `keywords`: An array of keywords that can be used as the type for the admonition. Note that if you override this, the default values will not be applied.
|
||||
- `keywords`: An array of keywords that can be used as the type for the admonition.
|
||||
- `extendDefaults`: Should the provided options (such as `keywords`) be merged into the existing defaults. Defaults to `false`.
|
||||
|
||||
The `keyword` will be passed as the `type` prop of the `Admonition` component. If you register more types than the default, you are also responsible for providing their implementation—including the container's style, icon, default title text, etc. You would usually need to [eject](../../swizzling.md#ejecting) the `@theme/Admonition` component, so you could re-use the same infrastructure as the other types.
|
||||
The `keyword` will be passed as the `type` prop of the `Admonition` component.
|
||||
|
||||
### Custom admonition type components {#custom-admonition-type-components}
|
||||
|
||||
By default, the theme doesn't know what do to with custom admonition keywords such as `:::my-custom-admonition`. It is your responsibility to map each admonition keyword to a React component so that the theme knows how to render them.
|
||||
|
||||
If you registered a new admonition type `my-custom-admonition` via the following config:
|
||||
|
||||
````js title="docusaurus.config.js"
|
||||
module.exports = {
|
||||
// ...
|
||||
presets: [
|
||||
[
|
||||
'classic',
|
||||
{
|
||||
// ...
|
||||
docs: {
|
||||
admonitions: {
|
||||
tag: ':::',
|
||||
keywords: ['my-custom-admonition'],
|
||||
extendDefaults: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
You can provide the corresponding React component for `:::my-custom-admonition` by creating the following file (unfortunately, since it's not a React component file, it's not swizzlable):
|
||||
|
||||
```js title="src/theme/Admonition/Types.js"
|
||||
import React from 'react';
|
||||
import DefaultAdmonitionTypes from '@theme-original/Admonition/Types';
|
||||
|
||||
function MyCustomAdmonition(props) {
|
||||
return (
|
||||
<div style={{border: 'solid red', padding: 10}}>
|
||||
<h5 style={{color: 'blue', fontSize: 30}}>{props.title}</h5>
|
||||
<div>{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const AdmonitionTypes = {
|
||||
...DefaultAdmonitionTypes,
|
||||
|
||||
// Add all your custom admonition types here...
|
||||
// You can also override the default ones if you want
|
||||
'my-custom-admonition': MyCustomAdmonition,
|
||||
};
|
||||
|
||||
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:
|
||||
|
||||
```md
|
||||
:::my-custom-admonition Custom Admonition
|
||||
|
||||
It works!
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
<BrowserWindow>
|
||||
|
||||
:::my-custom-admonition Custom Admonition
|
||||
|
||||
It works!
|
||||
|
||||
:::
|
||||
|
||||
</BrowserWindow>
|
||||
|
|
|
@ -303,6 +303,9 @@ const config = {
|
|||
const nextVersionDocsDirPath = 'docs';
|
||||
return `https://github.com/facebook/docusaurus/edit/main/website/${nextVersionDocsDirPath}/${docPath}`;
|
||||
},
|
||||
admonitions: {
|
||||
keywords: ['my-custom-admonition'],
|
||||
},
|
||||
showLastUpdateAuthor: true,
|
||||
showLastUpdateTime: true,
|
||||
remarkPlugins: [math, [npm2yarn, {sync: true}]],
|
||||
|
|
29
website/src/theme/Admonition/Types.tsx
Normal file
29
website/src/theme/Admonition/Types.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* 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 React from 'react';
|
||||
import type {Props} from '@theme/Admonition';
|
||||
import DefaultAdmonitionTypes from '@theme-original/Admonition/Types';
|
||||
|
||||
function MyCustomAdmonition(props: Props): JSX.Element {
|
||||
return (
|
||||
<div style={{border: 'solid red', padding: 10}}>
|
||||
<h5 style={{color: 'blue', fontSize: 30}}>{props.title}</h5>
|
||||
<div>{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const AdmonitionTypes = {
|
||||
...DefaultAdmonitionTypes,
|
||||
|
||||
// Add all your custom admonition types here...
|
||||
// you can also override the default ones
|
||||
'my-custom-admonition': MyCustomAdmonition,
|
||||
};
|
||||
|
||||
export default AdmonitionTypes;
|
Loading…
Add table
Add a link
Reference in a new issue