mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 22:53:08 +08:00
05b3c65b0f
* feat: notifications * feat: watchers * improvements * handle page move for watchers * make watchers non-blocking * more
144 lines
4.0 KiB
TypeScript
144 lines
4.0 KiB
TypeScript
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 (
|
|
<div
|
|
ref={focusRef}
|
|
className={classes.commentEditor}
|
|
data-editable={editable || undefined}
|
|
>
|
|
<EditorContent
|
|
editor={commentEditor}
|
|
className={clsx(classes.ProseMirror, { [classes.focused]: focused })}
|
|
/>
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
|
|
export default CommentEditor;
|