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 Placeholder from '@tiptap/extension-placeholder' import { motion } from "framer-motion"; import { CharacterTokenExtension } from './CharacterToken'; import { ShotTitle } from './ShotTitle'; import { ReadonlyText } from './ReadonlyText'; interface ShotEditorProps { content: any[]; roles?: any[]; onCharacterClick?: (attrs: any) => void; placeholder?: string; onChangeContent?: (content: any) => void; } declare module '@tiptap/core' { interface Commands { characterToken: { setCharacterToken: (attrs: any) => ReturnType; } } } interface CharacterToken { type: 'characterToken'; attrs: { name: string; gender: string; age: string; avatar: string; }; } interface EditorRef { editor: any; insertCharacter: (character: CharacterToken) => void; insertContent: (content: any) => void; } const ShotEditor = React.forwardRef( function ShotEditor({ content, onCharacterClick, roles, placeholder, onChangeContent }, ref) { const [segments, setSegments] = useState(content); const [isOptimizing, setIsOptimizing] = useState(false); useEffect(() => { onChangeContent?.(segments); }, [segments]); const handleSmartPolish = () => { setIsOptimizing(true); setTimeout(() => { setIsOptimizing(false); }, 3000); }; const editor = useEditor({ extensions: [ StarterKit, CharacterTokenExtension.configure({ roles }), ShotTitle, ReadonlyText, Placeholder.configure({ placeholder: ({ node }) => { console.log('-==========placeholder node===========-', segments); return placeholder || 'Add shot description here...'; }, }), ], content: { type: 'doc', content: segments }, editorProps: { attributes: { class: 'prose prose-invert max-w-none focus:outline-none' } }, immediatelyRender: false, onCreate: ({ editor }) => { editor.setOptions({ editable: true }) }, onUpdate: ({ editor }) => { const json = editor.getJSON(); flushSync(() => { setSegments(json.content); }); }, }) // 暴露方法给父组件 React.useImperativeHandle(ref, () => ({ editor, insertCharacter: (character: CharacterToken) => { editor?.commands.insertContent([ { type: 'text', text: ' ' }, character, { type: 'text', text: ' ' } ]); }, insertContent: (content: any) => { editor?.commands.insertContent(content); } }), [editor]) // 依赖 editor,确保更新 if (!editor) { return null } return ( ) } ); export default ShotEditor;