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:
Joshua Chen 2021-12-25 15:27:29 +08:00 committed by GitHub
parent f02fefb5b7
commit 3889e89380
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 233 additions and 67 deletions

View file

@ -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(

View file

@ -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);

View file

@ -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',
)!;

View 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>

View file

@ -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>&#124;&#124;</code>

View file

@ -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.