mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-01 03:08:17 +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.
|
||||
*/
|
||||
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import React, {isValidElement, useEffect, useState} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Highlight, {defaultProps, Language} from 'prism-react-renderer';
|
||||
import copy from 'copy-text-to-clipboard';
|
||||
|
@ -49,13 +49,44 @@ export default function CodeBlock({
|
|||
const codeBlockTitle = parseCodeBlockTitle(metastring) || title;
|
||||
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)
|
||||
? children.join('')
|
||||
: (children as string);
|
||||
|
||||
const language =
|
||||
parseLanguage(blockClassName) ?? (prism.defaultLanguage as Language);
|
||||
parseLanguage(blockClassName) ??
|
||||
(prism.defaultLanguage as Language | undefined);
|
||||
const {highlightLines, code} = parseLines(content, metastring, language);
|
||||
|
||||
const handleCopyCode = () => {
|
||||
|
@ -71,7 +102,7 @@ export default function CodeBlock({
|
|||
key={String(mounted)}
|
||||
theme={prismTheme}
|
||||
code={code}
|
||||
language={language}>
|
||||
language={language ?? ('text' as Language)}>
|
||||
{({className, style, tokens, getLineProps, getTokenProps}) => (
|
||||
<div
|
||||
className={clsx(
|
||||
|
|
|
@ -37,6 +37,11 @@
|
|||
border-radius: var(--ifm-global-radius);
|
||||
}
|
||||
|
||||
.codeBlockStandalone {
|
||||
padding: 0;
|
||||
border-radius: var(--ifm-global-radius);
|
||||
}
|
||||
|
||||
.copyButton {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: var(--ifm-global-radius);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, {ComponentProps, isValidElement, ReactElement} from 'react';
|
||||
import Head from '@docusaurus/Head';
|
||||
import Link from '@docusaurus/Link';
|
||||
import CodeBlock, {Props} from '@theme/CodeBlock';
|
||||
import CodeBlock from '@theme/CodeBlock';
|
||||
import Heading from '@theme/Heading';
|
||||
import Details from '@theme/Details';
|
||||
import type {MDXComponentsObject} from '@theme/MDXComponents';
|
||||
|
@ -33,40 +33,25 @@ const MDXComponents: MDXComponentsObject = {
|
|||
return <Head {...props}>{unwrappedChildren}</Head>;
|
||||
},
|
||||
code: (props) => {
|
||||
const {children} = props;
|
||||
|
||||
// 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} />
|
||||
const shouldBeInline = React.Children.toArray(props.children).every(
|
||||
(el) => typeof el === 'string' && !el.includes('\n'),
|
||||
);
|
||||
|
||||
return shouldBeInline ? <code {...props} /> : <CodeBlock {...props} />;
|
||||
},
|
||||
a: (props) => <Link {...props} />,
|
||||
pre: (props) => {
|
||||
const {children} = props;
|
||||
|
||||
// See comment for `code` above
|
||||
if (isValidElement(children) && isValidElement(children?.props?.children)) {
|
||||
return children.props.children;
|
||||
}
|
||||
|
||||
return (
|
||||
pre: (props) => (
|
||||
<CodeBlock
|
||||
{...((isValidElement(children)
|
||||
? children?.props
|
||||
: {...props}) as Props)}
|
||||
// If this pre is created by a ``` fenced codeblock, unwrap the children
|
||||
{...(isValidElement(props.children) &&
|
||||
props.children.props.originalType === 'code'
|
||||
? props.children?.props
|
||||
: {...props})}
|
||||
/>
|
||||
);
|
||||
},
|
||||
),
|
||||
details: (props): JSX.Element => {
|
||||
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(
|
||||
(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}
|
||||
|
||||
## 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
|
||||
|
||||
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}
|
||||
|
||||
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