forked from 77media/video-flow
98 lines
2.6 KiB
TypeScript
98 lines
2.6 KiB
TypeScript
import React, { useState, useCallback, useEffect } from 'react';
|
|
import { flushSync } from 'react-dom';
|
|
import { EditorContent, useEditor } from '@tiptap/react';
|
|
import StarterKit from '@tiptap/starter-kit';
|
|
import { HighlightTextExtension } from './HighlightText';
|
|
|
|
interface MainEditorProps {
|
|
content: any[];
|
|
onChangeContent?: (content: any[]) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export default function MainEditor({ content, onChangeContent, disabled }: MainEditorProps) {
|
|
const [renderContent, setRenderContent] = useState<any[]>(content);
|
|
|
|
useEffect(() => {
|
|
onChangeContent?.(renderContent);
|
|
}, [renderContent]);
|
|
|
|
const editor = useEditor({
|
|
extensions: [
|
|
StarterKit.configure({
|
|
paragraph: {
|
|
HTMLAttributes: {
|
|
class: 'paragraph'
|
|
}
|
|
},
|
|
}),
|
|
HighlightTextExtension,
|
|
],
|
|
content: {
|
|
type: 'doc',
|
|
content: renderContent.length === 0 ? [{ type: 'paragraph', content: [] }] : renderContent
|
|
},
|
|
editorProps: {
|
|
attributes: {
|
|
class: `prose prose-invert max-w-none focus:outline-none ${disabled ? 'cursor-not-allowed' : ''}`
|
|
},
|
|
handleDOMEvents: {
|
|
keydown: (view, event) => {
|
|
// 如果内容为空且按下删除键或退格键,阻止默认行为
|
|
if (
|
|
(event.key === 'Backspace' || event.key === 'Delete') &&
|
|
editor?.isEmpty
|
|
) {
|
|
event.preventDefault();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
onCreate: ({ editor }) => {
|
|
editor.setOptions({ editable: !disabled });
|
|
},
|
|
onUpdate: ({ editor }) => {
|
|
try {
|
|
const json = editor.getJSON();
|
|
// 确保至少有一个空段落
|
|
const safeContent = json.content.length === 0
|
|
? [{ type: 'paragraph', content: [] }]
|
|
: json.content;
|
|
|
|
flushSync(() => {
|
|
setRenderContent(safeContent);
|
|
});
|
|
} catch (error) {
|
|
console.error('Editor update error:', error);
|
|
}
|
|
},
|
|
immediatelyRender: false, // 解决 SSR 水合问题
|
|
});
|
|
|
|
// 监听编辑器内容变化,确保始终有一个段落
|
|
useEffect(() => {
|
|
const handleEmpty = () => {
|
|
if (editor?.isEmpty) {
|
|
editor.commands.setContent([{
|
|
type: 'paragraph',
|
|
content: []
|
|
}]);
|
|
}
|
|
};
|
|
|
|
editor?.on('update', handleEmpty);
|
|
return () => {
|
|
editor?.off('update', handleEmpty);
|
|
};
|
|
}, [editor]);
|
|
|
|
if (!editor) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<EditorContent editor={editor} />
|
|
);
|
|
} |