|
1 |
| -import { DocumentBlockCode, JSONDocument } from '@gitbook/api'; |
| 1 | +import type { DocumentBlockCode } from '@gitbook/api'; |
2 | 2 |
|
3 |
| -import { tcls } from '@/lib/tailwind'; |
| 3 | +import { getNodeFragmentByType } from '@/lib/document'; |
4 | 4 |
|
5 |
| -import { CopyCodeButton } from './CopyCodeButton'; |
6 |
| -import { highlight, HighlightLine, HighlightToken, plainHighlighting } from './highlight'; |
7 | 5 | import { BlockProps } from '../Block';
|
8 |
| -import { DocumentContext } from '../DocumentView'; |
9 |
| -import { Inline } from '../Inline'; |
10 |
| - |
11 |
| -import './theme.css'; |
| 6 | +import { ClientCodeBlock } from './ClientCodeBlock'; |
| 7 | +import { getInlines, RenderedInline } from './highlight'; |
| 8 | +import { Blocks } from '../Blocks'; |
| 9 | +import { ServerCodeBlock } from './ServerCodeBlock'; |
12 | 10 |
|
13 | 11 | /**
|
14 |
| - * Render an entire code-block. The syntax highlighting is done server-side. |
| 12 | + * Render a code block, can be client-side or server-side. |
15 | 13 | */
|
16 |
| -export async function CodeBlock(props: BlockProps<DocumentBlockCode>) { |
17 |
| - const { block, document, style, context } = props; |
18 |
| - const lines = await highlight(block); |
19 |
| - |
20 |
| - const id = block.key!; |
21 |
| - |
22 |
| - const withLineNumbers = !!block.data.lineNumbers && block.nodes.length > 1; |
23 |
| - const withWrap = block.data.overflow === 'wrap'; |
24 |
| - const title = block.data.title; |
25 |
| - const titleRoundingStyle = [ |
26 |
| - 'rounded-md', |
27 |
| - 'straight-corners:rounded-sm', |
28 |
| - title ? 'rounded-ss-none' : null, |
29 |
| - ]; |
30 |
| - |
31 |
| - return ( |
32 |
| - <div className={tcls('group/codeblock', 'grid', 'grid-flow-col', style)}> |
33 |
| - <div |
34 |
| - className={tcls( |
35 |
| - 'flex', |
36 |
| - 'items-center', |
37 |
| - 'justify-start', |
38 |
| - '[grid-area:1/1]', |
39 |
| - 'text-sm', |
40 |
| - 'gap-2', |
41 |
| - )} |
42 |
| - > |
43 |
| - {title ? ( |
44 |
| - <div |
45 |
| - className={tcls( |
46 |
| - 'text-xs', |
47 |
| - 'tracking-wide', |
48 |
| - 'text-dark/7', |
49 |
| - 'leading-none', |
50 |
| - 'inline-flex', |
51 |
| - 'items-center', |
52 |
| - 'justify-center', |
53 |
| - 'bg-light-2', |
54 |
| - 'rounded-t', |
55 |
| - 'straight-corners:rounded-t-s', |
56 |
| - 'px-3', |
57 |
| - 'py-2', |
58 |
| - 'dark:bg-dark-2', |
59 |
| - 'dark:text-light/7', |
60 |
| - )} |
61 |
| - > |
62 |
| - {title} |
63 |
| - </div> |
64 |
| - ) : null} |
65 |
| - </div> |
66 |
| - <CopyCodeButton |
67 |
| - codeId={id} |
68 |
| - style={[ |
69 |
| - 'group-hover/codeblock:opacity-[1]', |
70 |
| - 'transition-opacity', |
71 |
| - 'duration-75', |
72 |
| - 'opacity-0', |
73 |
| - 'text-xs', |
74 |
| - '[grid-area:2/1]', |
75 |
| - 'z-[2]', |
76 |
| - 'justify-self-end', |
77 |
| - 'backdrop-blur-md', |
78 |
| - 'leading-none', |
79 |
| - 'self-start', |
80 |
| - 'ring-1', |
81 |
| - 'ring-dark/2', |
82 |
| - 'text-dark/7', |
83 |
| - 'bg-transparent', |
84 |
| - 'rounded-md', |
85 |
| - 'mr-2', |
86 |
| - 'mt-2', |
87 |
| - 'p-1', |
88 |
| - 'hover:ring-dark/3', |
89 |
| - 'dark:ring-light/2', |
90 |
| - 'dark:text-light/7', |
91 |
| - 'dark:hover:ring-light/3', |
92 |
| - ]} |
93 |
| - /> |
94 |
| - <pre |
95 |
| - className={tcls( |
96 |
| - '[grid-area:2/1]', |
97 |
| - 'relative', |
98 |
| - 'overflow-auto', |
99 |
| - 'bg-light-2', |
100 |
| - 'dark:bg-dark-2', |
101 |
| - 'border-light-4', |
102 |
| - 'dark:border-dark-4', |
103 |
| - 'hide-scroll', |
104 |
| - titleRoundingStyle, |
105 |
| - )} |
106 |
| - > |
107 |
| - <code |
108 |
| - id={id} |
109 |
| - className={tcls( |
110 |
| - 'min-w-full', |
111 |
| - 'inline-grid', |
112 |
| - '[grid-template-columns:auto_1fr]', |
113 |
| - 'py-2', |
114 |
| - 'px-2', |
115 |
| - '[counter-reset:line]', |
116 |
| - withWrap ? 'whitespace-pre-wrap' : '', |
117 |
| - )} |
118 |
| - > |
119 |
| - {lines.map((line, index) => ( |
120 |
| - <CodeHighlightLine |
121 |
| - block={block} |
122 |
| - document={document} |
123 |
| - key={index} |
124 |
| - line={line} |
125 |
| - lineIndex={index + 1} |
126 |
| - isLast={index === lines.length - 1} |
127 |
| - withLineNumbers={withLineNumbers} |
128 |
| - withWrap={withWrap} |
129 |
| - context={context} |
130 |
| - /> |
131 |
| - ))} |
132 |
| - </code> |
133 |
| - </pre> |
134 |
| - </div> |
135 |
| - ); |
136 |
| -} |
137 |
| - |
138 |
| -function CodeHighlightLine(props: { |
139 |
| - block: DocumentBlockCode; |
140 |
| - document: JSONDocument; |
141 |
| - line: HighlightLine; |
142 |
| - lineIndex: number; |
143 |
| - isLast: boolean; |
144 |
| - withLineNumbers: boolean; |
145 |
| - withWrap: boolean; |
146 |
| - context: DocumentContext; |
147 |
| -}) { |
148 |
| - const { block, document, line, isLast, withLineNumbers, context } = props; |
149 |
| - return ( |
150 |
| - <span |
151 |
| - className={tcls( |
152 |
| - 'grid', |
153 |
| - '[grid-template-columns:subgrid]', |
154 |
| - 'col-span-2', |
155 |
| - 'relative', |
156 |
| - 'ring-1', |
157 |
| - 'ring-transparent', |
158 |
| - 'hover:ring-dark-4/5', |
159 |
| - 'hover:z-[1]', |
160 |
| - 'dark:hover:ring-light-4/4', |
161 |
| - 'rounded', |
162 |
| - //first child |
163 |
| - '[&.highlighted:first-child]:rounded-t-md', |
164 |
| - '[&.highlighted:first-child>*]:mt-1', |
165 |
| - //last child |
166 |
| - '[&.highlighted:last-child]:rounded-b-md', |
167 |
| - '[&.highlighted:last-child>*]:mb-1', |
168 |
| - //is only child, dont hover effect line |
169 |
| - '[&:only-child]:hover:ring-transparent', |
170 |
| - //select all highlighted |
171 |
| - '[&.highlighted]:rounded-none', |
172 |
| - //select first in group |
173 |
| - '[&:not(.highlighted)_+_.highlighted]:rounded-t-md', |
174 |
| - '[&:not(.highlighted)_+_.highlighted>*]:mt-1', |
175 |
| - //select last in group |
176 |
| - '[&.highlighted:has(+:not(.highlighted))]:rounded-b-md', |
177 |
| - '[&.highlighted:has(+:not(.highlighted))>*]:mb-1', |
178 |
| - //select if highlight is singular in group |
179 |
| - '[&:not(.highlighted)_+_.highlighted:has(+:not(.highlighted))]:rounded-md', |
180 |
| - |
181 |
| - line.highlighted ? ['highlighted', 'bg-light-3', 'dark:bg-dark-3'] : null, |
182 |
| - )} |
183 |
| - > |
184 |
| - {withLineNumbers ? ( |
185 |
| - <span |
186 |
| - className={tcls( |
187 |
| - 'text-sm', |
188 |
| - 'text-right', |
189 |
| - 'pr-3.5', |
190 |
| - 'rounded-l', |
191 |
| - 'pl-2', |
192 |
| - 'sticky', |
193 |
| - 'left-[-3px]', |
194 |
| - 'bg-gradient-to-r', |
195 |
| - 'from-80%', |
196 |
| - 'from-light-2', |
197 |
| - 'to-transparent', |
198 |
| - 'dark:from-dark-2', |
199 |
| - 'dark:to-transparent', |
200 |
| - withLineNumbers |
201 |
| - ? [ |
202 |
| - 'before:text-dark/5', |
203 |
| - 'before:content-[counter(line)]', |
204 |
| - '[counter-increment:line]', |
205 |
| - 'dark:before:text-light/4', |
206 |
| - |
207 |
| - line.highlighted |
208 |
| - ? [ |
209 |
| - 'before:text-dark/6', |
210 |
| - 'dark:before:text-light/8', |
211 |
| - 'bg-gradient-to-r', |
212 |
| - 'from-80%', |
213 |
| - 'from-light-3', |
214 |
| - 'to-transparent', |
215 |
| - 'dark:from-dark-3', |
216 |
| - 'dark:to-transparent', |
217 |
| - ] |
218 |
| - : null, |
219 |
| - ] |
220 |
| - : [], |
221 |
| - )} |
222 |
| - ></span> |
223 |
| - ) : null} |
224 |
| - |
225 |
| - <span className={tcls('ml-3', 'block', 'text-sm')}> |
226 |
| - <CodeHighlightTokens tokens={line.tokens} document={document} context={context} /> |
227 |
| - {isLast ? null : !withLineNumbers && line.tokens.length === 0 && 0 ? ( |
228 |
| - <span className="ew">{'\u200B'}</span> |
229 |
| - ) : ( |
230 |
| - '\n' |
231 |
| - )} |
232 |
| - </span> |
233 |
| - </span> |
234 |
| - ); |
235 |
| -} |
236 |
| - |
237 |
| -function CodeHighlightTokens(props: { |
238 |
| - tokens: HighlightToken[]; |
239 |
| - document: JSONDocument; |
240 |
| - context: DocumentContext; |
241 |
| -}) { |
242 |
| - const { tokens, document, context } = props; |
243 |
| - |
244 |
| - return ( |
245 |
| - <> |
246 |
| - {tokens.map((token, index) => ( |
247 |
| - <CodeHighlightToken |
| 14 | +export function CodeBlock(props: BlockProps<DocumentBlockCode>) { |
| 15 | + const { block, document, style, context, isEstimatedOffscreen } = props; |
| 16 | + const inlines = getInlines(block); |
| 17 | + const richInlines: RenderedInline[] = inlines.map((inline, index) => { |
| 18 | + const body = (() => { |
| 19 | + const fragment = getNodeFragmentByType(inline.inline, 'annotation-body'); |
| 20 | + if (!fragment) { |
| 21 | + return null; |
| 22 | + } |
| 23 | + return ( |
| 24 | + <Blocks |
248 | 25 | key={index}
|
249 |
| - token={token} |
250 |
| - document={document} |
251 |
| - context={context} |
252 |
| - /> |
253 |
| - ))} |
254 |
| - </> |
255 |
| - ); |
256 |
| -} |
257 |
| - |
258 |
| -function CodeHighlightToken(props: { |
259 |
| - token: HighlightToken; |
260 |
| - document: JSONDocument; |
261 |
| - context: DocumentContext; |
262 |
| -}) { |
263 |
| - const { token, document, context } = props; |
264 |
| - |
265 |
| - if (token.type === 'inline') { |
266 |
| - return ( |
267 |
| - <Inline |
268 |
| - inline={token.inline} |
269 |
| - document={document} |
270 |
| - context={context} |
271 |
| - ancestorInlines={[]} |
272 |
| - > |
273 |
| - <CodeHighlightTokens |
274 |
| - tokens={token.children} |
275 | 26 | document={document}
|
| 27 | + ancestorBlocks={[]} |
276 | 28 | context={context}
|
| 29 | + nodes={fragment.nodes} |
| 30 | + style={['space-y-4']} |
277 | 31 | />
|
278 |
| - </Inline> |
279 |
| - ); |
280 |
| - } |
| 32 | + ); |
| 33 | + })(); |
281 | 34 |
|
282 |
| - if (token.type === 'plain') { |
283 |
| - return <>{token.content}</>; |
284 |
| - } |
| 35 | + return { inline, body }; |
| 36 | + }); |
285 | 37 |
|
286 |
| - if (!token.token.color) { |
287 |
| - return <>{token.token.content}</>; |
| 38 | + if (isEstimatedOffscreen) { |
| 39 | + return <ClientCodeBlock block={block} style={style} inlines={richInlines} />; |
288 | 40 | }
|
289 | 41 |
|
290 |
| - return <span style={{ color: token.token.color }}>{token.token.content}</span>; |
| 42 | + return <ServerCodeBlock block={block} style={style} inlines={richInlines} />; |
291 | 43 | }
|
0 commit comments