forked from 77media/video-flow
113 lines
2.9 KiB
TypeScript
113 lines
2.9 KiB
TypeScript
import React, { useState, useCallback, useEffect } from 'react';
|
||
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;
|
||
}
|
||
|
||
declare module '@tiptap/core' {
|
||
interface Commands<ReturnType> {
|
||
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;
|
||
getContent: () => any;
|
||
}
|
||
|
||
const ShotEditor = React.forwardRef<EditorRef, ShotEditorProps>(
|
||
function ShotEditor({ content, onCharacterClick, roles, placeholder }, ref) {
|
||
const [segments, setSegments] = useState(content);
|
||
const [isOptimizing, setIsOptimizing] = useState(false);
|
||
|
||
const handleSmartPolish = () => {
|
||
setIsOptimizing(true);
|
||
setTimeout(() => {
|
||
setIsOptimizing(false);
|
||
}, 3000);
|
||
};
|
||
|
||
const editor = useEditor({
|
||
extensions: [
|
||
StarterKit,
|
||
CharacterTokenExtension.configure({
|
||
roles
|
||
}),
|
||
ShotTitle,
|
||
ReadonlyText,
|
||
Placeholder.configure({
|
||
placeholder: placeholder || 'Add shot description here...',
|
||
showOnlyWhenEditable: true,
|
||
showOnlyCurrent: false,
|
||
}),
|
||
],
|
||
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()
|
||
console.log('-==========json===========-', json);
|
||
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);
|
||
},
|
||
getContent: () => {
|
||
return segments;
|
||
}
|
||
}), [editor]) // 依赖 editor,确保更新
|
||
|
||
if (!editor) {
|
||
return null
|
||
}
|
||
|
||
return (
|
||
<EditorContent editor={editor} />
|
||
)
|
||
}
|
||
);
|
||
|
||
export default ShotEditor; |