feat(theme-classic): code block showLineNumbers (#7007)

This commit is contained in:
Alexey Pyltsyn 2022-04-13 15:42:35 +03:00 committed by GitHub
parent 4d9a0edf21
commit ee4c984bc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 11 deletions

View file

@ -156,6 +156,7 @@ declare module '@theme/CodeBlock' {
readonly metastring?: string; readonly metastring?: string;
readonly title?: string; readonly title?: string;
readonly language?: string; readonly language?: string;
readonly showLineNumbers?: boolean;
} }
export default function CodeBlock(props: Props): JSX.Element; export default function CodeBlock(props: Props): JSX.Element;

View file

@ -13,6 +13,7 @@ import {
parseCodeBlockTitle, parseCodeBlockTitle,
parseLanguage, parseLanguage,
parseLines, parseLines,
containsLineNumbers,
ThemeClassNames, ThemeClassNames,
usePrismTheme, usePrismTheme,
} from '@docusaurus/theme-common'; } from '@docusaurus/theme-common';
@ -26,6 +27,7 @@ export default function CodeBlock({
className: blockClassName = '', className: blockClassName = '',
metastring, metastring,
title, title,
showLineNumbers,
language: languageProp, language: languageProp,
}: Props): JSX.Element { }: Props): JSX.Element {
const {prism} = useThemeConfig(); const {prism} = useThemeConfig();
@ -87,6 +89,8 @@ export default function CodeBlock({
const language = const language =
languageProp ?? parseLanguage(blockClassName) ?? prism.defaultLanguage; languageProp ?? parseLanguage(blockClassName) ?? prism.defaultLanguage;
const {highlightLines, code} = parseLines(content, metastring, language); const {highlightLines, code} = parseLines(content, metastring, language);
const shouldShowLineNumbers =
showLineNumbers || containsLineNumbers(metastring);
return ( return (
<Highlight <Highlight
@ -116,24 +120,45 @@ export default function CodeBlock({
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */ /* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
tabIndex={0} tabIndex={0}
className={clsx(className, styles.codeBlock, 'thin-scrollbar')}> className={clsx(className, styles.codeBlock, 'thin-scrollbar')}>
<code className={styles.codeBlockLines}> <code
className={clsx(
styles.codeBlockLines,
shouldShowLineNumbers && styles.codeBlockLinesWithNumbering,
)}>
{tokens.map((line, i) => { {tokens.map((line, i) => {
if (line.length === 1 && line[0]!.content === '\n') { if (line.length === 1 && line[0]!.content === '\n') {
line[0]!.content = ''; line[0]!.content = '';
} }
const lineProps = getLineProps({line, key: i}); const lineProps = getLineProps({
line,
key: i,
...(shouldShowLineNumbers && {className: styles.codeLine}),
});
if (highlightLines.includes(i)) { if (highlightLines.includes(i)) {
lineProps.className += ' docusaurus-highlight-code-line'; lineProps.className += ' docusaurus-highlight-code-line';
} }
const lineTokens = line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
));
return ( return (
<span key={i} {...lineProps}> <span key={i} {...lineProps}>
{line.map((token, key) => ( {shouldShowLineNumbers ? (
<span key={key} {...getTokenProps({token, key})} /> <>
))} <span className={styles.codeLineNumber} />
<br /> <span className={styles.codeLineContent}>
{lineTokens}
</span>
</>
) : (
<>
{lineTokens}
<br />
</>
)}
</span> </span>
); );
})} })}

View file

@ -28,9 +28,9 @@
} }
.codeBlock { .codeBlock {
--ifm-pre-background: inherit;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: inherit;
} }
.codeBlockTitle + .codeBlockContent .codeBlock { .codeBlockTitle + .codeBlockContent .codeBlock {
@ -44,14 +44,48 @@
.codeBlockLines { .codeBlockLines {
font: inherit; font: inherit;
background: var(--ifm-pre-background);
/* rtl:ignore */ /* rtl:ignore */
float: left; float: left;
min-width: 100%; min-width: 100%;
padding: var(--ifm-pre-padding); padding: var(--ifm-pre-padding);
} }
.codeBlockLinesWithNumbering {
display: table;
padding: var(--ifm-pre-padding) 0;
}
@media print { @media print {
.codeBlockLines { .codeBlockLines {
white-space: pre-wrap; white-space: pre-wrap;
} }
} }
.codeLine {
display: table-row;
counter-increment: line-count;
}
.codeLineNumber {
display: table-cell;
text-align: right;
width: 1%;
position: sticky;
left: 0;
padding: 0 var(--ifm-pre-padding);
background: var(--ifm-pre-background);
}
.codeLineNumber::before {
content: counter(line-count);
opacity: 0.4;
}
:global(.docusaurus-highlight-code-line) .codeLineNumber::before {
opacity: 0.8;
}
.codeLineContent {
padding-right: var(--ifm-pre-padding);
}

View file

@ -34,6 +34,7 @@ export {
parseCodeBlockTitle, parseCodeBlockTitle,
parseLanguage, parseLanguage,
parseLines, parseLines,
containsLineNumbers,
} from './utils/codeBlockUtils'; } from './utils/codeBlockUtils';
export { export {

View file

@ -77,6 +77,10 @@ export function parseCodeBlockTitle(metastring?: string): string {
return metastring?.match(codeBlockTitleRegex)?.groups!.title ?? ''; return metastring?.match(codeBlockTitleRegex)?.groups!.title ?? '';
} }
export function containsLineNumbers(metastring?: string): boolean {
return metastring?.includes('showLineNumbers') || false;
}
/** /**
* Gets the language name from the class name (set by MDX). * Gets the language name from the class name (set by MDX).
* e.g. `"language-javascript"` => `"javascript"`. * e.g. `"language-javascript"` => `"javascript"`.

View file

@ -142,3 +142,51 @@ Multi-line text inside `pre` will turn into one-liner, but it's okay (https://gi
</strong>cd", at which time b is a substring. </strong>cd", at which time b is a substring.
<br /> <br />
</CodeBlock> </CodeBlock>
## Code blocks with line numbering tests
```jsx
function PageLayout(props) {
// highlight-next-line
return <Layout title="Awesome Docusaurus page" description="Test Test Test Test Test Test Test Test Test Test Test Test Test Test ">;
}
```
```jsx showLineNumbers
function PageLayout(props) {
// highlight-next-line
return <Layout title="Awesome Docusaurus page" description="Test Test Test Test Test Test Test Test Test Test Test Test Test Test ">;
}
```
```jsx {1,3,6} showLineNumbers
function PageLayout(props) {
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
console.log(
'Test Test Test Test Test Test Test Test Test Test Test Test Test Test ',
);
}
```

View file

@ -275,6 +275,44 @@ In the future, we may extend the magic comment system and let you define custom
::: :::
## Line numbering {#line-numbering}
You can enable line numbering for your code block by using `showLineNumbers` key within the language meta string (don't forget to add space directly before the key).
````md
```jsx {1,4-6,11} showLineNumbers
import React from 'react';
function MyComponent(props) {
if (props.isBar) {
return <div>Bar</div>;
}
return <div>Foo</div>;
}
export default MyComponent;
```
````
<BrowserWindow>
```jsx {1,4-6,11} showLineNumbers
import React from 'react';
function MyComponent(props) {
if (props.isBar) {
return <div>Bar</div>;
}
return <div>Foo</div>;
}
export default MyComponent;
```
</BrowserWindow>
## Interactive code editor {#interactive-code-editor} ## Interactive code editor {#interactive-code-editor}
(Powered by [React Live](https://github.com/FormidableLabs/react-live)) (Powered by [React Live](https://github.com/FormidableLabs/react-live))
@ -596,7 +634,10 @@ export default function MyReactPage() {
return ( return (
<div> <div>
{/* highlight-start */} {/* highlight-start */}
<CodeBlock language="jsx" title="/src/components/HelloCodeTitle.js"> <CodeBlock
language="jsx"
title="/src/components/HelloCodeTitle.js"
showLineNumbers>
{`function HelloCodeTitle(props) { {`function HelloCodeTitle(props) {
return <h1>Hello, {props.name}</h1>; return <h1>Hello, {props.name}</h1>;
}`} }`}
@ -608,15 +649,18 @@ export default function MyReactPage() {
``` ```
<BrowserWindow> <BrowserWindow>
<CodeBlock language="jsx" title="/src/components/HelloCodeTitle.js"> <CodeBlock
language="jsx"
title="/src/components/HelloCodeTitle.js"
showLineNumbers>
{`function HelloCodeTitle(props) { {`function HelloCodeTitle(props) {
return <h1>Hello, {props.name}</h1>; return <h1>Hello, {props.name}</h1>;
}`} }`}
</CodeBlock> </CodeBlock>
</BrowserWindow> </BrowserWindow>
The props accepted are `language` and `title`, in the same way as you write Markdown code blocks. The props accepted are `language`, `title` and `showLineNumbers`, in the same way as you write Markdown code blocks.
Although discouraged, you can also pass in a `metastring` prop like `metastring='{1-2} title="/src/components/HelloCodeTitle.js"'`, which is how Markdown code blocks are handled under the hood. However, we recommend you [use comments for highlighting lines](#highlighting-with-comments). Although discouraged, you can also pass in a `metastring` prop like `metastring='{1-2} title="/src/components/HelloCodeTitle.js" showLineNumbers'`, which is how Markdown code blocks are handled under the hood. However, we recommend you [use comments for highlighting lines](#highlighting-with-comments).
As [previously stated](#using-jsx-markup), syntax highlighting is only applied when the children is a simple string. As [previously stated](#using-jsx-markup), syntax highlighting is only applied when the children is a simple string.