Skip to content

Commit 67f7836

Browse files
authored
Revert "Highlight code client-side" (#2796)
1 parent d9c8d57 commit 67f7836

16 files changed

+405
-527
lines changed

.changeset/thin-files-flow.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/gitbook/src/components/DocumentView/Annotation/Annotation.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Blocks } from '../Blocks';
77
import { InlineProps } from '../Inline';
88
import { Inlines } from '../Inlines';
99

10-
export function Annotation(props: InlineProps<DocumentInlineAnnotation>) {
10+
export async function Annotation(props: InlineProps<DocumentInlineAnnotation>) {
1111
const { inline, context, document, children } = props;
1212

1313
const fragment = getNodeFragmentByType(inline, 'annotation-body');

packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 276 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,291 @@
1-
import type { DocumentBlockCode } from '@gitbook/api';
1+
import { DocumentBlockCode, JSONDocument } from '@gitbook/api';
22

3-
import { getNodeFragmentByType } from '@/lib/document';
3+
import { tcls } from '@/lib/tailwind';
44

5+
import { CopyCodeButton } from './CopyCodeButton';
6+
import { highlight, HighlightLine, HighlightToken, plainHighlighting } from './highlight';
57
import { BlockProps } from '../Block';
6-
import { ClientCodeBlock } from './ClientCodeBlock';
7-
import { getInlines, RenderedInline } from './highlight';
8-
import { Blocks } from '../Blocks';
9-
import { ServerCodeBlock } from './ServerCodeBlock';
8+
import { DocumentContext } from '../DocumentView';
9+
import { Inline } from '../Inline';
10+
11+
import './theme.css';
1012

1113
/**
12-
* Render a code block, can be client-side or server-side.
14+
* Render an entire code-block. The syntax highlighting is done server-side.
1315
*/
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
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
25248
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}
26275
document={document}
27-
ancestorBlocks={[]}
28276
context={context}
29-
nodes={fragment.nodes}
30-
style={['space-y-4']}
31277
/>
32-
);
33-
})();
278+
</Inline>
279+
);
280+
}
34281

35-
return { inline, body };
36-
});
282+
if (token.type === 'plain') {
283+
return <>{token.content}</>;
284+
}
37285

38-
if (isEstimatedOffscreen) {
39-
return <ClientCodeBlock block={block} style={style} inlines={richInlines} />;
286+
if (!token.token.color) {
287+
return <>{token.token.content}</>;
40288
}
41289

42-
return <ServerCodeBlock block={block} style={style} inlines={richInlines} />;
290+
return <span style={{ color: token.token.color }}>{token.token.content}</span>;
43291
}

0 commit comments

Comments
 (0)