feat(v2): add filename in CodeBlock (#2346)

* feat: add filename in CodeBlock

* Fix code to use Regex to find title from markdown and Update style

* Fix reviewed point
- Delete unnecessary template literals
- Delete unnecessary "important!" from css

* Add title in live codeblock

* Just edit code order

* Add demo for code title

* Add docs about code title in markdown-features.mdx

* Make code title height scalable

* Rename codeBlockWrapper to codeBlockContent

* Make copyButton appear when hovering codeTitle

* Fix docs description about code title
This commit is contained in:
Kohhee Peace 2020-03-26 01:05:18 +08:00 committed by GitHub
parent 201c663318
commit 5e0d11dbaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 166 additions and 60 deletions

View file

@ -17,6 +17,7 @@ import useThemeContext from '@theme/hooks/useThemeContext';
import styles from './styles.module.css'; import styles from './styles.module.css';
const highlightLinesRangeRegex = /{([\d,-]+)}/; const highlightLinesRangeRegex = /{([\d,-]+)}/;
const codeBlockTitleRegex = /title=".*"/;
export default ({children, className: languageClassName, metastring}) => { export default ({children, className: languageClassName, metastring}) => {
const { const {
@ -41,6 +42,7 @@ export default ({children, className: languageClassName, metastring}) => {
const target = useRef(null); const target = useRef(null);
const button = useRef(null); const button = useRef(null);
let highlightLines = []; let highlightLines = [];
let codeBlockTitle = '';
const {isDarkTheme} = useThemeContext(); const {isDarkTheme} = useThemeContext();
const lightModeTheme = prism.theme || defaultTheme; const lightModeTheme = prism.theme || defaultTheme;
@ -52,6 +54,13 @@ export default ({children, className: languageClassName, metastring}) => {
highlightLines = rangeParser.parse(highlightLinesRange).filter(n => n > 0); highlightLines = rangeParser.parse(highlightLinesRange).filter(n => n > 0);
} }
if (metastring && codeBlockTitleRegex.test(metastring)) {
codeBlockTitle = metastring
.match(codeBlockTitleRegex)[0]
.split('title=')[1]
.replace(/"+/g, '');
}
useEffect(() => { useEffect(() => {
let clipboard; let clipboard;
@ -90,38 +99,51 @@ export default ({children, className: languageClassName, metastring}) => {
code={children.replace(/\n$/, '')} code={children.replace(/\n$/, '')}
language={language}> language={language}>
{({className, style, tokens, getLineProps, getTokenProps}) => ( {({className, style, tokens, getLineProps, getTokenProps}) => (
<pre className={classnames(className, styles.codeBlock)}> <>
<button {codeBlockTitle && (
ref={button} <div style={style} className={styles.codeBlockTitle}>
type="button" {codeBlockTitle}
aria-label="Copy code to clipboard" </div>
className={styles.copyButton} )}
onClick={handleCopyCode}> <div className={styles.codeBlockContent}>
{showCopied ? 'Copied' : 'Copy'} <button
</button> ref={button}
type="button"
aria-label="Copy code to clipboard"
className={classnames(styles.copyButton, {
[styles.copyButtonWithTitle]: codeBlockTitle,
})}
onClick={handleCopyCode}>
{showCopied ? 'Copied' : 'Copy'}
</button>
<pre
className={classnames(className, styles.codeBlock, {
[styles.codeBlockWithTitle]: codeBlockTitle,
})}>
<div ref={target} className={styles.codeBlockLines} style={style}>
{tokens.map((line, i) => {
if (line.length === 1 && line[0].content === '') {
line[0].content = '\n'; // eslint-disable-line no-param-reassign
}
<div ref={target} className={styles.codeBlockLines} style={style}> const lineProps = getLineProps({line, key: i});
{tokens.map((line, i) => {
if (line.length === 1 && line[0].content === '') {
line[0].content = '\n'; // eslint-disable-line no-param-reassign
}
const lineProps = getLineProps({line, key: i}); if (highlightLines.includes(i + 1)) {
lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`;
}
if (highlightLines.includes(i + 1)) { return (
lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`; <div key={i} {...lineProps}>
} {line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
return ( ))}
<div key={i} {...lineProps}> </div>
{line.map((token, key) => ( );
<span key={key} {...getTokenProps({token, key})} /> })}
))} </div>
</div> </pre>
);
})}
</div> </div>
</pre> </>
)} )}
</Highlight> </Highlight>
); );

View file

@ -5,6 +5,19 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
.codeBlockContent {
position: relative;
}
.codeBlockTitle {
border-top-left-radius: var(--ifm-pre-border-radius);
border-top-right-radius: var(--ifm-pre-border-radius);
font-weight: bold;
padding: 4px 12px;
border-bottom: 1px solid;
width: 100%;
}
.codeBlock { .codeBlock {
overflow: auto; overflow: auto;
display: block; display: block;
@ -12,6 +25,11 @@
margin: 0; margin: 0;
} }
.codeBlockWithTitle {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.copyButton { .copyButton {
background: rgb(1, 22, 39); background: rgb(1, 22, 39);
border: 1px solid rgb(214, 222, 235); border: 1px solid rgb(214, 222, 235);
@ -30,7 +48,12 @@
bottom 200ms ease-in-out; bottom 200ms ease-in-out;
} }
.codeBlock:hover > .copyButton { .copyButtonWithTitle {
top: calc(var(--ifm-pre-padding));
}
.codeBlockTitle:hover + .codeBlockContent .copyButton,
.codeBlockContent:hover > .copyButton {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }

View file

@ -18,6 +18,7 @@ import Playground from '@theme/Playground';
import styles from './styles.module.css'; import styles from './styles.module.css';
const highlightLinesRangeRegex = /{([\d,-]+)}/; const highlightLinesRangeRegex = /{([\d,-]+)}/;
const codeBlockTitleRegex = /title=".*"/;
export default ({ export default ({
children, children,
@ -48,6 +49,7 @@ export default ({
const target = useRef(null); const target = useRef(null);
const button = useRef(null); const button = useRef(null);
let highlightLines = []; let highlightLines = [];
let codeBlockTitle = '';
const {isDarkTheme} = useThemeContext(); const {isDarkTheme} = useThemeContext();
const lightModeTheme = prism.theme || defaultTheme; const lightModeTheme = prism.theme || defaultTheme;
@ -59,6 +61,13 @@ export default ({
highlightLines = rangeParser.parse(highlightLinesRange).filter(n => n > 0); highlightLines = rangeParser.parse(highlightLinesRange).filter(n => n > 0);
} }
if (metastring && codeBlockTitleRegex.test(metastring)) {
codeBlockTitle = metastring
.match(codeBlockTitleRegex)[0]
.split('title=')[1]
.replace(/"+/g, '');
}
useEffect(() => { useEffect(() => {
let clipboard; let clipboard;
@ -109,38 +118,51 @@ export default ({
code={children.replace(/\n$/, '')} code={children.replace(/\n$/, '')}
language={language}> language={language}>
{({className, style, tokens, getLineProps, getTokenProps}) => ( {({className, style, tokens, getLineProps, getTokenProps}) => (
<pre className={classnames(className, styles.codeBlock)}> <>
<button {codeBlockTitle && (
ref={button} <div style={style} className={styles.codeBlockTitle}>
type="button" {codeBlockTitle}
aria-label="Copy code to clipboard" </div>
className={styles.copyButton} )}
onClick={handleCopyCode}> <div className={styles.codeBlockContent}>
{showCopied ? 'Copied' : 'Copy'} <button
</button> ref={button}
type="button"
aria-label="Copy code to clipboard"
className={classnames(styles.copyButton, {
[styles.copyButtonWithTitle]: codeBlockTitle,
})}
onClick={handleCopyCode}>
{showCopied ? 'Copied' : 'Copy'}
</button>
<pre
className={classnames(className, styles.codeBlock, {
[styles.codeBlockWithTitle]: codeBlockTitle,
})}>
<div ref={target} className={styles.codeBlockLines} style={style}>
{tokens.map((line, i) => {
if (line.length === 1 && line[0].content === '') {
line[0].content = '\n'; // eslint-disable-line no-param-reassign
}
<div ref={target} className={styles.codeBlockLines} style={style}> const lineProps = getLineProps({line, key: i});
{tokens.map((line, i) => {
if (line.length === 1 && line[0].content === '') {
line[0].content = '\n'; // eslint-disable-line no-param-reassign
}
const lineProps = getLineProps({line, key: i}); if (highlightLines.includes(i + 1)) {
lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`;
}
if (highlightLines.includes(i + 1)) { return (
lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`; <div key={i} {...lineProps}>
} {line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
return ( ))}
<div key={i} {...lineProps}> </div>
{line.map((token, key) => ( );
<span key={key} {...getTokenProps({token, key})} /> })}
))} </div>
</div> </pre>
);
})}
</div> </div>
</pre> </>
)} )}
</Highlight> </Highlight>
); );

View file

@ -5,6 +5,19 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
.codeBlockContent {
position: relative;
}
.codeBlockTitle {
border-top-left-radius: var(--ifm-pre-border-radius);
border-top-right-radius: var(--ifm-pre-border-radius);
font-weight: bold;
padding: 4px 12px;
border-bottom: 1px solid;
width: 100%;
}
.codeBlock { .codeBlock {
overflow: auto; overflow: auto;
display: block; display: block;
@ -12,6 +25,11 @@
margin: 0; margin: 0;
} }
.codeBlockWithTitle {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.copyButton { .copyButton {
background: rgb(1, 22, 39); background: rgb(1, 22, 39);
border: 1px solid rgb(214, 222, 235); border: 1px solid rgb(214, 222, 235);
@ -30,7 +48,12 @@
bottom 200ms ease-in-out; bottom 200ms ease-in-out;
} }
.codeBlock:hover > .copyButton { .copyButtonWithTitle {
top: calc(var(--ifm-pre-padding));
}
.codeBlockTitle:hover + .codeBlockContent .copyButton,
.codeBlockContent:hover > .copyButton {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
} }

View file

@ -314,6 +314,23 @@ function MyComponent(props) {
export default MyComponent; export default MyComponent;
``` ```
### Code title
You can add title to code block by adding `title` key after the language (leave a space between them).
```jsx title="src/components/HelloCodeTitle.js"
function HelloCodeTitle(props) {
return <h1>Hello, {props.name}</h1>;
}
```
```jsx title="src/components/HelloCodeTitle.js"
function HelloCodeTitle(props) {
return <h1>Hello, {props.name}</h1>;
}
```
### 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))

View file

@ -36,8 +36,7 @@ This provides a clear distinction between Docusaurus' official packages and comm
Meanwhile, the default doc site functionalities provided by Docusaurus 1 are now provided by `@docusaurus/preset-classic`. Therefore, we need to add this dependency as well: Meanwhile, the default doc site functionalities provided by Docusaurus 1 are now provided by `@docusaurus/preset-classic`. Therefore, we need to add this dependency as well:
```json ```json title="package.json"
// package.json
{ {
dependencies: { dependencies: {
- "docusaurus": "^1.x.x", - "docusaurus": "^1.x.x",