@examplary/ui 1.53.1 → 1.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/components/rich-text/minimal-rich-text-field.d.ts +3 -1
  2. package/dist/components/rich-text/minimal-rich-text-field.js +19 -10
  3. package/dist/components/rich-text/tiptap/comments/comment-bubble.d.ts +8 -0
  4. package/dist/components/rich-text/tiptap/comments/comment-bubble.js +96 -0
  5. package/dist/components/rich-text/tiptap/comments/comment-reply.d.ts +21 -0
  6. package/dist/components/rich-text/tiptap/comments/comment-reply.js +230 -0
  7. package/dist/components/rich-text/tiptap/comments/comment.d.ts +22 -0
  8. package/dist/components/rich-text/tiptap/comments/comment.js +199 -0
  9. package/dist/components/rich-text/tiptap/comments/helpers.d.ts +19 -0
  10. package/dist/components/rich-text/tiptap/comments/helpers.js +95 -0
  11. package/dist/components/rich-text/tiptap/comments/types.d.ts +29 -0
  12. package/dist/components/rich-text/tiptap/comments/types.js +1 -0
  13. package/dist/components/rich-text/tiptap/extensions.js +5 -1
  14. package/dist/components/rich-text/tiptap/rich-text-formatting-menu.d.ts +5 -2
  15. package/dist/components/rich-text/tiptap/rich-text-formatting-menu.js +103 -37
  16. package/dist/components/ui/comments.d.ts +27 -0
  17. package/dist/components/ui/comments.js +142 -0
  18. package/dist/components/ui/floating-menu.d.ts +6 -0
  19. package/dist/components/ui/floating-menu.js +28 -0
  20. package/dist/components/ui/index.d.ts +3 -0
  21. package/dist/components/ui/index.js +3 -0
  22. package/dist/components/ui/text-selection-menu.d.ts +17 -0
  23. package/dist/components/ui/text-selection-menu.js +62 -0
  24. package/dist/components/web-components/content-reference.js +35 -3
  25. package/dist/components/web-components/index.d.ts +2 -0
  26. package/dist/components/web-components/index.js +11 -0
  27. package/dist/components/web-components/inline-comment-reply.d.ts +3 -0
  28. package/dist/components/web-components/inline-comment-reply.js +25 -0
  29. package/dist/components/web-components/inline-comment.d.ts +3 -0
  30. package/dist/components/web-components/inline-comment.js +25 -0
  31. package/dist/src/global.css +1 -1
  32. package/package.json +4 -1
  33. package/src/global.css +11 -0
@@ -1,5 +1,6 @@
1
1
  import { Editor, EditorContentProps } from "@tiptap/react";
2
2
  import { XmlFragment } from "yjs";
3
+ import { CommentAuthor } from "./tiptap/comments/types";
3
4
  export declare const getEditorById: (id: string) => Editor;
