Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: support mermaid code blocks in Markdown #7490

Merged
merged 33 commits into from Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
54ee20f
feat: add mermaid diagram support ootb #1258
sjwall May 24, 2022
a003697
feat(mdx-loader): mermaid rendering optional
sjwall May 26, 2022
ed1f044
refactor: move mermaid components to own package
sjwall May 27, 2022
ed1f21c
refactor: remove theme-classic mermaid dependency
sjwall May 27, 2022
777c086
refactor(mdx-loader): markdown config option
sjwall May 27, 2022
3a32f60
refactor(theme-mermaid): remove exports
sjwall May 27, 2022
11836a0
little refactors
Josh-Cena May 27, 2022
5d836f5
Merge branch 'main' into mermaid
Josh-Cena May 27, 2022
8c9e005
remove unused
Josh-Cena May 27, 2022
722f75b
remark plugin refactor
Josh-Cena May 29, 2022
c0a17ac
Merge branch 'main' into mermaid
Josh-Cena May 29, 2022
0a5e372
greatly simplify
Josh-Cena May 29, 2022
73f53a8
simplify API
Josh-Cena May 29, 2022
ec6f920
fix tests
Josh-Cena May 29, 2022
47bec9b
move dogfooding
Josh-Cena May 29, 2022
be53cf3
Merge branch 'main' into mermaid
sjwall Jun 6, 2022
addacc6
Merge branch 'main' into mermaid
Josh-Cena Jun 15, 2022
51b6c48
Merge branch 'main' into mermaid
slorber Oct 7, 2022
5ec7c0b
mermaid.mermaidOptions => mermaid.options
slorber Oct 13, 2022
25cb8aa
validate config.markdown.mermaid
slorber Oct 13, 2022
56ca9c7
do not spread markdown config to mdx loader: be more explicit with at…
slorber Oct 13, 2022
9772c2e
typo
slorber Oct 13, 2022
5d49364
temp better mermaid integration
slorber Oct 13, 2022
062ed20
expose mermaid hooks as client apis instead of themes
slorber Oct 13, 2022
4da56ed
fix snapshots
slorber Oct 14, 2022
b6c408d
good mermaid integration
slorber Oct 14, 2022
d5ca004
increase random count
slorber Oct 14, 2022
76c8703
add max-width 100% to container/svg
slorber Oct 14, 2022
c8d1e2c
try to fix journey bug
slorber Oct 14, 2022
bd67df1
fix bad project-words merge
slorber Oct 14, 2022
8c88c55
refactor MDXComponents usage
slorber Oct 14, 2022
57d481c
add mermaid in tabs dogfood, see https://github.com/sjwall/mdx-mermai…
slorber Oct 14, 2022
1377f4a
````mdx-code-block
slorber Oct 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/docusaurus-mdx-loader/package.json
Expand Up @@ -39,6 +39,7 @@
"@docusaurus/types": "2.0.0-beta.20",
"@types/escape-html": "^1.0.2",
"@types/mdast": "^3.0.10",
"@types/mermaid": "^8.2.9",
"@types/stringify-object": "^3.3.1",
"@types/unist": "^2.0.6",
"remark": "^12.0.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/docusaurus-mdx-loader/src/loader.ts
Expand Up @@ -22,6 +22,7 @@ import toc from './remark/toc';
import unwrapMdxCodeBlocks from './remark/unwrapMdxCodeBlocks';
import transformImage from './remark/transformImage';
import transformLinks from './remark/transformLinks';
import mermaid from './remark/mermaid';

