返回 marp
CodeBlock.tsx
根目录 / website / components / CodeBlock.tsx
1 /* eslint-disable react/jsx-key */
2 import classNames from 'classnames'
3 import Highlight, {
4 defaultProps,
5 Language,
6 PrismTheme,
7 } from 'prism-react-renderer'
8 import nightOwlLight from 'prism-react-renderer/themes/nightOwlLight'
9 import { useRef, useState, MouseEvent } from 'react'
10 import { Button } from 'components/Button'
11
12 const theme: PrismTheme = {
13 plain: {
14 ...nightOwlLight.plain,
15 backgroundColor: '#f5f5f5',
16 },
17 styles: [
18 ...nightOwlLight.styles,
19 { types: ['italic'], style: { fontStyle: 'italic' } },
20 { types: ['important', 'bold'], style: { fontWeight: 'bold' } },
21 ],
22 }
23
24 export type CodeBlockProps = {
25 children: string
26 copyButton?: boolean
27 language: Language
28 lineNumber?: boolean
29 [key: string]: unknown
30 }
31
32 export const CodeBlock = ({
33 children,
34 className,
35 copyButton,
36 language,
37 lineNumber = false,
38 ...rest
39 }: CodeBlockProps) => {
40 const [copied, setCopied] = useState(false)
41 const copiedTimer = useRef<number | undefined>(undefined)
42
43 return (
44 <>
45 <Highlight
46 {...defaultProps}
47 code={children}
48 language={language}
49 theme={theme}
50 >
51 {({ className: cn, style, tokens, getLineProps, getTokenProps }) => (
52 <div className={classNames('code-block-container', className as any)}>
53 <pre
54 className={classNames(lineNumber && 'line-number', cn)}
55 style={style}
56 {...rest}
57 >
58 <code className="code-block">
59 <ol className="code-block">
60 {tokens.map((line, i) => {
61 const lineProps = getLineProps({ line, key: i })
62
63 return (
64 <li
65 {...lineProps}
66 className={classNames(
67 lineProps.className,
68 'code-block'
69 )}
70 >
71 {line.map((token, key) =>
72 token.empty ? (
73 <br key={key} />
74 ) : (
75 <span {...getTokenProps({ token, key })} />
76 )
77 )}
78 </li>
79 )
80 })}
81 </ol>
82 </code>
83 </pre>
84 {copyButton && (
85 <div className="copy-btn-container">
86 <Button
87 className={copied ? 'copied' : undefined}
88 onClick={(e: MouseEvent<HTMLButtonElement>) => {
89 const tmpTextarea = document.createElement('textarea')
90 tmpTextarea.value = children
91
92 tmpTextarea.style.position = 'absolute'
93 tmpTextarea.style.left = '0'
94 tmpTextarea.style.top = '0'
95 tmpTextarea.style.opacity = '0'
96 tmpTextarea.style.pointerEvents = 'none'
97
98 document.body.appendChild(tmpTextarea)
99 tmpTextarea.select()
100
101 document.execCommand('copy')
102 document.body.removeChild(tmpTextarea)
103 e.currentTarget.focus()
104
105 // Update React state
106 setCopied(true)
107
108 if (copiedTimer.current !== undefined) {
109 window.clearTimeout(copiedTimer.current)
110 }
111
112 copiedTimer.current = window.setTimeout(() => {
113 copiedTimer.current = undefined
114 setCopied(false)
115 }, 1000)
116 }}
117 >
118 {copied ? 'Copied!' : 'Copy'}
119 </Button>
120 </div>
121 )}
122 </div>
123 )}
124 </Highlight>
125 <style jsx>{`
126 .code-block-container {
127 @apply relative;
128 }
129
130 .prism-code {
131 @apply overflow-x-auto overflow-y-hidden whitespace-pre break-words rounded-md border text-sm leading-5;
132
133 font-family: inherit;
134 background-image: var(--noise-image);
135 }
136
137 .prism-code code {
138 @apply inline-block min-w-full p-4 font-mono;
139 }
140
141 .prism-code.line-number {
142 @apply whitespace-pre-wrap;
143 }
144
145 .prism-code.line-number ol {
146 counter-reset: line 0;
147 }
148
149 .prism-code.line-number li {
150 @apply relative pl-12;
151
152 counter-increment: line;
153 }
154
155 .prism-code.line-number li::before {
156 @apply absolute inset-0 w-12 pr-3 text-right text-xs leading-5 text-gray-500;
157
158 content: counter(line);
159 }
160
161 .copy-btn-container {
162 @apply absolute top-0 right-0 m-3;
163 }
164
165 .copy-btn-container :global(button) {
166 @apply w-24 py-1 text-xs opacity-0 transition-opacity duration-300;
167 }
168
169 .code-block-container:hover .copy-btn-container :global(button),
170 .copy-btn-container :global(button):focus {
171 @apply opacity-100;
172 }
173 `}</style>
174 </>
175 )
176 }
177
177 lines Plain Text