Skip to content

Commit 2f8f41c

Browse files
committed
SImplify deferred codeblock highlighting and fix streaming
1 parent d33171b commit 2f8f41c

File tree

1 file changed

+34
-33
lines changed

1 file changed

+34
-33
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
'use client';
22

33
import type { DocumentBlockCode } from '@gitbook/api';
4-
import { useEffect, useRef, useState } from 'react';
4+
import { useEffect, useMemo, useRef, useState } from 'react';
55

66
import { useInViewportListener } from '@/components/hooks/useInViewportListener';
7-
import { useScrollListener } from '@/components/hooks/useScrollListener';
8-
import { useDebounceCallback, useEventCallback } from 'usehooks-ts';
97
import type { BlockProps } from '../Block';
108
import { CodeBlockRenderer } from './CodeBlockRenderer';
119
import type { HighlightLine, RenderedInline } from './highlight';
@@ -22,53 +20,56 @@ type ClientBlockProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> &
2220
export function ClientCodeBlock(props: ClientBlockProps) {
2321
const { block, style, inlines } = props;
2422
const blockRef = useRef<HTMLDivElement>(null);
25-
const processedRef = useRef(false);
26-
const isInViewportRef = useRef<boolean | null>(null);
27-
const [lines, setLines] = useState<HighlightLine[]>(() => plainHighlight(block, []));
23+
const [isInViewport, setIsInViewport] = useState(false);
24+
const plainLines = useMemo(() => plainHighlight(block, []), [block]);
25+
const [lines, setLines] = useState<null | HighlightLine[]>(null);
2826

2927
// Preload the highlighter when the block is mounted.
3028
useEffect(() => {
3129
import('./highlight').then(({ preloadHighlight }) => preloadHighlight(block));
3230
}, [block]);
3331

34-
const runHighlight = useEventCallback(() => {
35-
if (processedRef.current) {
36-
return;
37-
}
38-
if (typeof window !== 'undefined') {
39-
import('./highlight').then(({ highlight }) => {
40-
highlight(block, inlines).then((lines) => {
41-
setLines(lines);
42-
processedRef.current = true;
43-
});
44-
});
45-
}
46-
});
47-
const debouncedRunHighlight = useDebounceCallback(runHighlight, 1000);
48-
32+
// Detect when the block is in viewport
4933
useInViewportListener(
5034
blockRef,
5135
(isInViewport, disconnect) => {
52-
// Disconnect once in viewport
5336
if (isInViewport) {
37+
// Disconnect once in viewport
5438
disconnect();
55-
// If it's initially in viewport, we need to run the highlight
56-
if (isInViewportRef.current === null) {
57-
runHighlight();
58-
}
39+
setIsInViewport(true);
5940
}
60-
isInViewportRef.current = isInViewport;
6141
},
6242
{ rootMargin: '200px' }
6343
);
6444

65-
const handleScroll = useDebounceCallback(() => {
66-
if (isInViewportRef.current) {
67-
debouncedRunHighlight();
45+
// When the block is visible or updated, we need to re-run the highlight
46+
useEffect(() => {
47+
if (isInViewport) {
48+
// If the block is in viewport, we need to run the highlight
49+
let cancelled = false;
50+
51+
if (typeof window !== 'undefined') {
52+
import('./highlight').then(({ highlight }) => {
53+
highlight(block, inlines).then((lines) => {
54+
if (cancelled) {
55+
return;
56+
}
57+
58+
setLines(lines);
59+
});
60+
});
61+
}
62+
63+
return () => {
64+
cancelled = true;
65+
};
6866
}
69-
}, 80);
7067

71-
useScrollListener(handleScroll, useRef(typeof window !== 'undefined' ? window : null));
68+
// Otherwise if the block is not in viewport, we reset to the plain lines
69+
setLines(null);
70+
}, [isInViewport, block, inlines]);
7271

73-
return <CodeBlockRenderer ref={blockRef} block={block} style={style} lines={lines} />;
72+
return (
73+
<CodeBlockRenderer ref={blockRef} block={block} style={style} lines={lines ?? plainLines} />
74+
);
7475
}

0 commit comments

Comments
 (0)