import type {LoaderContext} from 'webpack';
import type {Processor, Plugin} from 'unified';
Expand All @@ -38,7 +39,7 @@ const pragma = `

const DEFAULT_OPTIONS: MDXOptions = {
rehypePlugins: [],
remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc],
remarkPlugins: [unwrapMdxCodeBlocks, emoji, headings, toc, mermaid],
beforeDefaultRemarkPlugins: [],
beforeDefaultRehypePlugins: [],
};
Expand Down
@@ -0,0 +1,62 @@
/**
* 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 {createCompiler} from '@mdx-js/mdx';
import mermaid from '..';

describe('mermaid remark plugin', () => {
function createTestCompiler() {
return createCompiler({
remarkPlugins: [mermaid],
});
}

it('no mermaid', async () => {
const mdxCompiler = createTestCompiler();
const result = await mdxCompiler.process(
'# Heading 1\n\nNo Mermaid diagram :(',
);
expect(result.contents).toBe(
'\n\n\nconst layoutProps = {\n \n};\nconst MDXLayout = "wrapper"\nexport default function MDXContent({\n components,\n ...props\n}) {\n return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">\n <h1>{`Heading 1`}</h1>\n <p>{`No Mermaid diagram :(`}</p>\n </MDXLayout>;\n}\n\n;\nMDXContent.isMDXComponent = true;',
);
});

it('basic', async () => {
const mdxCompiler = createTestCompiler();
const result = await mdxCompiler.process(`# Heading 1\n
\`\`\`mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
\`\`\``);
expect(result.contents).toBe(`


const layoutProps = {
${' '}
};
const MDXLayout = "wrapper"
export default function MDXContent({
components,
...props
}) {
return <MDXLayout {...layoutProps} {...props} components={components} mdxType="MDXLayout">
<h1>{\`Heading 1\`}</h1>
<mermaid value={\`graph TD;
A-->B;
A-->C;
B-->D;
C-->D;\`} />
</MDXLayout>;
}

;
MDXContent.isMDXComponent = true;`);
});
});
48 changes: 48 additions & 0 deletions packages/docusaurus-mdx-loader/src/remark/mermaid/index.ts
@@ -0,0 +1,48 @@
/**
* 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} from 'unified';
import type {Data, Literal, Node, Parent} from 'unist';

type CodeMermaid = Literal<string> & {
type: 'code';
lang: 'mermaid';
};

function processMermaidNode(
node: CodeMermaid,
index: number,
parent: Parent<Node<Data> | Literal, Data>,
) {
parent.children.splice(index, 1, {
type: 'jsx',
value: `<mermaid value={\`${node.value}\`}/>`,
position: node.position,
});
}

export default function plugin(): Transformer {
return async (root) => {
// Find all the mermaid diagram code blocks. i.e. ```mermaid
const instances: [CodeMermaid, number, Parent<Node<Data>, Data>][] = [];
visit(
root,
{type: 'code', lang: 'mermaid'},
(node: CodeMermaid, index, parent) => {
if (parent) {
instances.push([node, index, parent]);
}
},
);

// Replace each Mermaid code block with the Mermaid component
instances.forEach(([node, index, parent]) => {
processMermaidNode(node, index, parent);
});
};
}
2 changes: 2 additions & 0 deletions packages/docusaurus-theme-classic/package.json
Expand Up @@ -34,6 +34,7 @@
"copy-text-to-clipboard": "^3.0.1",
"infima": "0.2.0-alpha.39",
"lodash": "^4.17.21",
"mermaid": "^9.1.1",
"nprogress": "^0.2.0",
"postcss": "^8.4.14",
"prism-react-renderer": "^1.3.3",
Expand All @@ -46,6 +47,7 @@
"@docusaurus/module-type-aliases": "2.0.0-beta.20",
"@docusaurus/types": "2.0.0-beta.20",
"@types/mdx-js__react": "^1.5.5",
"@types/mermaid": "^8.2.9",
"@types/nprogress": "^0.2.0",
"@types/prismjs": "^1.26.0",
"@types/rtlcss": "^3.5.0",
Expand Down
22 changes: 22 additions & 0 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Expand Up @@ -552,6 +552,14 @@ declare module '@theme/Heading' {
export default function Heading(props: Props): JSX.Element;
}

declare module '@theme/Mermaid' {
export interface Props {
value: string;
}

export default function Mermaid(props: Props): JSX.Element;
}

declare module '@theme/Layout' {
import type {ReactNode} from 'react';

Expand Down Expand Up @@ -667,6 +675,14 @@ declare module '@theme/MDXComponents/Pre' {
export default function MDXPre(props: Props): JSX.Element;
}

declare module '@theme/MDXComponents/Mermaid' {
export interface Props {
value: string;
}

export default function MDXMermaid(props: Props): JSX.Element;
}

declare module '@theme/MDXComponents' {
import type {ComponentType, ComponentProps} from 'react';

Expand All @@ -677,6 +693,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 MDXMermaid from '@theme/MDXComponents/Mermaid';

export type MDXComponentsObject = {
readonly head: typeof MDXHead;
Expand All @@ -692,6 +709,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 mermaid: typeof MDXMermaid;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[tagName: string]: ComponentType<any>;
};
Expand Down Expand Up @@ -1278,3 +1296,7 @@ declare module '@theme/prism-include-languages' {
PrismObject: typeof PrismNamespace,
): void;
}

declare module '@theme/useMermaid' {
export default function useMermaid(): void;
}
2 changes: 2 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/Layout/index.tsx
Expand Up @@ -21,6 +21,7 @@ import LayoutProviders from '@theme/LayoutProviders';
import ErrorPageContent from '@theme/ErrorPageContent';
import './styles.css';
import type {Props} from '@theme/Layout';
import useMermaid from '@theme/useMermaid';

export default function Layout(props: Props): JSX.Element {
const {
Expand All @@ -33,6 +34,7 @@ export default function Layout(props: Props): JSX.Element {
} = props;

useKeyboardNavigation();
useMermaid();

return (
<LayoutProviders>
Expand Down
@@ -0,0 +1,14 @@
/**
* 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 Mermaid from '@theme/Mermaid';
import type {Props} from '@theme/MDXComponents/Mermaid';

export default function MDXMermaid(props: Props): JSX.Element {
return <Mermaid {...props} />;
}
Expand Up @@ -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 MDXMermaid from '@theme/MDXComponents/Mermaid';

import type {MDXComponentsObject} from '@theme/MDXComponents';

Expand All @@ -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} />,
mermaid: MDXMermaid,
Josh-Cena marked this conversation as resolved.
Show resolved Hide resolved
};

export default MDXComponents;
64 changes: 64 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/Mermaid/index.tsx
@@ -0,0 +1,64 @@
/**
* 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, {useEffect, useState} from 'react';
import useIsBrowser from '@docusaurus/useIsBrowser';
import mermaid from 'mermaid';
import type {Props} from '@theme/Mermaid';

/**
* Assign a unique ID to each mermaid svg as per requirements
* of `mermaid.render`.
*/
let id = 0;

export default function Mermaid({value}: Props): JSX.Element {
// When theme updates, rerender the SVG.
const [svg, setSvg] = useState<string>('');
const isBrowser = useIsBrowser();

useEffect(() => {
const render = () => {
mermaid.render(`mermaid-svg-${id.toString()}`, value, (renderedSvg) =>
setSvg(renderedSvg),
);
id += 1;
};

render();

if (isBrowser) {
const html: HTMLHtmlElement = document.querySelector('html')!;

const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (
mutation.type !== 'attributes' ||
mutation.attributeName !== 'data-mermaid'
) {
continue;
}
render();
break;
}
});

observer.observe(html, {attributes: true});
return () => {
try {
observer.disconnect();
} catch {
// Do nothing
}
};
}
return undefined;
}, [isBrowser, value]);

// eslint-disable-next-line react/no-danger
return <div dangerouslySetInnerHTML={{__html: svg}} />;
}