({
autoFocus,
className,
contentClassName,
disabled,
onBlur,
onChange,
placeholder = 'Write something…',
ref,
showToolbar = true,
value,
}: MarkdownEditorProps & { ref?: Ref<MarkdownEditorHandle> })
| 101 | } |
| 102 | |
| 103 | function MarkdownEditor({ |
| 104 | autoFocus, |
| 105 | className, |
| 106 | contentClassName, |
| 107 | disabled, |
| 108 | onBlur, |
| 109 | onChange, |
| 110 | placeholder = 'Write something…', |
| 111 | ref, |
| 112 | showToolbar = true, |
| 113 | value, |
| 114 | }: MarkdownEditorProps & { ref?: Ref<MarkdownEditorHandle> }) { |
| 115 | const onChangeRef = useRef(onChange); |
| 116 | const onBlurRef = useRef(onBlur); |
| 117 | // Tracks the last markdown the editor reported externally. We compare |
| 118 | // against this to suppress echo updates: tiptap-markdown can re-serialize |
| 119 | // content slightly differently than the input string (whitespace/list |
| 120 | // markers/hard breaks/etc.), and we don't want to flag those |
| 121 | // normalizations as user edits — that would falsely flip RHF's |
| 122 | // `isDirty` flag. |
| 123 | // |
| 124 | // The baseline is the editor's own serialized markdown, NOT the raw |
| 125 | // input `value`. Comparing user edits against the canonical |
| 126 | // (post-normalization) form is what makes the comparison correct. |
| 127 | const lastEmittedRef = useRef<string>(value); |
| 128 | |
| 129 | // Tiptap dispatches transactions for the initial content during view |
| 130 | // construction. Those `onUpdate` calls are echoes of the initial |
| 131 | // parse, not real user edits — forwarding them to RHF would mark |
| 132 | // the form dirty on mount. We start forwarding edits only after |
| 133 | // `onCreate` has captured the canonical baseline. |
| 134 | const isInitializedRef = useRef(false); |
| 135 | |
| 136 | // Tracks whether we have already cleared the undo stack on initial |
| 137 | // mount. After the first sync useEffect run we set this to true; on |
| 138 | // subsequent renders we only clear the stack when an external value |
| 139 | // arrived (see `shouldExternalSync` below). |
| 140 | const hasResetInitialHistoryRef = useRef(false); |
| 141 | |
| 142 | useEffect(() => { |
| 143 | onChangeRef.current = onChange; |
| 144 | onBlurRef.current = onBlur; |
| 145 | }, [onChange, onBlur]); |
| 146 | |
| 147 | const editor = useEditor({ |
| 148 | content: value, |
| 149 | editable: !disabled, |
| 150 | extensions: [ |
| 151 | StarterKit.configure({ |
| 152 | codeBlock: { HTMLAttributes: { class: 'hljs' } }, |
| 153 | }), |
| 154 | Placeholder.configure({ |
| 155 | emptyEditorClass: 'is-editor-empty', |
| 156 | placeholder, |
| 157 | }), |
| 158 | Markdown.configure({ |
| 159 | breaks: true, |
| 160 | html: false, |
nothing calls this directly
no test coverage detected