@@ -30,18 +30,10 @@ import { Markdown } from '@tiptap/markdown';
3030import Image from '@tiptap/extension-image' ;
3131import { TableKit } from '@tiptap/extension-table' ;
3232
33- import { Editor } from './types' ;
33+ import { Editor , BaseEditorProps } from './types' ;
3434import { createTipTapAdapter } from './utils/tiptap/adapter' ;
3535
36- interface RichEditorProps {
37- value : string ;
38- onChange ?: ( value : string ) => void ;
39- onFocus ?: ( ) => void ;
40- onBlur ?: ( ) => void ;
41- placeholder ?: string ;
42- autoFocus ?: boolean ;
43- onEditorReady ?: ( editor : Editor ) => void ;
44- }
36+ interface RichEditorProps extends BaseEditorProps { }
4537
4638const RichEditor : React . FC < RichEditorProps > = ( {
4739 value,
@@ -53,14 +45,60 @@ const RichEditor: React.FC<RichEditorProps> = ({
5345 onEditorReady,
5446} ) => {
5547 const lastSyncedValueRef = useRef < string > ( value ) ;
56-
5748 const adaptedEditorRef = useRef < Editor | null > ( null ) ;
49+ const isInitializedRef = useRef < boolean > ( false ) ;
50+ const isUpdatingFromPropsRef = useRef < boolean > ( false ) ;
51+ const onEditorReadyRef = useRef ( onEditorReady ) ;
52+ const autoFocusRef = useRef ( autoFocus ) ;
53+ const initialValueRef = useRef < string > ( value ) ;
54+
55+ useEffect ( ( ) => {
56+ onEditorReadyRef . current = onEditorReady ;
57+ autoFocusRef . current = autoFocus ;
58+ } , [ onEditorReady , autoFocus ] ) ;
59+
60+ const isViewAvailable = ( editorInstance : TipTapEditor | null ) : boolean => {
61+ if ( ! editorInstance ) {
62+ return false ;
63+ }
64+ if ( editorInstance . isDestroyed ) {
65+ return false ;
66+ }
67+ return ! ! ( editorInstance . view && editorInstance . state ) ;
68+ } ;
69+
70+ const handleCreate = useCallback (
71+ ( { editor : editorInstance } : { editor : TipTapEditor } ) => {
72+ if ( isInitializedRef . current || ! isViewAvailable ( editorInstance ) ) {
73+ return ;
74+ }
75+
76+ isInitializedRef . current = true ;
77+
78+ const initialValue = initialValueRef . current ;
79+ if ( initialValue && initialValue . trim ( ) !== '' ) {
80+ editorInstance . commands . setContent ( initialValue , {
81+ contentType : 'markdown' ,
82+ } ) ;
83+ lastSyncedValueRef . current = initialValue ;
84+ }
85+
86+ adaptedEditorRef . current = createTipTapAdapter ( editorInstance ) ;
87+ onEditorReadyRef . current ?.( adaptedEditorRef . current ) ;
88+
89+ if ( autoFocusRef . current ) {
90+ editorInstance . commands . focus ( ) ;
91+ }
92+ } ,
93+ [ ] ,
94+ ) ;
5895
5996 const handleUpdate = useCallback (
6097 ( { editor : editorInstance } : { editor : TipTapEditor } ) => {
61- if ( onChange ) {
98+ if ( onChange && ! isUpdatingFromPropsRef . current ) {
6299 const markdown = editorInstance . getMarkdown ( ) ;
63100 onChange ( markdown ) ;
101+ lastSyncedValueRef . current = markdown ;
64102 }
65103 } ,
66104 [ onChange ] ,
@@ -84,7 +122,7 @@ const RichEditor: React.FC<RichEditorProps> = ({
84122 placeholder,
85123 } ) ,
86124 ] ,
87- content : value || '' ,
125+ onCreate : handleCreate ,
88126 onUpdate : handleUpdate ,
89127 onFocus : handleFocus ,
90128 onBlur : handleBlur ,
@@ -96,67 +134,56 @@ const RichEditor: React.FC<RichEditorProps> = ({
96134 } ) ;
97135
98136 useEffect ( ( ) => {
99- if ( ! editor ) {
137+ if (
138+ ! editor ||
139+ ! isInitializedRef . current ||
140+ ! isViewAvailable ( editor ) ||
141+ value === lastSyncedValueRef . current
142+ ) {
100143 return ;
101144 }
102145
103- const checkEditorReady = ( ) => {
104- if ( editor . view && editor . view . dom ) {
146+ try {
147+ const currentMarkdown = editor . getMarkdown ( ) ;
148+ if ( currentMarkdown !== value ) {
149+ isUpdatingFromPropsRef . current = true ;
105150 if ( value && value . trim ( ) !== '' ) {
106151 editor . commands . setContent ( value , { contentType : 'markdown' } ) ;
107152 } else {
108153 editor . commands . clearContent ( ) ;
109154 }
110155 lastSyncedValueRef . current = value || '' ;
111- if ( ! adaptedEditorRef . current ) {
112- adaptedEditorRef . current = createTipTapAdapter ( editor ) ;
113- }
114- onEditorReady ?.( adaptedEditorRef . current ) ;
115- } else {
116- setTimeout ( checkEditorReady , 10 ) ;
156+ setTimeout ( ( ) => {
157+ isUpdatingFromPropsRef . current = false ;
158+ } , 0 ) ;
117159 }
118- } ;
119-
120- checkEditorReady ( ) ;
121- } , [ editor ] ) ;
122-
123- useEffect ( ( ) => {
124- if ( ! editor ) {
125- return ;
126- }
127-
128- if ( value === lastSyncedValueRef . current ) {
129- return ;
130- }
131-
132- const currentMarkdown = editor . getMarkdown ( ) ;
133- if ( currentMarkdown !== value ) {
134- if ( value && value . trim ( ) !== '' ) {
135- editor . commands . setContent ( value , { contentType : 'markdown' } ) ;
136- } else {
137- editor . commands . clearContent ( ) ;
138- }
139- lastSyncedValueRef . current = value || '' ;
160+ } catch ( error ) {
161+ console . warn ( 'Editor view not available when syncing value:' , error ) ;
140162 }
141163 } , [ editor , value ] ) ;
142164
143165 useEffect ( ( ) => {
144- if ( editor && autoFocus ) {
145- setTimeout ( ( ) => {
146- editor . commands . focus ( ) ;
147- } , 100 ) ;
148- }
149- } , [ editor , autoFocus ] ) ;
166+ initialValueRef . current = value ;
167+ lastSyncedValueRef . current = value ;
168+ isInitializedRef . current = false ;
169+ adaptedEditorRef . current = null ;
170+ isUpdatingFromPropsRef . current = false ;
171+
172+ return ( ) => {
173+ if ( editor ) {
174+ editor . destroy ( ) ;
175+ }
176+ isInitializedRef . current = false ;
177+ adaptedEditorRef . current = null ;
178+ isUpdatingFromPropsRef . current = false ;
179+ } ;
180+ } , [ editor ] ) ;
150181
151182 if ( ! editor ) {
152183 return < div className = "editor-loading" > Loading editor...</ div > ;
153184 }
154185
155- return (
156- < div className = "rich-editor-wrap" >
157- < EditorContent editor = { editor } />
158- </ div >
159- ) ;
186+ return < EditorContent className = "rich-editor-wrap" editor = { editor } /> ;
160187} ;
161188
162189export default RichEditor ;
0 commit comments