4
5
  export type MinimalRichTextFieldProps = {
5
6
  className?: string;
@@ -7,6 +8,7 @@ export type MinimalRichTextFieldProps = {
7
8
  defaultValue?: string;
8
9
  placeholder?: string;
9
10
  style?: string;
11
+ commentAuthor?: CommentAuthor;
10
12
  onChange?: (value: string) => void;
11
13
  onBlur?: (value: string) => void;
12
14
  slotBefore?: (editor: any) => React.ReactNode;
@@ -21,4 +23,4 @@ export type MinimalRichTextFieldProps = {
21
23
  fragment?: XmlFragment;
22
24
  awareness?: any;
23
25
  } & Omit<EditorContentProps, "editor">;
24
- export declare const MinimalRichTextField: ({ className, value, defaultValue, placeholder, onChange, onBlur, slotBefore, style, singleLine, autoFocus, showFormattingMenu, uploadFile, fragment, awareness, id, ...props }: MinimalRichTextFieldProps) => import("react/jsx-runtime").JSX.Element;
26
+ export declare const MinimalRichTextField: ({ className, value, defaultValue, placeholder, onChange, onBlur, slotBefore, style, singleLine, autoFocus, showFormattingMenu, commentAuthor, uploadFile, fragment, awareness, id, ...props }: MinimalRichTextFieldProps) => import("react/jsx-runtime").JSX.Element;
@@ -40,6 +40,9 @@ import { EditorContent, useEditor, } from "@tiptap/react";
40
40
  import { CollaborationCaret } from "./tiptap/collaboration-caret";
41
41
  import { cn } from "../../utils";
42
42
  import { registerWebComponents } from "../web-components";
43
+ import { Comment } from "./tiptap/comments/comment";
44
+ import { CommentBubble } from "./tiptap/comments/comment-bubble";
45
+ import { CommentReply } from "./tiptap/comments/comment-reply";
43
46
  import { defaultRichTextExtensions } from "./tiptap/extensions";
44
47
  import { fileHandler } from "./tiptap/file-handler";
45
48
  import Image from "./tiptap/image";
@@ -81,7 +84,7 @@ var isEmpty = function (value) {
81
84
  return false;
82
85
  };
83
86
  export var MinimalRichTextField = function (_a) {
84
- var _b = _a.className, className = _b === void 0 ? "" : _b, _c = _a.value, value = _c === void 0 ? "" : _c, defaultValue = _a.defaultValue, _d = _a.placeholder, placeholder = _d === void 0 ? "" : _d, onChange = _a.onChange, onBlur = _a.onBlur, slotBefore = _a.slotBefore, style = _a.style, _e = _a.singleLine, singleLine = _e === void 0 ? false : _e, _f = _a.autoFocus, autoFocus = _f === void 0 ? false : _f, _g = _a.showFormattingMenu, showFormattingMenu = _g === void 0 ? false : _g, _h = _a.uploadFile, uploadFile = _h === void 0 ? undefined : _h, fragment = _a.fragment, awareness = _a.awareness, id = _a.id, props = __rest(_a, ["className", "value", "defaultValue", "placeholder", "onChange", "onBlur", "slotBefore", "style", "singleLine", "autoFocus", "showFormattingMenu", "uploadFile", "fragment", "awareness", "id"]);
87
+ var _b = _a.className, className = _b === void 0 ? "" : _b, _c = _a.value, value = _c === void 0 ? "" : _c, defaultValue = _a.defaultValue, _d = _a.placeholder, placeholder = _d === void 0 ? "" : _d, onChange = _a.onChange, onBlur = _a.onBlur, slotBefore = _a.slotBefore, style = _a.style, _e = _a.singleLine, singleLine = _e === void 0 ? false : _e, _f = _a.autoFocus, autoFocus = _f === void 0 ? false : _f, _g = _a.showFormattingMenu, showFormattingMenu = _g === void 0 ? false : _g, commentAuthor = _a.commentAuthor, _h = _a.uploadFile, uploadFile = _h === void 0 ? undefined : _h, fragment = _a.fragment, awareness = _a.awareness, id = _a.id, props = __rest(_a, ["className", "value", "defaultValue", "placeholder", "onChange", "onBlur", "slotBefore", "style", "singleLine", "autoFocus", "showFormattingMenu", "commentAuthor", "uploadFile", "fragment", "awareness", "id"]);
85
88
  var _j = useState(value), content = _j[0], setContent = _j[1];
86
89
  var extensions = useMemo(function () {
87
90
  return __spreadArray(__spreadArray([], defaultRichTextExtensions, true), [
@@ -99,21 +102,27 @@ export var MinimalRichTextField = function (_a) {
99
102
  awareness && CollaborationCaret.configure({ awareness: awareness }),
100
103
  placeholder && Placeholder.configure({ placeholder: placeholder }),
101
104
  uploadFile && fileHandler(uploadFile),
105
+ commentAuthor && Comment.configure({ createdBy: commentAuthor }),
106
+ commentAuthor && CommentReply.configure({ createdBy: commentAuthor }),
102
107
  ], false).filter(Boolean);
103
- }, [singleLine, placeholder, uploadFile, fragment, awareness]);
108
+ }, [singleLine, placeholder, uploadFile, fragment, awareness, commentAuthor]);
104
109
  var editor = useEditor({
105
110
  onUpdate: function (_a) {
106
111
  var editor = _a.editor;
107
- var html = editor.getHTML();
108
- html = isEmpty(html) ? "" : html;
109
- setContent(html);
110
- onChange === null || onChange === void 0 ? void 0 : onChange(html);
112
+ if (onChange || !fragment) {
113
+ var html = editor.getHTML();
114
+ html = isEmpty(html) ? "" : html;
115
+ setContent(html);
116
+ onChange === null || onChange === void 0 ? void 0 : onChange(html);
117
+ }
111
118
  },
112
119
  onBlur: function (_a) {
113
120
  var editor = _a.editor;
114
- var html = editor.getHTML();
115
- html = isEmpty(html) ? "" : html;
116
- onBlur === null || onBlur === void 0 ? void 0 : onBlur(html);
121
+ if (onBlur) {
122
+ var html = editor.getHTML();
123
+ html = isEmpty(html) ? "" : html;
124
+ onBlur(html);
125
+ }
117
126
  },
118
127
  autofocus: autoFocus,
119
128
  content: fragment ? undefined : content,
@@ -161,5 +170,5 @@ export var MinimalRichTextField = function (_a) {
161
170
  }
162
171
  };
163
172
  }, [editor, id]);
164
- return (_jsxs(_Fragment, { children: [slotBefore ? slotBefore(editor) : null, showFormattingMenu && _jsx(RichTextFormattingMenu, { editor: editor }), _jsx(EditorContent, __assign({ editor: editor }, props))] }));
173
+ return (_jsxs(_Fragment, { children: [slotBefore ? slotBefore(editor) : null, showFormattingMenu && (_jsx(RichTextFormattingMenu, { allowComments: !!commentAuthor, editor: editor })), !!commentAuthor && (_jsx(CommentBubble, { editor: editor, commentAuthor: commentAuthor })), _jsx(EditorContent, __assign({ editor: editor }, props))] }));
165
174
  };
