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

fix(theme-classic): make React elements in pre render correctly #6177

Merged
merged 6 commits into from
Dec 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
slorber marked this conversation as resolved.
Show resolved Hide resolved
{...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 = () => {
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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'),
);
},
a: (props) => <Link {...props} />,
pre: (props) => {
const {children} = props;

// See comment for `code` above
if (isValidElement(children) && isValidElement(children?.props?.children)) {
slorber marked this conversation as resolved.
Show resolved Hide resolved
return children.props.children;
}

return (
<CodeBlock
{...((isValidElement(children)
? children?.props
: {...props}) as Props)}
/>
);
return shouldBeInline ? <code {...props} /> : <CodeBlock {...props} />;
},
a: (props) => <Link {...props} />,
pre: (props) => (
<CodeBlock
// 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',
)!;
Expand Down
136 changes: 136 additions & 0 deletions website/_dogfooding/_pages tests/code-block-tests.mdx
Original file line number Diff line number Diff line change
@@ -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>
33 changes: 0 additions & 33 deletions website/_dogfooding/_pages tests/markdownPageTests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down