-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
index.tsx
124 lines (117 loc) · 3.44 KB
/
index.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { ReactComponent as IconCheck } from '@ant-design/icons-svg/inline-svg/outlined/check.svg';
import { ReactComponent as IconCopy } from '@ant-design/icons-svg/inline-svg/outlined/copy.svg';
import classNames from 'classnames';
import { useSiteData } from 'dumi';
import Highlight, { defaultProps, type Language } from 'prism-react-renderer';
import 'prism-themes/themes/prism-one-light.css';
import React, {
useEffect,
useRef,
useState,
type FC,
type ReactNode,
} from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import './index.less';
/**
* define DSL which can be highlighted as similar language
*/
const SIMILAR_DSL: Record<string, Language> = {
acss: 'css',
axml: 'markup',
vue: 'markup',
};
interface SourceCodeProps {
children: string;
lang: Language;
highlightLines?: number[];
extra?: ReactNode;
textarea?: ReactNode;
}
const SourceCode: FC<SourceCodeProps> = (props) => {
const { children = '', lang, highlightLines = [] } = props;
const timer = useRef<number>();
const [isCopied, setIsCopied] = useState(false);
const [text, setText] = useState(children);
const { themeConfig } = useSiteData();
useEffect(() => {
const isShell = /shellscript|shell|bash|sh|zsh/.test(lang);
if (isShell) {
const text = children.replace(/^(\$|>)\s/gm, '');
setText(text);
}
}, [lang, children]);
const code = (
<Highlight
{...defaultProps}
code={children}
language={SIMILAR_DSL[lang] || lang}
theme={undefined}
>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} style={style}>
{tokens.map((line, i) => (
<div
key={String(i)}
className={classNames({
highlighted: highlightLines.includes(i + 1),
wrap: themeConfig.showLineNum,
})}
>
{themeConfig.showLineNum && (
<span className="token-line-num">{i + 1}</span>
)}
<div
{...getLineProps({
line,
key: i,
})}
className={classNames({
'line-cell': themeConfig.showLineNum,
})}
>
{line.map((token, key) => (
// getTokenProps 返回值包含 key
// eslint-disable-next-line react/jsx-key
<span {...getTokenProps({ token, key })} />
))}
</div>
</div>
))}
</pre>
)}
</Highlight>
);
return (
<div className="dumi-default-source-code">
<CopyToClipboard
text={text}
onCopy={() => {
setIsCopied(true);
clearTimeout(timer.current);
timer.current = window.setTimeout(() => setIsCopied(false), 2000);
}}
>
<button
type="button"
className="dumi-default-source-code-copy"
data-copied={isCopied || undefined}
>
{isCopied ? <IconCheck /> : <IconCopy />}
</button>
</CopyToClipboard>
{props.textarea ? (
<div className="dumi-default-source-code-scroll-container">
<div className="dumi-default-source-code-scroll-content">
{code}
{props.textarea}
</div>
</div>
) : (
code
)}
{props.extra}
</div>
);
};
export default SourceCode;