@@ -0,0 +1,8 @@
1
+ import { Editor } from "@tiptap/core";
2
+ import { CommentAttrs } from "./types";
3
+ type CommentBubbleProps = {
4
+ editor: Editor;
5
+ commentAuthor?: CommentAttrs["createdBy"];
6
+ };
7
+ export declare const CommentBubble: ({ editor, commentAuthor, }: CommentBubbleProps) => import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useState } from "react";
3
+ import { Trans } from "react-i18next";
4
+ import { useEditorState } from "@tiptap/react";
5
+ import { BubbleMenu } from "@tiptap/react/menus";
6
+ import { findReplies } from "./helpers";
7
+ import { Button } from "../../../ui/button";
8
+ import { CommentItem } from "../../../ui/comments";
9
+ import { FloatingMenu } from "../../../ui/floating-menu";
10
+ var CommentForm = function (_a) {
11
+ var editor = _a.editor, comment = _a.comment, commentAuthor = _a.commentAuthor, replies = _a.replies;
12
+ var _b = useState(new Set()), editingIds = _b[0], setEditingIds = _b[1];
13
+ var isEditingAny = editingIds.size > 0;
14
+ var onEditingChange = useCallback(function (id, editing) {
15
+ if (!id)
16
+ return;
17
+ setEditingIds(function (prev) {
18
+ var has = prev.has(id);
19
+ if (editing === has)
20
+ return prev;
21
+ var next = new Set(prev);
22
+ if (editing)
23
+ next.add(id);
24
+ else
25
+ next.delete(id);
26
+ return next;
27
+ });
28
+ }, []);
29
+ var saveComment = function (value) {
30
+ var trimmed = value.trim();
31
+ if (!trimmed) {
32
+ editor.chain().focus().removeComment({ id: comment.id }).run();
33
+ return;
34
+ }
35
+ editor
36
+ .chain()
37
+ .focus()
38
+ .updateComment({ id: comment.id, value: trimmed })
39
+ .run();
40
+ };
41
+ var removeComment = function () {
42
+ editor.chain().focus().removeComment({ id: comment.id }).run();
43
+ };
44
+ var saveReply = function (id) { return function (value) {
45
+ var trimmed = value.trim();
46
+ if (!trimmed) {
47
+ editor.chain().focus().removeCommentReply({ id: id }).run();
48
+ return;
49
+ }
50
+ editor.chain().focus().updateCommentReply({ id: id, value: trimmed }).run();
51
+ }; };
52
+ var removeReply = function (id) { return function () {
53
+ editor.chain().focus().removeCommentReply({ id: id }).run();
54
+ }; };
55
+ var _c = useState(false), draftOpen = _c[0], setDraftOpen = _c[1];
56
+ var submitDraft = function (value) {
57
+ var trimmed = value.trim();
58
+ if (trimmed) {
59
+ editor
60
+ .chain()
61
+ .focus()
62
+ .addCommentReply({ commentId: comment.id, value: trimmed })
63
+ .run();
64
+ }
65
+ setDraftOpen(false);
66
+ };
67
+ return (_jsxs(FloatingMenu, { className: "flex flex-col w-80 ml-3", children: [_jsx(CommentItem, { comment: comment, onSave: saveComment, onResolve: removeComment, onDelete: removeComment, commentAuthor: commentAuthor, onEditingChange: onEditingChange, className: "p-3" }), replies.map(function (reply) { return (_jsx(CommentItem, { comment: reply, onSave: saveReply(reply.id), onDelete: removeReply(reply.id), commentAuthor: commentAuthor, onEditingChange: onEditingChange, isTopLevel: false, className: "p-3 pl-11" }, reply.id)); }), draftOpen && (_jsx(CommentItem, { comment: {
68
+ id: "reply-draft",
69
+ inReplyTo: comment.id,
70
+ createdBy: commentAuthor,
71
+ }, commentAuthor: commentAuthor, onSave: submitDraft, onCancel: function () { return setDraftOpen(false); }, isTopLevel: false, className: "p-3 pl-11" })), !isEditingAny && !draftOpen && (_jsx("div", { className: "ml-12 pb-3", children: _jsx(Button, { size: "sm", variant: "secondary", className: "text-xs h-7", onClick: function () { return setDraftOpen(true); }, children: _jsx(Trans, { children: "formatting.comments.reply" }) }) }))] }));
72
+ };
73
+ export var CommentBubble = function (_a) {
74
+ var editor = _a.editor, commentAuthor = _a.commentAuthor;
75
+ var state = useEditorState({
76
+ editor: editor,
77
+ selector: function (ctx) {
78
+ var isComment = ctx.editor.isActive("comment");
79
+ var attrs = isComment
80
+ ? ctx.editor.getAttributes("comment")
81
+ : null;
82
+ var replies = attrs
83
+ ? findReplies(ctx.editor, attrs.id).map(function (r) { return r.attrs; })
84
+ : [];
85
+ return {
86
+ isComment: isComment,
87
+ attrs: attrs,
88
+ replies: replies,
89
+ };
90
+ },
91
+ });
92
+ return (_jsx(BubbleMenu, { editor: editor, shouldShow: function (_a) {
93
+ var editor = _a.editor;
94
+ return editor.isActive("comment");
95
+ }, options: { placement: "bottom" }, children: state.isComment && state.attrs && (_jsx(CommentForm, { editor: editor, comment: state.attrs, commentAuthor: commentAuthor, replies: state.replies }, state.attrs.id)) }));
96
+ };
@@ -0,0 +1,21 @@
1
+ import { Node } from "@tiptap/core";
2
+ import { CommentReplyOptions } from "./types";
3
+ export declare const SKIP_RESCUE_META = "comment-reply:intentional-delete";
4
+ declare module "@tiptap/core" {
5
+ interface Commands<ReturnType> {
6
+ "inline-comment-reply": {
7
+ addCommentReply: (params: {
8
+ commentId: string;
9
+ value?: string;
10
+ }) => ReturnType;
11
+ updateCommentReply: (params: {
12
+ id: string;
13
+ value: string;
14
+ }) => ReturnType;
15
+ removeCommentReply: (params: {
16
+ id: string;
17
+ }) => ReturnType;
18
+ };
19
+ }
20
+ }
21
+ export declare const CommentReply: Node<CommentReplyOptions, any>;
@@ -0,0 +1,230 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ import { Node } from "@tiptap/core";
13
+ import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
14
+ import { createId, findReplies, locateCommentRanges, locateReplyById, } from "./helpers";
15
+ export var SKIP_RESCUE_META = "comment-reply:intentional-delete";
16
+ export var CommentReply = Node.create({
17
+ name: "inline-comment-reply",
18
+ inline: true,
19
+ group: "inline",
20
+ atom: true,
21
+ selectable: false,
22
+ draggable: false,
23
+ addOptions: function () {
24
+ return { createdBy: undefined };
25
+ },
26
+ addAttributes: function () {
27
+ return {
28
+ id: {
29
+ default: null,
30
+ parseHTML: function (el) { return el.getAttribute("id"); },
31
+ renderHTML: function (attrs) {
32
+ var _a;
33
+ return ({
34
+ id: (_a = attrs.id) !== null && _a !== void 0 ? _a : createId(),
35
+ });
36
+ },
37
+ },
38
+ commentId: {
39
+ default: null,
40
+ parseHTML: function (el) { return el.getAttribute("data-comment-id"); },
41
+ renderHTML: function (attrs) { return ({
42
+ "data-comment-id": attrs.commentId,
43
+ }); },
44
+ },
45
+ createdAt: {
46
+ default: null,
47
+ parseHTML: function (el) { return el.getAttribute("data-created-at"); },
48
+ renderHTML: function (attrs) { return ({
49
+ "data-created-at": attrs.createdAt,
50
+ }); },
51
+ },
52
+ createdBy: {
53
+ default: null,
54
+ parseHTML: function (el) { return ({
55
+ id: el.getAttribute("data-user-id"),
56
+ name: el.getAttribute("data-user-name"),
57
+ color: el.getAttribute("data-user-color"),
58
+ }); },
59
+ renderHTML: function (attrs) {
60
+ var _a, _b, _c;
61
+ return ({
62
+ "data-user-id": (_a = attrs.createdBy) === null || _a === void 0 ? void 0 : _a.id,
63
+ "data-user-name": (_b = attrs.createdBy) === null || _b === void 0 ? void 0 : _b.name,
64
+ "data-user-color": (_c = attrs.createdBy) === null || _c === void 0 ? void 0 : _c.color,
65
+ });
66
+ },
67
+ },
68
+ value: {
69
+ default: "",
70
+ parseHTML: function (el) { var _a; return (_a = el.getAttribute("value")) !== null && _a !== void 0 ? _a : ""; },
71
+ renderHTML: function (attrs) {
72
+ var _a;
73
+ return ({
74
+ value: (_a = attrs.value) !== null && _a !== void 0 ? _a : "",
75
+ });
76
+ },
77
+ },
78
+ };
79
+ },
80
+ parseHTML: function () {
81
+ return [{ tag: "inline-comment-reply" }];
82
+ },
83
+ renderHTML: function (_a) {
84
+ var HTMLAttributes = _a.HTMLAttributes;
85
+ return ["inline-comment-reply", HTMLAttributes];
86
+ },
87
+ addCommands: function () {
88
+ var _this = this;
89
+ return {
90
+ addCommentReply: function (_a) {
91
+ var commentId = _a.commentId, value = _a.value;
92
+ return function (_a) {
93
+ var state = _a.state, tr = _a.tr, dispatch = _a.dispatch;
94
+ var nodeType = state.schema.nodes["inline-comment-reply"];
95
+ if (!nodeType)
96
+ return false;
97
+ var ranges = locateCommentRanges(state.doc, commentId);
98
+ if (ranges.length === 0)
99
+ return false;
100
+ if (!dispatch)
101
+ return true;
102
+ var existing = findReplies(state.doc, commentId);
103
+ var last = existing[existing.length - 1];
104
+ var insertAt = last ? last.pos + last.nodeSize : ranges[0].from;
105
+ var node = nodeType.create({
106
+ id: createId(),
107
+ commentId: commentId,
108
+ createdBy: _this.options.createdBy,
109
+ createdAt: new Date().toISOString(),
110
+ value: value !== null && value !== void 0 ? value : "",
111
+ });
112
+ tr.insert(insertAt, node);
113
+ dispatch(tr);
114
+ return true;
115
+ };
116
+ },
117
+ updateCommentReply: function (_a) {
118
+ var id = _a.id, value = _a.value;
119
+ return function (_a) {
120
+ var state = _a.state, tr = _a.tr, dispatch = _a.dispatch;
121
+ var nodeType = state.schema.nodes["inline-comment-reply"];
122
+ if (!nodeType)
123
+ return false;
124
+ var located = locateReplyById(state.doc, id);
125
+ if (!located)
126
+ return false;
127
+ if (!dispatch)
128
+ return true;
129
+ var existing = state.doc.nodeAt(located.pos);
130
+ if (!existing)
131
+ return false;
132
+ tr.setNodeMarkup(located.pos, undefined, __assign(__assign({}, existing.attrs), { value: value }));
133
+ dispatch(tr);
134
+ return true;
135
+ };
136
+ },
137
+ removeCommentReply: function (_a) {
138
+ var id = _a.id;
139
+ return function (_a) {
140
+ var state = _a.state, tr = _a.tr, dispatch = _a.dispatch;
141
+ var located = locateReplyById(state.doc, id);
142
+ if (!located)
143
+ return false;
144
+ if (!dispatch)
145
+ return true;
146
+ tr.setMeta(SKIP_RESCUE_META, true);
147
+ tr.delete(located.pos, located.pos + located.nodeSize);
148
+ dispatch(tr);
149
+ return true;
150
+ };
151
+ },
152
+ };
153
+ },
154
+ addProseMirrorPlugins: function () {
155
+ return [
156
+ new Plugin({
157
+ key: new PluginKey("rescueCommentReplies"),
158
+ props: {
159
+ handleKeyDown: function (view, event) {
160
+ if (event.key !== "Backspace" && event.key !== "Delete") {
161
+ return false;
162
+ }
163
+ var selection = view.state.selection;
164
+ if (!selection.empty)
165
+ return false;
166
+ var $pos = view.state.doc.resolve(selection.from);
167
+ var node = event.key === "Backspace" ? $pos.nodeBefore : $pos.nodeAfter;
168
+ if ((node === null || node === void 0 ? void 0 : node.type.name) !== "inline-comment-reply")
169
+ return false;
170
+ var newPos = event.key === "Backspace"
171
+ ? selection.from - node.nodeSize
172
+ : selection.from + node.nodeSize;
173
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, newPos)));
174
+ return true;
175
+ },
176
+ },
177
+ appendTransaction: function (transactions, oldState, newState) {
178
+ if (!transactions.some(function (t) { return t.docChanged; }))
179
+ return null;
180
+ if (transactions.some(function (t) { return t.getMeta(SKIP_RESCUE_META); })) {
181
+ return null;
182
+ }
183
+ if (transactions.some(function (t) { return t.getMeta("addToHistory") === false; })) {
184
+ return null;
185
+ }
186
+ var nodeType = newState.schema.nodes["inline-comment-reply"];
187
+ if (!nodeType)
188
+ return null;
189
+ var oldReplies = [];
190
+ oldState.doc.descendants(function (node) {
191
+ if (node.type === nodeType) {
192
+ oldReplies.push({
193
+ id: node.attrs.id,
194
+ attrs: node.attrs,
195
+ });
196
+ }
197
+ });
198
+ if (oldReplies.length === 0)
199
+ return null;
200
+ var surviving = new Set();
201
+ newState.doc.descendants(function (node) {
202
+ if (node.type === nodeType)
203
+ surviving.add(node.attrs.id);
204
+ });
205
+ var missing = oldReplies.filter(function (r) { return !surviving.has(r.id); });
206
+ if (missing.length === 0)
207
+ return null;
208
+ var tr = newState.tr;
209
+ tr.setMeta("addToHistory", false);
210
+ var didChange = false;
211
+ for (var _i = 0, missing_1 = missing; _i < missing_1.length; _i++) {
212
+ var reply = missing_1[_i];
213
+ var commentId = reply.attrs.commentId;
214
+ if (!commentId)
215
+ continue;
216
+ var ranges = locateCommentRanges(tr.doc, commentId);
217
+ if (ranges.length === 0)
218
+ continue;
219
+ var existing = findReplies(tr.doc, commentId);
220
+ var last = existing[existing.length - 1];
221
+ var insertAt = last ? last.pos + last.nodeSize : ranges[0].from;
222
+ tr.insert(insertAt, nodeType.create(reply.attrs));
223
+ didChange = true;
224
+ }
225
+ return didChange ? tr : null;
226
+ },
227
+ }),
228
+ ];
229
+ },
230
+ });
@@ -0,0 +1,22 @@
1
+ import { Mark } from "@tiptap/core";
2
+ import { CommentOptions } from "./types";
3
+ declare module "@tiptap/core" {
4
+ interface Commands<ReturnType> {
5
+ comment: {
6
+ toggleComment: () => ReturnType;
7
+ addComment: (params: {
8
+ from: number;
9
+ to: number;
10
+ value?: string;
11
+ }) => ReturnType;
12
+ updateComment: (params: {
13
+ id: string;
14
+ value: string;
15
+ }) => ReturnType;
16
+ removeComment: (params: {
17
+ id: string;
18
+ }) => ReturnType;
19
+ };
20
+ }
21
+ }
22
+ export declare const Comment: Mark<CommentOptions, any>;
@@ -0,0 +1,199 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
13
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
14
+ if (ar || !(i in from)) {
15
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
16
+ ar[i] = from[i];
17
+ }
18
+ }
19
+ return to.concat(ar || Array.prototype.slice.call(from));
20
+ };
21
+ import { Mark } from "@tiptap/core";
22
+ import { SKIP_RESCUE_META } from "./comment-reply";
23
+ import { createId, findReplies, locateCommentRanges } from "./helpers";
24
+ export var Comment = Mark.create({
25
+ name: "comment",
26
+ inclusive: false,
27
+ spanning: true,
28
+ keepOnSplit: true,
29
+ excludes: "",
30
+ addOptions: function () {
31
+ return { createdBy: undefined };
32
+ },
33
+ addAttributes: function () {
34
+ return {
35
+ id: {
36
+ default: null,
37
+ parseHTML: function (el) { return el.getAttribute("id"); },
38
+ renderHTML: function (attrs) {
39
+ var _a;
40
+ return ({
41
+ id: (_a = attrs.id) !== null && _a !== void 0 ? _a : createId(),
42
+ });
43
+ },
44
+ },
45
+ createdAt: {
46
+ default: null,
47
+ parseHTML: function (el) { return el.getAttribute("data-created-at"); },
48
+ renderHTML: function (attrs) { return ({
49
+ "data-created-at": attrs.createdAt,
50
+ }); },
51
+ },
52
+ createdBy: {
53
+ default: null,
54
+ parseHTML: function (el) {
55
+ return {
56
+ id: el.getAttribute("data-user-id"),
57
+ name: el.getAttribute("data-user-name"),
58
+ color: el.getAttribute("data-user-color"),
59
+ };
60
+ },
61
+ renderHTML: function (attrs) {
62
+ var _a, _b, _c;
63
+ return ({
64
+ "data-user-id": (_a = attrs.createdBy) === null || _a === void 0 ? void 0 : _a.id,
65
+ "data-user-name": (_b = attrs.createdBy) === null || _b === void 0 ? void 0 : _b.name,
66
+ "data-user-color": (_c = attrs.createdBy) === null || _c === void 0 ? void 0 : _c.color,
67
+ });
68
+ },
69
+ },
70
+ value: {
71
+ default: "",
72
+ parseHTML: function (el) { var _a; return (_a = el.getAttribute("value")) !== null && _a !== void 0 ? _a : ""; },
73
+ renderHTML: function (attrs) {
74
+ var _a;
75
+ return ({
76
+ value: (_a = attrs.value) !== null && _a !== void 0 ? _a : "",
77
+ });
78
+ },
79
+ },
80
+ };
81
+ },
82
+ parseHTML: function () {
83
+ return [{ tag: "inline-comment" }];
84
+ },
85
+ renderHTML: function (_a) {
86
+ var _b;
87
+ var HTMLAttributes = _a.HTMLAttributes;
88
+ return [
89
+ "inline-comment",
90
+ __assign(__assign({}, HTMLAttributes), { style: "--comment-color: ".concat((_b = HTMLAttributes["data-user-color"]) !== null && _b !== void 0 ? _b : "var(--color-blue-400)") }),
91
+ 0,
92
+ ];
93
+ },
94
+ addCommands: function () {
95
+ var _this = this;
96
+ return {
97
+ addComment: function (_a) {
98
+ var from = _a.from, to = _a.to, value = _a.value;
99
+ return function (_a) {
100
+ var state = _a.state, tr = _a.tr, dispatch = _a.dispatch;
101
+ if (to <= from)
102
+ return false;
103
+ var markType = state.schema.marks.comment;
104
+ if (!markType)
105
+ return false;
106
+ if (!dispatch)
107
+ return true;
108
+ var attrs = {
109
+ id: createId(),
110
+ createdBy: _this.options.createdBy,
111
+ value: value !== null && value !== void 0 ? value : "",
112
+ createdAt: new Date().toISOString(),
113
+ };
114
+ tr.addMark(from, to, markType.create(attrs));
115
+ dispatch(tr);
116
+ return true;
117
+ };
118
+ },
119
+ toggleComment: function () {
120
+ return function (_a) {
121
+ var state = _a.state, tr = _a.tr, dispatch = _a.dispatch;
122
+ var markType = state.schema.marks.comment;
123
+ if (!markType || !dispatch)
124
+ return false;
125
+ // Find if there's a comment mark at the current selection
126
+ var _b = state.selection, from = _b.from, to = _b.to;
127
+ var commentMarks = state.doc.rangeHasMark(from, to, markType);
128
+ if (commentMarks) {
129
+ return;
130
+ }
131
+ // Otherwise, add a new comment mark with empty body at the current selection
132
+ var attrs = {
133
+ id: createId(),
134
+ createdBy: _this.options.createdBy,
135
+ value: "",
136
+ createdAt: new Date().toISOString(),
137
+ };
138
+ tr.addMark(from, to, markType.create(attrs));
139
+ dispatch(tr);
140
+ return true;
141
+ };
142
+ },
143
+ updateComment: function (_a) {
144
+ var id = _a.id, value = _a.value;
145
+ return function (_a) {
146
+ var state = _a.state, tr = _a.tr, dispatch = _a.dispatch;
147
+ var markType = state.schema.marks.comment;
148
+ if (!markType)
149
+ return false;
150
+ var ranges = locateCommentRanges(state.doc, id);
151
+ if (ranges.length === 0)
152
+ return false;
153
+ if (!dispatch)
154
+ return true;
155
+ var existing = state.doc
156
+ .resolve(ranges[0].from + 1)
157
+ .marks()
158
+ .find(function (m) { return m.type === markType && m.attrs.id === id; });
159
+ if (!existing)
160
+ return false;
161
+ var next = markType.create(__assign(__assign({}, existing.attrs), { value: value }));
162
+ for (var _i = 0, _b = __spreadArray([], ranges, true).sort(function (a, b) { return b.from - a.from; }); _i < _b.length; _i++) {
163
+ var r = _b[_i];
164
+ tr.removeMark(r.from, r.to, markType);
165
+ tr.addMark(r.from, r.to, next);
166
+ }
167
+ dispatch(tr);
168
+ return true;
169
+ };
170
+ },
171
+ removeComment: function (_a) {
172
+ var id = _a.id;
173
+ return function (_a) {
174
+ var state = _a.state, tr = _a.tr, dispatch = _a.dispatch;
175
+ var markType = state.schema.marks.comment;
176
+ if (!markType)
177
+ return false;
178
+ var ranges = locateCommentRanges(state.doc, id);
179
+ if (ranges.length === 0)
180
+ return false;
181
+ if (!dispatch)
182
+ return true;
183
+ for (var _i = 0, _b = __spreadArray([], ranges, true).sort(function (a, b) { return b.from - a.from; }); _i < _b.length; _i++) {
184
+ var r = _b[_i];
185
+ tr.removeMark(r.from, r.to, markType);
186
+ }
187
+ var replies = __spreadArray([], findReplies(state.doc, id), true).sort(function (a, b) { return b.pos - a.pos; });
188
+ for (var _c = 0, replies_1 = replies; _c < replies_1.length; _c++) {
189
+ var r = replies_1[_c];
190
+ tr.delete(r.pos, r.pos + r.nodeSize);
191
+ }
192
+ tr.setMeta(SKIP_RESCUE_META, true);
193
+ dispatch(tr);
194
+ return true;
195
+ };
196
+ },
197
+ };
198
+ },
199
+ });