feat: replace codemirror and image viewr, opt for simplier approach
This commit is contained in:
@@ -1,51 +0,0 @@
|
||||
import { useCallback } from 'react'
|
||||
|
||||
/**
|
||||
* Ref shape for @uiw/react-codemirror (view/state not in package types).
|
||||
* Use with: ref={editorRef} and type editorRef as React.MutableRefObject<CodeMirrorEditorRef | null>.
|
||||
*/
|
||||
export type CodeMirrorEditorRef = {
|
||||
view: {
|
||||
state: {
|
||||
doc: { length: number; toString(): string }
|
||||
selection: { main: { from: number } }
|
||||
}
|
||||
dispatch: (arg: { changes: { from: number; to: number; insert: string } }) => void
|
||||
}
|
||||
}
|
||||
|
||||
export type InsertPosition = 'prepend' | 'append' | 'cursor'
|
||||
|
||||
/**
|
||||
* Returns a stable insertAt(insertText, mode) that inserts text into the CodeMirror
|
||||
* editor at the given position (prepend, append, or cursor), then calls onChange
|
||||
* with the new content. Falls back to string concatenation when the editor ref
|
||||
* is not yet mounted.
|
||||
*/
|
||||
export function useCodeMirrorInsert(
|
||||
editorRef: React.RefObject<CodeMirrorEditorRef | null>,
|
||||
currentContent: string,
|
||||
onChange: (value: string) => void
|
||||
): (insertText: string, mode: InsertPosition) => void {
|
||||
return useCallback(
|
||||
(insertText: string, mode: InsertPosition) => {
|
||||
const ref = editorRef.current
|
||||
if (ref?.view) {
|
||||
const view = ref.view
|
||||
const doc = view.state.doc
|
||||
const len = doc.length
|
||||
const from =
|
||||
mode === 'prepend' ? 0 : mode === 'append' ? len : view.state.selection.main.from
|
||||
view.dispatch({ changes: { from, to: from, insert: insertText } })
|
||||
onChange(view.state.doc.toString())
|
||||
return
|
||||
}
|
||||
if (mode === 'prepend') {
|
||||
onChange(insertText + currentContent)
|
||||
} else {
|
||||
onChange(currentContent + insertText)
|
||||
}
|
||||
},
|
||||
[currentContent, onChange]
|
||||
)
|
||||
}
|
||||
@@ -21,8 +21,7 @@ export function useResizeHeight(
|
||||
const unobserve = observeResize(el, (size) => {
|
||||
if (size.height > 0) setHeight(size.height)
|
||||
})
|
||||
const initial = el.getBoundingClientRect().height
|
||||
if (initial > 0) setHeight(initial)
|
||||
// Rely on ResizeObserver for initial size to avoid forced synchronous layout (getBoundingClientRect).
|
||||
return unobserve
|
||||
}, deps ?? [])
|
||||
|
||||
|
||||
52
frontend/src/hooks/useSimpleEditorInsert.ts
Normal file
52
frontend/src/hooks/useSimpleEditorInsert.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useCallback, useRef } from 'react'
|
||||
|
||||
export type InsertPosition = 'prepend' | 'append' | 'cursor'
|
||||
|
||||
/**
|
||||
* Returns a stable insertAt(insertText, mode) that inserts text into the simple code editor
|
||||
* (textarea identified by textareaId) at the given position, then calls onChange with the new content.
|
||||
* Cursor is restored after the next render via a scheduled effect.
|
||||
*/
|
||||
export function useSimpleEditorInsert(
|
||||
textareaId: string,
|
||||
currentContent: string,
|
||||
onChange: (value: string) => void
|
||||
): (insertText: string, mode: InsertPosition) => void {
|
||||
const pendingCursorRef = useRef<number | null>(null)
|
||||
|
||||
const insertAt = useCallback(
|
||||
(insertText: string, mode: InsertPosition) => {
|
||||
const ta = document.getElementById(textareaId) as HTMLTextAreaElement | null
|
||||
let start: number
|
||||
let end: number
|
||||
if (ta) {
|
||||
start = mode === 'cursor' ? ta.selectionStart : mode === 'prepend' ? 0 : ta.value.length
|
||||
end = mode === 'cursor' ? ta.selectionEnd : start
|
||||
} else {
|
||||
start = mode === 'prepend' ? 0 : currentContent.length
|
||||
end = start
|
||||
}
|
||||
const newValue =
|
||||
currentContent.slice(0, start) + insertText + currentContent.slice(end)
|
||||
const nextCursor = start + insertText.length
|
||||
pendingCursorRef.current = nextCursor
|
||||
onChange(newValue)
|
||||
// Restore cursor after React re-renders
|
||||
if (ta) {
|
||||
requestAnimationFrame(() => {
|
||||
const el = document.getElementById(textareaId) as HTMLTextAreaElement | null
|
||||
if (el && pendingCursorRef.current !== null) {
|
||||
el.focus()
|
||||
el.setSelectionRange(pendingCursorRef.current, pendingCursorRef.current)
|
||||
pendingCursorRef.current = null
|
||||
}
|
||||
})
|
||||
} else {
|
||||
pendingCursorRef.current = null
|
||||
}
|
||||
},
|
||||
[textareaId, currentContent, onChange]
|
||||
)
|
||||
|
||||
return insertAt
|
||||
}
|
||||
Reference in New Issue
Block a user