feat(workspace): add line numbers and horizontal scrolling to code block editor
This commit is contained in:
1 parent
108ac6621c
commit
1d6863824b
3 files changed
+132
-34
No files matched your search
@@ -347,6 +347,7 @@ export const CodeBlockEditor: React.FC<CodeBlockEditorProps> = ({
|
||||
const copyTimerRef = useRef<number | null>(null);
|
||||
const localTextareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const preRef = useRef<HTMLPreElement | null>(null);
|
||||
const lineNumbersRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@@ -506,18 +507,42 @@ export const CodeBlockEditor: React.FC<CodeBlockEditorProps> = ({
|
||||
};
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLTextAreaElement>) => {
|
||||
const target = e.currentTarget;
|
||||
if (preRef.current) {
|
||||
preRef.current.scrollTop = e.currentTarget.scrollTop;
|
||||
preRef.current.scrollLeft = e.currentTarget.scrollLeft;
|
||||
preRef.current.scrollTop = target.scrollTop;
|
||||
preRef.current.scrollLeft = target.scrollLeft;
|
||||
}
|
||||
if (lineNumbersRef.current) {
|
||||
lineNumbersRef.current.scrollTop = target.scrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLineNumbersScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const target = e.currentTarget;
|
||||
if (localTextareaRef.current) {
|
||||
localTextareaRef.current.scrollTop = target.scrollTop;
|
||||
}
|
||||
if (preRef.current) {
|
||||
preRef.current.scrollTop = target.scrollTop;
|
||||
}
|
||||
};
|
||||
|
||||
const handleLineNumbersWheel = (e: React.WheelEvent) => {
|
||||
if (localTextareaRef.current) {
|
||||
localTextareaRef.current.scrollTop += e.deltaY;
|
||||
}
|
||||
};
|
||||
|
||||
const lineCount = useMemo(() => {
|
||||
return currentContent.split('\n').length;
|
||||
}, [currentContent]);
|
||||
|
||||
const rawLang = block.lang || '';
|
||||
const selectValue = rawLang ? resolveLanguageId(rawLang) || rawLang : '';
|
||||
const hasCustomUnknownLang = Boolean(selectValue && !KNOWN_OPTION_IDS.has(selectValue));
|
||||
|
||||
return (
|
||||
<div className="bg-code border border-border rounded-btn my-0.5 overflow-x-auto max-w-full">
|
||||
<div className="bg-code border border-border rounded-btn my-0.5 max-w-full overflow-hidden">
|
||||
{/* Code Block Header */}
|
||||
<div className="flex items-center justify-between gap-2 px-2 py-1 border-b border-border text-[11px] font-mono text-faint">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
@@ -584,41 +609,60 @@ export const CodeBlockEditor: React.FC<CodeBlockEditorProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Live Highlighted Overlay + Textarea */}
|
||||
<div className="relative w-full overflow-x-auto">
|
||||
<pre
|
||||
ref={preRef}
|
||||
{/* Live Highlighted Overlay + Textarea with Line Numbers */}
|
||||
<div className="flex relative w-full overflow-hidden">
|
||||
{/* Line Numbers Column */}
|
||||
<div
|
||||
ref={lineNumbersRef}
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 m-0 p-2 font-mono text-xs text-main leading-relaxed whitespace-pre-wrap break-words [overflow-wrap:anywhere] pointer-events-none select-none overflow-hidden"
|
||||
onScroll={handleLineNumbersScroll}
|
||||
onWheel={handleLineNumbersWheel}
|
||||
data-testid="code-line-numbers"
|
||||
className="select-none py-2 pl-2.5 pr-2 font-mono text-xs text-faint leading-relaxed text-right border-r border-border/40 shrink-0 overflow-hidden"
|
||||
>
|
||||
<code
|
||||
className={prismLang ? `language-${prismLang}` : 'language-none'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: highlightedHtml + (currentContent.endsWith('\n') ? '\n' : ''),
|
||||
{Array.from({ length: lineCount }, (_, i) => (
|
||||
<div key={i + 1} data-line-number={i + 1}>
|
||||
{i + 1}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Code Content (Overlay + Textarea) */}
|
||||
<div className="relative flex-1 min-w-0 overflow-hidden">
|
||||
<pre
|
||||
ref={preRef}
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 m-0 p-2 font-mono text-xs text-main leading-relaxed whitespace-pre pointer-events-none select-none overflow-hidden"
|
||||
>
|
||||
<code
|
||||
className={prismLang ? `language-${prismLang}` : 'language-none'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: highlightedHtml + (currentContent.endsWith('\n') ? '\n' : ''),
|
||||
}}
|
||||
/>
|
||||
</pre>
|
||||
<textarea
|
||||
ref={(el) => {
|
||||
localTextareaRef.current = el;
|
||||
if (textareaRef) textareaRef.current = el;
|
||||
registerTextareaRef?.(block.id, el);
|
||||
}}
|
||||
rows={1}
|
||||
value={currentContent}
|
||||
readOnly={!canEdit}
|
||||
spellCheck={false}
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onChange={onTextareaChange}
|
||||
onKeyDown={onTextareaKeyDown}
|
||||
onScroll={handleScroll}
|
||||
style={{ caretColor: 'var(--text-main)' }}
|
||||
className="relative block w-full m-0 p-2 font-mono text-xs leading-relaxed whitespace-pre bg-transparent text-transparent caret-main selection:bg-accent/25 selection:text-transparent border-0 focus:outline-none focus:ring-0 resize-none overflow-x-auto"
|
||||
/>
|
||||
</pre>
|
||||
<textarea
|
||||
ref={(el) => {
|
||||
localTextareaRef.current = el;
|
||||
if (textareaRef) textareaRef.current = el;
|
||||
registerTextareaRef?.(block.id, el);
|
||||
}}
|
||||
rows={1}
|
||||
value={currentContent}
|
||||
readOnly={!canEdit}
|
||||
spellCheck={false}
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onChange={onTextareaChange}
|
||||
onKeyDown={onTextareaKeyDown}
|
||||
onScroll={handleScroll}
|
||||
style={{ caretColor: 'var(--text-main)' }}
|
||||
className="relative block w-full m-0 p-2 font-mono text-xs leading-relaxed whitespace-pre-wrap break-words [overflow-wrap:anywhere] bg-transparent text-transparent caret-main selection:bg-accent/25 selection:text-transparent border-0 focus:outline-none focus:ring-0 resize-none overflow-hidden"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user