feat: replace codemirror and image viewr, opt for simplier approach

This commit is contained in:
2026-03-12 23:07:51 +01:00
parent b71d32da5e
commit 4dca9c6478
19 changed files with 464 additions and 709 deletions

View File

@@ -0,0 +1,102 @@
/**
* Shared code editor with Nunjucks support by default.
* Uses react-simple-code-editor + Prism + prism-react-renderer; highlights base language + {{ }}, {% %}, {# #}.
* Wherever this editor is used, it provides the same features (syntax highlighting + Nunjucks).
* The parent (node) provides the expected base language for highlighting.
*/
import React, { useCallback, useRef } from 'react'
import Editor from 'react-simple-code-editor'
import { highlight, type HighlightLanguage } from '@/lib/syntaxHighlight'
export type CodeEditorProps = {
value: string
onValueChange: (value: string) => void
/** Base language for syntax highlighting (Nunjucks is always applied on top). */
language: HighlightLanguage
/** Stable id for the underlying textarea (for insert-at-cursor). */
textareaId?: string
readOnly?: boolean
placeholder?: string
padding?: number
tabSize?: number
insertSpaces?: boolean
ignoreTabKey?: boolean
style?: React.CSSProperties
className?: string
textareaClassName?: string
preClassName?: string
/** Minimum height so the container doesn't collapse before resize. */
minHeight?: number
}
const defaultStyle: React.CSSProperties = {
fontFamily: 'ui-monospace, monospace',
fontSize: 12,
lineHeight: 1.5,
overflow: 'auto',
}
export function CodeEditor({
value,
onValueChange,
language,
textareaId,
readOnly = false,
padding = 8,
tabSize = 2,
insertSpaces = true,
ignoreTabKey = false,
style,
className,
textareaClassName,
preClassName,
minHeight = 120,
}: CodeEditorProps) {
const containerRef = useRef<HTMLDivElement | null>(null)
const highlightCode = useCallback(
(code: string) => highlight(code, language),
[language]
)
const handleValueChange = useCallback(
(newValue: string) => {
if (readOnly) {
onValueChange(newValue)
return
}
const ta =
(textareaId ? document.getElementById(textareaId) : containerRef.current?.querySelector('textarea')) as HTMLTextAreaElement | null
const start = ta?.selectionStart ?? newValue.length
const end = ta?.selectionEnd ?? newValue.length
onValueChange(newValue)
requestAnimationFrame(() => {
const el = (textareaId
? document.getElementById(textareaId)
: containerRef.current?.querySelector('textarea')) as HTMLTextAreaElement | null
if (el) el.setSelectionRange(start, end)
})
},
[onValueChange, readOnly, textareaId]
)
return (
<div ref={containerRef} className="code-editor" style={{ minHeight: 0 }}>
<Editor
value={value}
onValueChange={handleValueChange}
highlight={highlightCode}
tabSize={tabSize}
insertSpaces={insertSpaces}
ignoreTabKey={ignoreTabKey}
padding={padding}
readOnly={readOnly}
textareaId={textareaId}
style={{ ...defaultStyle, minHeight, ...style }}
className={className}
textareaClassName={textareaClassName}
preClassName={preClassName}
/>
</div>
)
}