mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 11:18:24 +02:00
fix(theme-classic): make React elements in pre render correctly (#6177)
* fix(theme-classic): make React elements in pre render correctly * Properly fix * Use MDX * Add docs * Better comment * Update code-block-tests.mdx
This commit is contained in:
parent
f02fefb5b7
commit
3889e89380
6 changed files with 233 additions and 67 deletions
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {isValidElement, useEffect, useState} from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import Highlight, {defaultProps, Language} from 'prism-react-renderer';
|
import Highlight, {defaultProps, Language} from 'prism-react-renderer';
|
||||||
import copy from 'copy-text-to-clipboard';
|
import copy from 'copy-text-to-clipboard';
|
||||||
|
@ -49,13 +49,44 @@ export default function CodeBlock({
|
||||||
const codeBlockTitle = parseCodeBlockTitle(metastring) || title;
|
const codeBlockTitle = parseCodeBlockTitle(metastring) || title;
|
||||||
const prismTheme = usePrismTheme();
|
const prismTheme = usePrismTheme();
|
||||||
|
|
||||||
// In case interleaved Markdown (e.g. when using CodeBlock as standalone component).
|
// <pre> tags in markdown map to CodeBlocks and they may contain JSX children.
|
||||||
|
// When the children is not a simple string, we just return a styled block without actually highlighting.
|
||||||
|
if (React.Children.toArray(children).some((el) => isValidElement(el))) {
|
||||||
|
return (
|
||||||
|
<Highlight
|
||||||
|
{...defaultProps}
|
||||||
|
key={String(mounted)}
|
||||||
|
theme={prismTheme}
|
||||||
|
code=""
|
||||||
|
language={'text' as Language}>
|
||||||
|
{({className, style}) => (
|
||||||
|
<pre
|
||||||
|
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
|
||||||
|
tabIndex={0}
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
styles.codeBlockStandalone,
|
||||||
|
'thin-scrollbar',
|
||||||
|
styles.codeBlockContainer,
|
||||||
|
blockClassName,
|
||||||
|
ThemeClassNames.common.codeBlock,
|
||||||
|
)}
|
||||||
|
style={style}>
|
||||||
|
<code className={styles.codeBlockLines}>{children}</code>
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</Highlight>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The children is now guaranteed to be one/more plain strings
|
||||||
const content = Array.isArray(children)
|
const content = Array.isArray(children)
|
||||||
? children.join('')
|
? children.join('')
|
||||||
: (children as string);
|
: (children as string);
|
||||||
|
|
||||||
const language =
|
const language =
|
||||||
parseLanguage(blockClassName) ?? (prism.defaultLanguage as Language);
|
parseLanguage(blockClassName) ??
|
||||||
|
(prism.defaultLanguage as Language | undefined);
|
||||||
const {highlightLines, code} = parseLines(content, metastring, language);
|
const {highlightLines, code} = parseLines(content, metastring, language);
|
||||||
|
|
||||||
const handleCopyCode = () => {
|
const handleCopyCode = () => {
|
||||||
|
@ -71,7 +102,7 @@ export default function CodeBlock({
|
||||||
key={String(mounted)}
|
key={String(mounted)}
|
||||||
theme={prismTheme}
|
theme={prismTheme}
|
||||||
code={code}
|
code={code}
|
||||||
language={language}>
|
language={language ?? ('text' as Language)}>
|
||||||
{({className, style, tokens, getLineProps, getTokenProps}) => (
|
{({className, style, tokens, getLineProps, getTokenProps}) => (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|
|
@ -37,6 +37,11 @@
|
||||||
border-radius: var(--ifm-global-radius);
|
border-radius: var(--ifm-global-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.codeBlockStandalone {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: var(--ifm-global-radius);
|
||||||
|
}
|
||||||
|
|
||||||
.copyButton {
|
.copyButton {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
border-radius: var(--ifm-global-radius);
|
border-radius: var(--ifm-global-radius);
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import React, {ComponentProps, isValidElement, ReactElement} from 'react';
|
import React, {ComponentProps, isValidElement, ReactElement} from 'react';
|
||||||
import Head from '@docusaurus/Head';
|
import Head from '@docusaurus/Head';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import CodeBlock, {Props} from '@theme/CodeBlock';
|
import CodeBlock from '@theme/CodeBlock';
|
||||||
import Heading from '@theme/Heading';
|
import Heading from '@theme/Heading';
|
||||||
import Details from '@theme/Details';
|
import Details from '@theme/Details';
|
||||||
import type {MDXComponentsObject} from '@theme/MDXComponents';
|
import type {MDXComponentsObject} from '@theme/MDXComponents';
|
||||||
|
@ -33,40 +33,25 @@ const MDXComponents: MDXComponentsObject = {
|
||||||
return <Head {...props}>{unwrappedChildren}</Head>;
|
return <Head {...props}>{unwrappedChildren}</Head>;
|
||||||
},
|
},
|
||||||
code: (props) => {
|
code: (props) => {
|
||||||
const {children} = props;
|
const shouldBeInline = React.Children.toArray(props.children).every(
|
||||||
|
(el) => typeof el === 'string' && !el.includes('\n'),
|
||||||
// For retrocompatibility purposes (pretty rare use case)
|
|
||||||
// See https://github.com/facebook/docusaurus/pull/1584
|
|
||||||
if (isValidElement(children)) {
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !children.includes('\n') ? (
|
|
||||||
<code {...props} />
|
|
||||||
) : (
|
|
||||||
<CodeBlock {...props} />
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return shouldBeInline ? <code {...props} /> : <CodeBlock {...props} />;
|
||||||
},
|
},
|
||||||
a: (props) => <Link {...props} />,
|
a: (props) => <Link {...props} />,
|
||||||
pre: (props) => {
|
pre: (props) => (
|
||||||
const {children} = props;
|
|
||||||
|
|
||||||
// See comment for `code` above
|
|
||||||
if (isValidElement(children) && isValidElement(children?.props?.children)) {
|
|
||||||
return children.props.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
{...((isValidElement(children)
|
// If this pre is created by a ``` fenced codeblock, unwrap the children
|
||||||
? children?.props
|
{...(isValidElement(props.children) &&
|
||||||
: {...props}) as Props)}
|
props.children.props.originalType === 'code'
|
||||||
|
? props.children?.props
|
||||||
|
: {...props})}
|
||||||
/>
|
/>
|
||||||
);
|
),
|
||||||
},
|
|
||||||
details: (props): JSX.Element => {
|
details: (props): JSX.Element => {
|
||||||
const items = React.Children.toArray(props.children) as ReactElement[];
|
const items = React.Children.toArray(props.children) as ReactElement[];
|
||||||
// Split summary item from the rest to pass it as a separate prop to the Detais theme component
|
// Split summary item from the rest to pass it as a separate prop to the Details theme component
|
||||||
const summary: ReactElement<ComponentProps<'summary'>> = items.find(
|
const summary: ReactElement<ComponentProps<'summary'>> = items.find(
|
||||||
(item) => item?.props?.mdxType === 'summary',
|
(item) => item?.props?.mdxType === 'summary',
|
||||||
)!;
|
)!;
|
||||||
|
|
136
website/_dogfooding/_pages tests/code-block-tests.mdx
Normal file
136
website/_dogfooding/_pages tests/code-block-tests.mdx
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
import CodeBlock from '@theme/CodeBlock';
|
||||||
|
import BrowserWindow from '@site/src/components/BrowserWindow';
|
||||||
|
|
||||||
|
# Code block tests
|
||||||
|
|
||||||
|
See:
|
||||||
|
|
||||||
|
- https://github.com/facebook/docusaurus/pull/1584
|
||||||
|
- https://github.com/facebook/docusaurus/pull/3749
|
||||||
|
- https://github.com/facebook/docusaurus/pull/6177
|
||||||
|
|
||||||
|
## `pre`
|
||||||
|
|
||||||
|
### `pre > string`
|
||||||
|
|
||||||
|
Multi-line text inside `pre` will turn into one-liner, but it's okay (https://github.com/mdx-js/mdx/issues/1095)
|
||||||
|
|
||||||
|
<pre>1 2 3</pre>
|
||||||
|
|
||||||
|
<!-- prettier-ignore -->
|
||||||
|
<pre>
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
### `pre > string[]`
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
1{'\n'}2{'\n'}3{'\n'}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
### `pre > element`
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<BrowserWindow url="http://localhost:3000">Lol bro</BrowserWindow>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
### `pre > element[]`
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<a href="/">Front page</a>
|
||||||
|
{'\n'}
|
||||||
|
<strong>Input: </strong>a = "abcd", b = "cdabcdab"{'\n'}
|
||||||
|
<strong>Output: </strong>3{'\n'}
|
||||||
|
<strong>Explanation: </strong>a after three repetitions become "ab
|
||||||
|
<strong>cdabcdab</strong>cd", at which time b is a substring.{'\n'}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
### `pre > code > element`
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<code>
|
||||||
|
<b>Hey bro</b>
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## `code`
|
||||||
|
|
||||||
|
### `code > string`
|
||||||
|
|
||||||
|
<code>1 2 3</code>
|
||||||
|
|
||||||
|
<code>
|
||||||
|
{`link:
|
||||||
|
title: front page
|
||||||
|
path: /docs/`}
|
||||||
|
</code>
|
||||||
|
|
||||||
|
### `code > string[]`
|
||||||
|
|
||||||
|
<code>
|
||||||
|
link:{' \n'}
|
||||||
|
{' '}title: front page{'\n'}
|
||||||
|
{' '}path: /docs/{'\n'}
|
||||||
|
</code>
|
||||||
|
|
||||||
|
### `code > element`
|
||||||
|
|
||||||
|
<code>
|
||||||
|
<BrowserWindow url="http://localhost:3000">Lol bro</BrowserWindow>
|
||||||
|
</code>
|
||||||
|
|
||||||
|
### `code > element[]`
|
||||||
|
|
||||||
|
<code>
|
||||||
|
<a href="/">Front page</a>
|
||||||
|
<br />
|
||||||
|
<strong>Input: </strong>a = "abcd", b = "cdabcdab"
|
||||||
|
<br />
|
||||||
|
<strong>Output: </strong>3<br />
|
||||||
|
<strong>Explanation: </strong>a after three repetitions become "ab<strong>
|
||||||
|
cdabcdab
|
||||||
|
</strong>cd", at which time b is a substring.
|
||||||
|
<br />
|
||||||
|
</code>
|
||||||
|
|
||||||
|
## `CodeBlock`
|
||||||
|
|
||||||
|
### `CodeBlock > string`
|
||||||
|
|
||||||
|
<CodeBlock>1 2 3</CodeBlock>
|
||||||
|
|
||||||
|
<CodeBlock className="language-yaml" title="test">
|
||||||
|
{`link:
|
||||||
|
title: front page
|
||||||
|
path: /docs/`}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
### `CodeBlock > string[]`
|
||||||
|
|
||||||
|
<CodeBlock className="language-yaml" title="test">
|
||||||
|
link:{'\n'}
|
||||||
|
{' '}title: front page{'\n'}
|
||||||
|
{' '}path: /docs/{'\n'}
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
### `CodeBlock > element`
|
||||||
|
|
||||||
|
<CodeBlock className="language-yaml" title="test">
|
||||||
|
<BrowserWindow url="http://localhost:3000">Lol bro</BrowserWindow>
|
||||||
|
</CodeBlock>
|
||||||
|
|
||||||
|
### `CodeBlock > element[]`
|
||||||
|
|
||||||
|
<CodeBlock className="language-yaml" title="test">
|
||||||
|
<a href="/">Front page</a>
|
||||||
|
<br />
|
||||||
|
<strong>Input: </strong>a = "abcd", b = "cdabcdab"
|
||||||
|
<br />
|
||||||
|
<strong>Output: </strong>3<br />
|
||||||
|
<strong>Explanation: </strong>a after three repetitions become "ab<strong>
|
||||||
|
cdabcdab
|
||||||
|
</strong>cd", at which time b is a substring.
|
||||||
|
<br />
|
||||||
|
</CodeBlock>
|
|
@ -160,41 +160,8 @@ function Clock(props) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<CodeBlock className="language-yaml" title="test">
|
|
||||||
test
|
|
||||||
</CodeBlock>
|
|
||||||
|
|
||||||
<code>test</code>
|
|
||||||
|
|
||||||
## direct using of `pre`
|
|
||||||
|
|
||||||
<pre>test</pre>
|
|
||||||
|
|
||||||
<!-- Multi-line text inside `pre` will turn into one-liner, but it's okay (https://github.com/mdx-js/mdx/issues/1095) -->
|
|
||||||
<pre>
|
|
||||||
1
|
|
||||||
2
|
|
||||||
3
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
## Custom heading id {#custom}
|
## Custom heading id {#custom}
|
||||||
|
|
||||||
## Children elements inside pre/code elements
|
|
||||||
|
|
||||||
See https://github.com/facebook/docusaurus/pull/1584
|
|
||||||
|
|
||||||
<pre><code>
|
|
||||||
<BrowserWindow url="http://localhost:3000" >
|
|
||||||
Lol bro
|
|
||||||
</BrowserWindow>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<code>
|
|
||||||
<BrowserWindow url="http://localhost:3000" >
|
|
||||||
Lol bro
|
|
||||||
</BrowserWindow>
|
|
||||||
</code>
|
|
||||||
|
|
||||||
## Pipe
|
## Pipe
|
||||||
|
|
||||||
Code tag + double pipe: <code>||</code>
|
Code tag + double pipe: <code>||</code>
|
||||||
|
|
|
@ -362,6 +362,48 @@ function MyPlayground(props) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Using JSX markup in code blocks
|
||||||
|
|
||||||
|
Code blocks in Markdown always preserves its content as plain text, meaning you can't do something like:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
type EditUrlFunction = (params: {
|
||||||
|
version: string;
|
||||||
|
// This doesn't turn into a link (for good reason!)
|
||||||
|
// See <a href="/docs/versioning">doc versioning</a>
|
||||||
|
versionDocsDirPath: string;
|
||||||
|
docPath: string;
|
||||||
|
permalink: string;
|
||||||
|
locale: string;
|
||||||
|
}) => string | undefined;
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to embed HTML markup such as anchor links or bold type, you can use the `<pre>` tag, `<code>` tag, or `<CodeBlock>` component.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<pre>
|
||||||
|
<b>Input: </b>1 2 3 4{'\n'}
|
||||||
|
<b>Output: </b>"366300745"{'\n'}
|
||||||
|
</pre>
|
||||||
|
```
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<b>Input: </b>1 2 3 4{'\n'}
|
||||||
|
<b>Output: </b>"366300745"{'\n'}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
:::caution MDX is whitespace insensitive
|
||||||
|
|
||||||
|
MDX is in line with JSX behavior: line break characters, even when inside `<pre>`, are turned into spaces. You have to explicitly write the new line character for it to be printed out.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::caution
|
||||||
|
|
||||||
|
Syntax highlighting only works on plain strings. Docusaurus will not attempt to parse code block content containing JSX children.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Multi-language support code blocks {#multi-language-support-code-blocks}
|
## Multi-language support code blocks {#multi-language-support-code-blocks}
|
||||||
|
|
||||||
With MDX, you can easily create interactive components within your documentation, for example, to display code in multiple programming languages and switching between them using a tabs component.
|
With MDX, you can easily create interactive components within your documentation, for example, to display code in multiple programming languages and switching between them using a tabs component.
|
||||||
|
|
Loading…
Add table
Reference in a new issue