| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- import { Editor, EditorContent, Extension, useEditor } from '@tiptap/react';
- import React, { useCallback, useEffect, useMemo, useRef } from 'react';
- import Document from '@tiptap/extension-document';
- import Text from '@tiptap/extension-text';
- import { UndoRedo } from '@tiptap/extensions';
- import Paragraph from '@tiptap/extension-paragraph';
- import HardBreak from '@tiptap/extension-hard-break';
- import { Placeholder } from '@tiptap/extensions';
- import InputSlot from './extension/inputSlot';
- import SelectSlot from './extension/selectSlot';
- import SkillSlot from './extension/skillSlot';
- import { strings } from '@douyinfe/semi-foundation/aiChatInput/constants';
- import { Content as TiptapContent } from "@tiptap/core";
- import { cssClasses } from '@douyinfe/semi-foundation/aiChatInput/constants';
- import { EditorView } from '@tiptap/pm/view';
- import { handleCompositionEndLogic, handlePasteLogic, handleTextInputLogic, handleZeroWidthCharLogic } from './extension/plugins';
- import SemiStatusExtension from './extension/statusExtension';
- const PREFIX = cssClasses.PREFIX;
- export default (props: {
- innerRef?: React.Ref<HTMLDivElement>;
- defaultContent?: TiptapContent;
- placeholder?: string;
- setEditor?: (editor: Editor) => void;
- onKeyDown?: (e: KeyboardEvent) => void;
- onChange?: (content: string) => void;
- extensions?: Extension[];
- handleKeyDown?: (view: any, event: KeyboardEvent) => boolean;
- onPaste?: (files: File[]) => void;
- onFocus?: (event: FocusEvent) => void;
- onBlur?: (event: FocusEvent) => void;
- handleCreate?: () => void
- }) => {
- const { setEditor, onKeyDown, onChange, placeholder, extensions = [],
- defaultContent, onPaste, innerRef, handleKeyDown, onFocus, onBlur, handleCreate } = props;
- const isComposing = useRef(false);
-
- const handleCompositionStart = useCallback((view: EditorView) => {
- isComposing.current = true;
- }, []);
- const handleCompositionEnd = useCallback((view: EditorView) => {
- isComposing.current = false;
- handleCompositionEndLogic(view);
- }, []);
- const handleTextInput = useCallback((view: EditorView, from: number, to: number, text: string) => {
- if (isComposing.current) {
- return false;
- }
- return handleTextInputLogic(view, from, to, text);
- }, []);
- const allExtensions = useMemo(() => {
- return [
- Document, Paragraph, Text, UndoRedo, HardBreak,
- InputSlot, SelectSlot, SkillSlot,
- Placeholder.configure({
- placeholder: placeholder,
- }),
- SemiStatusExtension,
- ...extensions,
- ];
- }, [extensions, placeholder]);
- const editorProps = useMemo(() => {
- return {
- handleKeyDown: handleKeyDown,
- handlePaste: handlePasteLogic,
- handleTextInput,
- handleDOMEvents: {
- compositionstart: handleCompositionStart,
- compositionend: handleCompositionEnd,
- }
- };
- }, [handleKeyDown, handleTextInput, handleCompositionStart, handleCompositionEnd]);
- // const onSelectionUpdate = useCallback(({ editor }) => {
- // // For debug
- // const fromPos = editor.state.selection.from;
- // const { $from } = editor.state.selection;
- // console.log('光标/选区位置', fromPos, editor.state.selection, editor.state.doc);
- // // console.log('before', $from.nodeBefore, $from.nodeAfter);
- // }, []);
- const onCreate = useCallback(({ editor }) => {
- const { state, view } = editor;
- const tr = handleZeroWidthCharLogic(state);
- if (tr) {
- // 一次性触发,避免多次触发导致 appendTransaction 被多次调用
- view.dispatch(tr);
- }
- handleCreate();
- }, [handleCreate]);
- const onUpdate = useCallback(({ editor }) => {
- // The content has changed.
- const content = editor.getText();
- onChange(content);
- }, [onChange]);
- const handlePaste = useCallback((e) => {
- // To support file paste
- const items = e.clipboardData?.items as any;
- let files = [];
- if (items) {
- for (const it of items) {
- const file = it.getAsFile();
- file && files.push(it.getAsFile());
- }
- }
- if (files.length) {
- onPaste?.(files);
- }
- }, [onPaste]);
- const editor = useEditor({
- extensions: allExtensions as Extension[],
- content: defaultContent ?? ``,
- editorProps: editorProps,
- // onSelectionUpdate,
- onCreate,
- onUpdate,
- onPaste: handlePaste,
- });
- useEffect(() => {
- setEditor(editor);
- }, [editor, setEditor]);
- return (<>
- <EditorContent
- editor={editor}
- onKeyDown={onKeyDown as any}
- onFocus={onFocus as any}
- onBlur={onBlur as any}
- ref={innerRef}
- className={`${PREFIX}-editor-content`}
- />
- </>);
- };
|