import { EditorContent, ReactNodeViewRenderer, useEditor } from "@tiptap/react"; import { Placeholder } from "@tiptap/extension-placeholder"; import { StarterKit } from "@tiptap/starter-kit"; import { Mention, LinkExtension } from "@docmost/editor-ext"; import classes from "./comment.module.css"; import { useFocusWithin } from "@mantine/hooks"; import clsx from "clsx"; import { forwardRef, useEffect, useImperativeHandle } from "react"; import { useTranslation } from "react-i18next"; import EmojiCommand from "@/features/editor/extensions/emoji-command"; import mentionRenderItems from "@/features/editor/components/mention/mention-suggestion"; import MentionView from "@/features/editor/components/mention/mention-view"; interface CommentEditorProps { defaultContent?: any; onUpdate?: any; onSave?: any; editable: boolean; placeholder?: string; autofocus?: boolean; } const CommentEditor = forwardRef( ( { defaultContent, onUpdate, onSave, editable, placeholder, autofocus, }: CommentEditorProps, ref, ) => { const { t } = useTranslation(); const { ref: focusRef, focused } = useFocusWithin(); const commentEditor = useEditor({ extensions: [ StarterKit.configure({ gapcursor: false, dropcursor: false, link: false, }), Placeholder.configure({ placeholder: placeholder || t("Reply..."), }), LinkExtension, EmojiCommand, Mention.configure({ suggestion: { allowSpaces: true, items: () => [], // @ts-ignore render: mentionRenderItems, }, HTMLAttributes: { class: "mention", }, }).extend({ addNodeView() { this.editor.isInitialized = true; return ReactNodeViewRenderer(MentionView); }, }), ], editorProps: { handleDOMEvents: { keydown: (_view, event) => { if ( [ "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Enter", ].includes(event.key) ) { const emojiCommand = document.querySelector("#emoji-command"); const mentionPopup = document.querySelector("#mention"); if (emojiCommand || mentionPopup) { return true; } } if ((event.ctrlKey || event.metaKey) && event.key === "Enter") { event.preventDefault(); if (onSave) onSave(); return true; } }, }, }, onUpdate({ editor }) { if (onUpdate) onUpdate(editor.getJSON()); }, content: defaultContent, editable, immediatelyRender: true, shouldRerenderOnTransaction: false, autofocus: (autofocus && "end") || false, }); // Sync content from props for read-only editors (e.g. when updated via // websocket on another browser). Skip for editable editors to avoid // resetting the cursor position on every keystroke. useEffect(() => { if (!editable && commentEditor && defaultContent) { commentEditor.commands.setContent(defaultContent); } }, [defaultContent, editable, commentEditor]); useEffect(() => { setTimeout(() => { if (autofocus) { commentEditor?.commands.focus("end"); } }, 10); }, [commentEditor, autofocus]); useImperativeHandle(ref, () => ({ clearContent: () => { commentEditor.commands.clearContent(); }, })); return (
); }, ); export default CommentEditor;