Skip to content

Commit d87726b

Browse files
author
robin
committed
refactor(editor): enhance editor components with base props and initialization logic
- Introduced BaseEditorProps interface to standardize props across MarkdownEditor and RichEditor components. - Improved initialization logic in RichEditor and MarkdownEditor to handle editor state more effectively. - Updated useEditor hook to support initial values and prevent unnecessary updates during prop changes. - Refactored command methods to utilize dispatch for state changes in CodeMirror editor.
1 parent 498c142 commit d87726b

9 files changed

Lines changed: 145 additions & 278 deletions

File tree

ui/src/components/Editor/MarkdownEditor.tsx

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,10 @@ import { useEffect, useRef } from 'react';
2121

2222
import { EditorView } from '@codemirror/view';
2323

24-
import { Editor } from './types';
24+
import { BaseEditorProps } from './types';
2525
import { useEditor } from './utils';
2626

27-
interface MarkdownEditorProps {
28-
value: string;
29-
onChange?: (value: string) => void;
30-
onFocus?: () => void;
31-
onBlur?: () => void;
32-
placeholder?: string;
33-
autoFocus?: boolean;
34-
onEditorReady?: (editor: Editor) => void;
35-
}
27+
interface MarkdownEditorProps extends BaseEditorProps {}
3628

3729
const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
3830
value,
@@ -45,6 +37,7 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
4537
}) => {
4638
const editorRef = useRef<HTMLDivElement>(null);
4739
const lastSyncedValueRef = useRef<string>(value);
40+
const isInitializedRef = useRef<boolean>(false);
4841

4942
const editor = useEditor({
5043
editorRef,
@@ -53,24 +46,20 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
5346
onBlur,
5447
placeholder,
5548
autoFocus,
49+
initialValue: value,
5650
});
5751

5852
useEffect(() => {
59-
if (!editor) {
53+
if (!editor || isInitializedRef.current) {
6054
return;
6155
}
6256

63-
editor.setValue(value || '');
64-
lastSyncedValueRef.current = value || '';
57+
isInitializedRef.current = true;
6558
onEditorReady?.(editor);
66-
}, [editor]);
59+
}, [editor, onEditorReady]);
6760

6861
useEffect(() => {
69-
if (!editor) {
70-
return;
71-
}
72-
73-
if (value === lastSyncedValueRef.current) {
62+
if (!editor || value === lastSyncedValueRef.current) {
7463
return;
7564
}
7665

@@ -82,15 +71,19 @@ const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
8271
}, [editor, value]);
8372

8473
useEffect(() => {
74+
lastSyncedValueRef.current = value;
75+
isInitializedRef.current = false;
76+
8577
return () => {
8678
if (editor) {
8779
const view = editor as unknown as EditorView;
8880
if (view.destroy) {
8981
view.destroy();
9082
}
9183
}
84+
isInitializedRef.current = false;
9285
};
93-
}, [editor]);
86+
}, []);
9487

9588
return (
9689
<div className="content-wrap">

ui/src/components/Editor/RichEditor.tsx

Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,10 @@ import { Markdown } from '@tiptap/markdown';
3030
import Image from '@tiptap/extension-image';
3131
import { TableKit } from '@tiptap/extension-table';
3232

33-
import { Editor } from './types';
33+
import { Editor, BaseEditorProps } from './types';
3434
import { 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

4638
const 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

162189
export default RichEditor;

0 commit comments

Comments
 (0)