/ concept-collection / mdshare
concept-collection / mdshare
mdshare / src / CodeMirrorEditor.tsx
69 lines · 2.1 KBBlameHistoryRaw
1import { useEffect, useRef } from 'react'
2import { EditorView, keymap } from '@codemirror/view'
3import { EditorState } from '@codemirror/state'
4import { indentWithTab } from '@codemirror/commands'
5import { markdown } from '@codemirror/lang-markdown'
6import { basicSetup } from 'codemirror'
8interface Props {
9 value: string
10 onChange: (value: string) => void
13// Thin React wrapper around a CodeMirror 6 editor. The editor owns the text
14// while the user types; `value` is only pushed in when it differs from the
15// editor's contents (i.e. on external changes like a hash edit).
16export default function CodeMirrorEditor({ value, onChange }: Props) {
17 const containerRef = useRef<HTMLDivElement>(null)
18 const viewRef = useRef<EditorView | null>(null)
19 const onChangeRef = useRef(onChange)
20 onChangeRef.current = onChange
22 useEffect(() => {
23 if (!containerRef.current) return
24 const view = new EditorView({
25 state: EditorState.create({
26 doc: value,
27 extensions: [
28 basicSetup,
29 keymap.of([indentWithTab]),
30 markdown(),
31 EditorView.lineWrapping,
32 EditorView.updateListener.of((update) => {
33 if (update.docChanged) {
34 onChangeRef.current(update.state.doc.toString())
35 }
36 }),
37 EditorView.theme({
38 '&': { height: '100%', fontSize: '13px' },
39 '.cm-scroller': {
40 fontFamily:
41 "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
42 },
43 }),
44 ],
45 }),
46 parent: containerRef.current,
47 })
48 viewRef.current = view
49 return () => {
50 view.destroy()
51 viewRef.current = null
52 }
53 // The editor is created once; `value` afterwards flows through the effect below.
54 // eslint-disable-next-line react-hooks/exhaustive-deps
55 }, [])
57 useEffect(() => {
58 const view = viewRef.current
59 if (!view) return
60 const current = view.state.doc.toString()
61 if (current !== value) {
62 view.dispatch({
63 changes: { from: 0, to: current.length, insert: value },
64 })
65 }
66 }, [value])
68 return <div className="editor-container" ref={containerRef} />