* start of removing react-codemirror Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * change theme Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * disable copilot temporarily Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
172 lines
3.6 KiB
TypeScript
172 lines
3.6 KiB
TypeScript
import React, {
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
forwardRef,
|
|
useImperativeHandle,
|
|
} from 'react'
|
|
import {
|
|
EditorState,
|
|
EditorStateConfig,
|
|
Extension,
|
|
StateEffect,
|
|
} from '@codemirror/state'
|
|
import { EditorView } from '@codemirror/view'
|
|
import { oneDark } from '@codemirror/theme-one-dark'
|
|
|
|
//reference: https://github.com/sachinraja/rodemirror/blob/main/src/use-first-render.ts
|
|
const useFirstRender = () => {
|
|
const firstRender = useRef(true)
|
|
|
|
useEffect(() => {
|
|
firstRender.current = false
|
|
}, [])
|
|
|
|
return firstRender.current
|
|
}
|
|
|
|
const defaultLightThemeOption = EditorView.theme(
|
|
{
|
|
'&': {
|
|
backgroundColor: '#fff',
|
|
},
|
|
},
|
|
{
|
|
dark: false,
|
|
}
|
|
)
|
|
|
|
interface CodeEditorRef {
|
|
editor?: HTMLDivElement | null
|
|
view?: EditorView
|
|
state?: EditorState
|
|
}
|
|
|
|
interface CodeEditorProps {
|
|
onCreateEditor?: (view: EditorView | null) => void
|
|
initialDocValue?: EditorStateConfig['doc']
|
|
extensions?: Extension
|
|
theme: 'light' | 'dark'
|
|
autoFocus?: boolean
|
|
selection?: EditorStateConfig['selection']
|
|
}
|
|
|
|
interface UseCodeMirror extends CodeEditorProps {
|
|
container?: HTMLDivElement | null
|
|
}
|
|
|
|
const CodeEditor = forwardRef<CodeEditorRef, CodeEditorProps>((props, ref) => {
|
|
const {
|
|
onCreateEditor,
|
|
extensions = [],
|
|
initialDocValue,
|
|
theme,
|
|
autoFocus = false,
|
|
selection,
|
|
} = props
|
|
const editor = useRef<HTMLDivElement>(null)
|
|
|
|
const { view, state, container } = useCodeMirror({
|
|
container: editor.current,
|
|
onCreateEditor,
|
|
extensions,
|
|
initialDocValue,
|
|
theme,
|
|
autoFocus,
|
|
selection,
|
|
})
|
|
|
|
useImperativeHandle(
|
|
ref,
|
|
() => ({ editor: editor.current, view: view, state: state }),
|
|
[editor, container, view, state]
|
|
)
|
|
|
|
return <div ref={editor}></div>
|
|
})
|
|
|
|
export function useCodeMirror(props: UseCodeMirror) {
|
|
const {
|
|
onCreateEditor,
|
|
extensions = [],
|
|
initialDocValue,
|
|
theme,
|
|
autoFocus = false,
|
|
selection,
|
|
} = props
|
|
|
|
const [container, setContainer] = useState<HTMLDivElement | null>()
|
|
const [view, setView] = useState<EditorView>()
|
|
const [state, setState] = useState<EditorState>()
|
|
|
|
const isFirstRender = useFirstRender()
|
|
|
|
const targetExtensions = useMemo(() => {
|
|
let exts = Array.isArray(extensions) ? extensions : []
|
|
if (theme === 'dark') {
|
|
exts = [...exts, oneDark]
|
|
} else if (theme === 'light') {
|
|
exts = [...exts, defaultLightThemeOption]
|
|
}
|
|
|
|
return exts
|
|
}, [extensions, theme])
|
|
|
|
useEffect(() => {
|
|
if (container && !state) {
|
|
const config = {
|
|
doc: initialDocValue,
|
|
selection,
|
|
extensions: [...Array.of(extensions)],
|
|
}
|
|
const stateCurrent = EditorState.create(config)
|
|
setState(stateCurrent)
|
|
if (!view) {
|
|
const viewCurrent = new EditorView({
|
|
state: stateCurrent,
|
|
parent: container,
|
|
})
|
|
setView(viewCurrent)
|
|
onCreateEditor && onCreateEditor(viewCurrent)
|
|
}
|
|
}
|
|
return () => {
|
|
if (view) {
|
|
setState(undefined)
|
|
setView(undefined)
|
|
}
|
|
}
|
|
}, [container, state])
|
|
|
|
useEffect(() => setContainer(props.container), [props.container])
|
|
|
|
useEffect(
|
|
() => () => {
|
|
if (view) {
|
|
view.destroy()
|
|
setView(undefined)
|
|
}
|
|
},
|
|
[view]
|
|
)
|
|
|
|
useEffect(() => {
|
|
if (autoFocus && view) {
|
|
view.focus()
|
|
}
|
|
}, [autoFocus, view])
|
|
|
|
useEffect(() => {
|
|
if (view && !isFirstRender) {
|
|
view.dispatch({
|
|
effects: StateEffect.reconfigure.of(targetExtensions),
|
|
})
|
|
}
|
|
}, [targetExtensions])
|
|
|
|
return { view, setView, container, setContainer, state, setState }
|
|
}
|
|
|
|
export default CodeEditor
|