@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
@@ -0,0 +1,19 @@
1
+ import type { Editor } from "@tiptap/core";
2
+ import type { Node as PMNode } from "@tiptap/pm/model";
3
+ import { CommentInfo, CommentReplyAttrs } from "./types";
4
+ export declare function createId(): string;
5
+ export declare function findComments(source: Editor | PMNode): CommentInfo[];
6
+ export interface CommentReplyLocation {
7
+ attrs: CommentReplyAttrs;
8
+ pos: number;
9
+ nodeSize: number;
10
+ }
11
+ export declare function findReplies(source: Editor | PMNode, commentId: string): CommentReplyLocation[];
12
+ export declare function locateReplyById(doc: PMNode, id: string): {
13
+ pos: number;
14
+ nodeSize: number;
15
+ } | null;
16
+ export declare function locateCommentRanges(doc: PMNode, id: string): {
17
+ from: number;
18
+ to: number;
19
+ }[];
@@ -0,0 +1,95 @@
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 { nanoid } from "nanoid";
13
+ export function createId() {
14
+ return "cm_".concat(nanoid());
15
+ }
16
+ export function findComments(source) {
17
+ var doc = "state" in source ? source.state.doc : source;
18
+ var rangesById = new Map();
19
+ doc.descendants(function (node, pos) {
20
+ var _a;
21
+ var mark = node.marks.find(function (m) { return m.type.name === "comment"; });
22
+ if (!mark)
23
+ return;
24
+ var attrs = mark.attrs;
25
+ var text = node.isText ? ((_a = node.text) !== null && _a !== void 0 ? _a : "") : "";
26
+ var existing = rangesById.get(attrs.id);
27
+ if (existing) {
28
+ existing.to = Math.max(existing.to, pos + node.nodeSize);
29
+ existing.text += text;
30
+ return;
31
+ }
32
+ rangesById.set(attrs.id, {
33
+ id: attrs.id,
34
+ from: pos,
35
+ to: pos + node.nodeSize,
36
+ text: text,
37
+ attrs: __assign({}, attrs),
38
+ });
39
+ });
40
+ return Array.from(rangesById.values()).map(function (range) { return (__assign(__assign({}, range.attrs), { from: range.from, to: range.to, text: range.text })); });
41
+ }
42
+ export function findReplies(source, commentId) {
43
+ var doc = "state" in source ? source.state.doc : source;
44
+ var replies = [];
45
+ doc.descendants(function (node, pos) {
46
+ if (node.type.name !== "inline-comment-reply" ||
47
+ node.attrs.commentId !== commentId) {
48
+ return;
49
+ }
50
+ replies.push({
51
+ attrs: node.attrs,
52
+ pos: pos,
53
+ nodeSize: node.nodeSize,
54
+ });
55
+ });
56
+ replies.sort(function (a, b) {
57
+ var _a, _b;
58
+ return new Date(((_a = a.attrs) === null || _a === void 0 ? void 0 : _a.createdAt) || 0).getTime() -
59
+ new Date(((_b = b.attrs) === null || _b === void 0 ? void 0 : _b.createdAt) || 0).getTime();
60
+ });
61
+ return replies;
62
+ }
63
+ export function locateReplyById(doc, id) {
64
+ var found = null;
65
+ doc.descendants(function (node, pos) {
66
+ if (found)
67
+ return false;
68
+ if (node.type.name === "inline-comment-reply" && node.attrs.id === id) {
69
+ found = { pos: pos, nodeSize: node.nodeSize };
70
+ return false;
71
+ }
72
+ return undefined;
73
+ });
74
+ return found;
75
+ }
76
+ export function locateCommentRanges(doc, id) {
77
+ var ranges = [];
78
+ doc.descendants(function (node, pos) {
79
+ var mark = node.marks.find(function (m) { return m.type.name === "comment" && m.attrs.id === id; });
80
+ if (!mark)
81
+ return;
82
+ ranges.push({ from: pos, to: pos + node.nodeSize });
83
+ });
84
+ ranges.sort(function (a, b) { return a.from - b.from; });
85
+ var merged = [];
86
+ for (var _i = 0, ranges_1 = ranges; _i < ranges_1.length; _i++) {
87
+ var r = ranges_1[_i];
88
+ var last = merged[merged.length - 1];
89
+ if (last && last.to === r.from)
90
+ last.to = r.to;
91
+ else
92
+ merged.push(__assign({}, r));
93
+ }
94
+ return merged;
95
+ }
@@ -0,0 +1,29 @@
1
+ export type CommentAuthor = {
2
+ id: string;
3
+ name: string;
4
+ color?: string;
5
+ };
6
+ export interface CommentAttrs {
7
+ id: string;
8
+ createdBy?: CommentAuthor;
9
+ createdAt: string;
10
+ value: string;
11
+ }
12
+ export interface CommentInfo extends CommentAttrs {
13
+ from: number;
14
+ to: number;
15
+ text: string;
16
+ }
17
+ export interface CommentOptions {
18
+ createdBy?: CommentAuthor;
19
+ }
20
+ export interface CommentReplyAttrs {
21
+ id: string;
22
+ commentId: string;
23
+ createdBy?: CommentAuthor;
24
+ createdAt: string;
25
+ value: string;
26
+ }
27
+ export interface CommentReplyOptions {
28
+ createdBy?: CommentAuthor;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -14,6 +14,8 @@ import Link from "@tiptap/extension-link";
14
14
  import { TableKit } from "@tiptap/extension-table";
15
15
  import Typography from "@tiptap/extension-typography";
16
16
  import StarterKit from "@tiptap/starter-kit";
17
+ import { CommentReply } from "./comments/comment-reply.js";
18
+ import { Comment } from "./comments/comment.js";
17
19
  import ContentReference from "./content-reference.js";
18
20
  import FileAttachment from "./file-attachment.js";
19
21
  import { Mathematics } from "./mathematics.js";
@@ -39,5 +41,7 @@ export var defaultRichTextExtensions = [
39
41
  ];
40
42
  export var htmlRenderingRichTextExtensions = __spreadArray([
41
43
  Document,
42
- Image
44
+ Image,
45
+ Comment,
46
+ CommentReply
43
47
  ], defaultRichTextExtensions, true);
@@ -1,4 +1,7 @@
1
1
  import { Editor } from "@tiptap/core";
2
- export declare const RichTextFormattingMenu: ({ editor }: {
2
+ type RichTextFormattingMenuProps = {
3
3
  editor: Editor;
4
- }) => import("react/jsx-runtime").JSX.Element;
4
+ allowComments?: boolean;
5
+ };
6
+ export declare const RichTextFormattingMenu: ({ allowComments, editor, }: RichTextFormattingMenuProps) => import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -1,14 +1,16 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useState } from "react";
3
- import { Trans } from "react-i18next";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { useTranslation } from "react-i18next";
4
4
  import { useEditorState } from "@tiptap/react";
5
5
  import { BubbleMenu } from "@tiptap/react/menus";
6
- import { BoldIcon, CropIcon, HighlighterIcon, ItalicIcon, LinkIcon, ListIcon, SquareFunctionIcon, TextQuoteIcon, TrashIcon, } from "lucide-react";
6
+ import { BoldIcon, CropIcon, HighlighterIcon, ItalicIcon, LinkIcon, ListIcon, MessageCircleIcon, SquareFunctionIcon, TextQuoteIcon, TrashIcon, } from "lucide-react";
7
7
  import { CropPageClippingModal } from "./crop-page-clipping-modal";
8
8
  import { cn } from "../../../utils";
9
+ import { FloatingMenu } from "../../ui/floating-menu";
9
10
  export var RichTextFormattingMenu = function (_a) {
10
- var editor = _a.editor;
11
+ var allowComments = _a.allowComments, editor = _a.editor;
11
12
  var _b = useState(null), pageClipping = _b[0], setPageClipping = _b[1];
13
+ var t = useTranslation().t;
12
14
  var setLink = useCallback(function () {
13
15
  if (editor.isActive("link")) {
14
16
  var mark_1 = editor.chain().focus().extendMarkRange("link");
@@ -49,41 +51,105 @@ export var RichTextFormattingMenu = function (_a) {
49
51
  isPageClipping: ctx.editor.isActive("page-clipping"),
50
52
  isFileAttachment: ctx.editor.isActive("file-attachment"),
51
53
  isImage: ctx.editor.isActive("image"),
54
+ isComment: allowComments ? ctx.editor.isActive("comment") : false,
52
55
  }); },
53
56
  });
57
+ var actions = useMemo(function () {
58
+ var items = [];
59
+ if (allowComments) {
60
+ items.push({
61
+ icon: MessageCircleIcon,
62
+ label: t("formatting.comment"),
63
+ active: state.isComment,
64
+ action: function () { return editor.chain().focus().toggleComment().run(); },
65
+ });
66
+ }
67
+ if (state.isPageClipping) {
68
+ items.push({
69
+ icon: CropIcon,
70
+ label: t("formatting.crop.edit"),
71
+ active: state.isPageClipping,
72
+ action: function () { return setPageClipping(editor.getAttributes("page-clipping")); },
73
+ });
74
+ }
75
+ else if (state.isImage || state.isFileAttachment) {
76
+ items.push({
77
+ icon: TrashIcon,
78
+ label: t("formatting.delete"),
79
+ active: state.isImage || state.isFileAttachment,
80
+ action: function () { return editor.commands.deleteSelection(); },
81
+ });
82
+ }
83
+ else {
84
+ items.push({
85
+ icon: BoldIcon,
86
+ label: t("formatting.bold"),
87
+ active: state.isBold,
88
+ action: function () {
89
+ return editor.chain().focus().toggleBold().run();
90
+ },
91
+ }, {
92
+ icon: ItalicIcon,
93
+ label: t("formatting.italic"),
94
+ active: state.isItalic,
95
+ action: function () {
96
+ return editor.chain().focus().toggleItalic().run();
97
+ },
98
+ }, {
99
+ icon: HighlighterIcon,
100
+ label: t("formatting.highlight"),
101
+ active: state.isHighlight,
102
+ action: function () {
103
+ return editor.chain().focus().toggleHighlight().run();
104
+ },
105
+ }, {
106
+ icon: LinkIcon,
107
+ label: t("formatting.link"),
108
+ active: state.isLink,
109
+ action: setLink,
110
+ }, {
111
+ icon: ListIcon,
112
+ label: t("formatting.bulletList"),
113
+ active: state.isBulletList,
114
+ action: function () {
115
+ return editor.chain().focus().toggleBulletList().run();
116
+ },
117
+ }, {
118
+ icon: ListIcon,
119
+ label: t("formatting.orderedList"),
120
+ active: state.isOrderedList,
121
+ action: function () {
122
+ return editor.chain().focus().toggleOrderedList().run();
123
+ },
124
+ }, {
125
+ icon: TextQuoteIcon,
126
+ label: t("formatting.blockquote"),
127
+ active: state.isBlockquote,
128
+ action: function () {
129
+ return editor.chain().focus().toggleBlockquote().run();
130
+ },
131
+ }, {
132
+ icon: SquareFunctionIcon,
133
+ label: t("formatting.inlineMath"),
134
+ active: state.isInlineMath,
135
+ action: function () {
136
+ return editor.chain().focus()
137
+ .insertInlineMath({ latex: "" })
138
+ .run();
139
+ },
140
+ });
141
+ }
142
+ return items;
143
+ }, [state, setLink, t, allowComments, editor]);
54
144
  if (!editor) {
55
145
  return null;
56
146
  }
57
- if (state.isPageClipping) {
58
- return (_jsxs(_Fragment, { children: [_jsx(CropPageClippingModal, { pageClipping: pageClipping, onClose: function () { return setPageClipping(null); }, onUpdate: function (newParameters) {
59
- setPageClipping(null);
60
- editor
61
- .chain()
62
- .focus()
63
- .updateAttributes("page-clipping", newParameters)
64
- .run();
65
- } }), _jsx(BubbleMenu, { editor: editor, children: _jsx("div", { className: "border p-0.5 gap-0.5 bg-white rounded-md flex items-center shadow-md text-xs font-medium select-none", children: _jsxs("span", { onClick: function () {
66
- return setPageClipping(editor.getAttributes("page-clipping"));
67
- }, className: "p-1 cursor-pointer flex items-center gap-1 pr-2 hover:bg-zinc-100 rounded-base", children: [_jsx(CropIcon, { className: "size-3.5 m-0.5" }), _jsx(Trans, { children: "formatting.crop.crop" })] }) }) })] }));
68
- }
69
- if (state.isImage || state.isFileAttachment) {
70
- return (_jsx(BubbleMenu, { editor: editor, children: _jsx("div", { className: "border p-0.5 gap-0.5 bg-white rounded-md flex items-center shadow-md text-xs font-medium select-none", children: _jsxs("span", { onClick: function () { return editor.commands.deleteSelection(); }, className: "p-1 cursor-pointer flex items-center gap-1 pr-2 hover:bg-zinc-100 rounded-base", children: [_jsx(TrashIcon, { className: "size-3.5 m-0.5" }), _jsx(Trans, { children: "formatting.delete" })] }) }) }));
71
- }
72
- return (_jsx(BubbleMenu, { editor: editor, children: _jsxs("div", { className: "border p-0.5 gap-0.5 bg-white rounded-md flex items-center shadow-md text-xs font-medium select-none", children: [_jsx("span", { onClick: function () {
73
- return editor.chain().focus().toggleBold().run();
74
- }, className: cn("p-1 cursor-pointer", state.isBold ? "bg-zinc-200 rounded-base" : ""), children: _jsx(BoldIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isBold ? 2.75 : 2 }) }), _jsx("span", { onClick: function () {
75
- return editor.chain().focus().toggleItalic().run();
76
- }, className: cn("p-1 cursor-pointer", state.isItalic ? "bg-zinc-200 rounded-base" : ""), children: _jsx(ItalicIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isItalic ? 2.75 : 2 }) }), _jsx("span", { onClick: function () {
77
- return editor.chain().focus().toggleHighlight().run();
78
- }, className: cn("p-1 cursor-pointer", state.isHighlight ? "bg-zinc-200 rounded-base" : ""), children: _jsx(HighlighterIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isHighlight ? 2.75 : 2 }) }), _jsx("span", { onClick: setLink, className: cn("p-1 cursor-pointer", state.isLink ? "bg-zinc-200 rounded-base" : ""), children: _jsx(LinkIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isLink ? 2.75 : 2 }) }), _jsx("span", { onClick: function () {
79
- return editor.chain().focus().toggleBulletList().run();
80
- }, className: cn("p-1 cursor-pointer", state.isBulletList ? "bg-zinc-200 rounded-base" : ""), children: _jsx(ListIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isBulletList ? 2.75 : 2 }) }), _jsx("span", { onClick: function () {
81
- return editor.chain().focus().toggleOrderedList().run();
82
- }, className: cn("p-1 cursor-pointer", state.isOrderedList ? "bg-zinc-200 rounded-base" : ""), children: _jsx(ListIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isOrderedList ? 2.75 : 2 }) }), _jsx("span", { onClick: function () {
83
- return editor.chain().focus().toggleBlockquote().run();
84
- }, className: cn("p-1 cursor-pointer", state.isBlockquote ? "bg-zinc-200 rounded-base" : ""), children: _jsx(TextQuoteIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isBlockquote ? 2.75 : 2 }) }), _jsx("span", { onClick: function () {
85
- return editor.chain().focus()
86
- .insertInlineMath({ latex: "" })
87
- .run();
88
- }, className: cn("p-1 cursor-pointer", state.isInlineMath ? "bg-zinc-200 rounded-base" : ""), children: _jsx(SquareFunctionIcon, { className: "size-3.5 m-0.5", strokeWidth: state.isInlineMath ? 2.75 : 2 }) })] }) }));
147
+ return (_jsxs(_Fragment, { children: [state.isPageClipping && (_jsx(CropPageClippingModal, { pageClipping: pageClipping, onClose: function () { return setPageClipping(null); }, onUpdate: function (newParameters) {
148
+ setPageClipping(null);
149
+ editor
150
+ .chain()
151
+ .focus()
152
+ .updateAttributes("page-clipping", newParameters)
153
+ .run();
154
+ } })), _jsx(BubbleMenu, { editor: editor, children: _jsx(FloatingMenu, { className: "ml-2 flex items-center gap-0.5", children: actions.map(function (item, index) { return (_jsx("button", { onClick: item.action, type: "button", title: item.label, "aria-label": item.label, className: cn("p-1 cursor-pointer flex items-center gap-1 hover:bg-zinc-100 rounded-md", item.active ? "bg-zinc-200" : ""), children: _jsx(item.icon, { className: "size-3.5 m-0.5" }) }, index)); }) }) })] }));
89
155
  };
@@ -0,0 +1,27 @@
1
+ export type Comment = {
2
+ id?: string;
3
+ inReplyTo?: string;
4
+ value?: string;
5
+ createdAt?: string;
6
+ createdBy?: {
7
+ id: string;
8
+ name: string;
9
+ color?: string;
10
+ };
11
+ range?: [number, number];
12
+ quotation?: string;
13
+ };
14
+ type CommentItemProps = {
15
+ comment: Comment;
16
+ commentAuthor?: Comment["createdBy"];
17
+ onSave?: (value: string) => Promise<void> | void;
18
+ onResolve?: () => Promise<void> | void;
19
+ onReply?: () => void;
20
+ onDelete?: () => void;
21
+ onCancel?: () => void;
22
+ onEditingChange?: (id: string | undefined, editing: boolean) => void;
23
+ isTopLevel?: boolean;
24
+ className?: string;
25
+ };
26
+ export declare const CommentItem: ({ comment, onSave, onResolve, onReply, onDelete, onCancel, commentAuthor, onEditingChange, isTopLevel, className, }: CommentItemProps) => import("react/jsx-runtime").JSX.Element;
27
+ export {};
@@ -0,0 +1,142 @@
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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
23
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
49
+ import { useEffect, useMemo, useState } from "react";
50
+ import { Trans, useTranslation } from "react-i18next";
51
+ import { formatDistanceToNow } from "date-fns";
52
+ import { CheckIcon, MessageCircleIcon, MoreHorizontalIcon, PencilLineIcon, TrashIcon, } from "lucide-react";
53
+ import { Avatar, getUserColor } from "./avatar";
54
+ import { Button } from "./button";
55
+ import { Popover, PopoverContent, PopoverItem, PopoverTrigger, } from "./popover";
56
+ import { cn } from "../../utils";
57
+ import { MinimalRichTextField } from "../rich-text/minimal-rich-text-field";
58
+ import { RichTextDisplay } from "../rich-text/rich-text-display";
59
+ export var CommentItem = function (_a) {
60
+ var _b, _c;
61
+ var comment = _a.comment, onSave = _a.onSave, onResolve = _a.onResolve, onReply = _a.onReply, onDelete = _a.onDelete, onCancel = _a.onCancel, commentAuthor = _a.commentAuthor, onEditingChange = _a.onEditingChange, _d = _a.isTopLevel, isTopLevel = _d === void 0 ? true : _d, className = _a.className;
62
+ var t = useTranslation().t;
63
+ var _e = useState(comment.value || ""), draft = _e[0], setDraft = _e[1];
64
+ var _f = useState(false), focused = _f[0], setFocused = _f[1];
65
+ var _g = useState(false), loading = _g[0], setLoading = _g[1];
66
+ var _h = useState(!comment.value && ((_b = comment.createdBy) === null || _b === void 0 ? void 0 : _b.id) === (commentAuthor === null || commentAuthor === void 0 ? void 0 : commentAuthor.id) && !!onSave), editing = _h[0], setEditing = _h[1];
67
+ useEffect(function () {
68
+ if (onEditingChange) {
69
+ onEditingChange(comment.id, editing);
70
+ return function () { return onEditingChange(comment.id, false); };
71
+ }
72
+ }, [editing, comment.id, onEditingChange]);
73
+ var quoteColor = useMemo(function () {
74
+ var _a, _b;
75
+ return ((_a = comment.createdBy) === null || _a === void 0 ? void 0 : _a.color) ||
76
+ (comment.createdBy && getUserColor((_b = comment.createdBy) === null || _b === void 0 ? void 0 : _b.id));
77
+ }, [comment.createdBy]);
78
+ return (_jsxs("div", { className: cn("flex gap-3 items-start", className), children: [_jsx(Avatar, { entity: __assign({ type: "user" }, (comment.createdBy || {})), size: 24 }), _jsxs("div", { className: "flex-1 flex flex-col gap-1", children: [_jsxs("div", { children: [_jsx("div", { className: cn("leading-[1] font-medium font-heading", !comment.createdAt && "py-1.25"), children: ((_c = comment.createdBy) === null || _c === void 0 ? void 0 : _c.name) || "Unknown User" }), comment.createdAt && (_jsx("div", { className: "text-xs font-normal text-zinc-500", children: formatDistanceToNow(new Date(comment.createdAt), {
79
+ addSuffix: true,
80
+ }) }))] }), comment.quotation && (_jsxs("blockquote", { className: "mt-1 mb-2 pl-3.5 py-0.5 relative text-sm italic text-zinc-800 line-clamp-2", onClick: function () {
81
+ if (!comment.id)
82
+ return;
83
+ var highlight = document.querySelector("[data-comment=\"".concat(comment.id, "\"]"));
84
+ if (!highlight)
85
+ return;
86
+ highlight.scrollIntoView({
87
+ behavior: "smooth",
88
+ block: "center",
89
+ });
90
+ highlight.classList.add("highlighted");
91
+ setTimeout(function () { return highlight.classList.remove("highlighted"); }, 1200);
92
+ }, children: [comment.quotation, _jsx("span", { className: "absolute inset-0 right-auto w-0.75 rounded bg-zinc-600 opacity-50", style: { backgroundColor: quoteColor } })] })), editing ? (_jsxs(_Fragment, { children: [_jsx(MinimalRichTextField, { value: draft, "data-comment-input": comment.id, onChange: function (value) { return setDraft(value); }, onKeyDown: function (e) { return __awaiter(void 0, void 0, void 0, function () {
93
+ return __generator(this, function (_a) {
94
+ switch (_a.label) {
95
+ case 0:
96
+ if (!(e.key === "Enter" && e.metaKey)) return [3 /*break*/, 4];
97
+ e.preventDefault();
98
+ setLoading(true);
99
+ if (!(draft.trim().length > 0)) return [3 /*break*/, 4];
100
+ _a.label = 1;
101
+ case 1:
102
+ _a.trys.push([1, , 3, 4]);
103
+ return [4 /*yield*/, onSave(draft)];
104
+ case 2:
105
+ _a.sent();
106
+ setEditing(false);
107
+ return [3 /*break*/, 4];
108
+ case 3:
109
+ setLoading(false);
110
+ return [7 /*endfinally*/];
111
+ case 4: return [2 /*return*/];
112
+ }
113
+ });
114
+ }); }, autoFocus: true, placeholder: comment.inReplyTo
115
+ ? t("formatting.comments.reply-placeholder")
116
+ : t("formatting.comments.comment-placeholder"), disabled: loading, className: cn("border border-border focus-within:border-border-accent", "px-3 py-2 text-sm font-normal rounded-3xl", "focus-within:rounded-xl focus-within:min-h-20 transition-all", focused && "rounded-xl min-h-20"), onFocus: function () { return setFocused(true); } }), _jsxs("div", { className: "flex items-center justify-end gap-2 mt-2", children: [onCancel && (_jsx(Button, { type: "button", variant: "secondary", size: "sm", disabled: loading, onClick: function () {
117
+ onCancel();
118
+ setDraft(comment.value || "");
119
+ setEditing(false);
120
+ }, children: _jsx(Trans, { children: "formatting.comments.cancel" }) })), _jsx(Button, { type: "button", onClick: function () { return __awaiter(void 0, void 0, void 0, function () {
121
+ return __generator(this, function (_a) {
122
+ switch (_a.label) {
123
+ case 0:
124
+ setLoading(true);
125
+ _a.label = 1;
126
+ case 1:
127
+ _a.trys.push([1, , 3, 4]);
128
+ return [4 /*yield*/, onSave(draft)];
129
+ case 2:
130
+ _a.sent();
131
+ setEditing(false);
132
+ return [3 /*break*/, 4];
133
+ case 3:
134
+ setLoading(false);
135
+ return [7 /*endfinally*/];
136
+ case 4: return [2 /*return*/];
137
+ }
138
+ });
139
+ }); }, disabled: !draft.trim() || loading, variant: "primary", size: "sm", title: "Save comment", "aria-label": "Save comment", children: _jsx(Trans, { children: "formatting.comments.save-comment" }) })] })] })) : (_jsx(RichTextDisplay, { className: "text-sm font-normal", children: comment.value || "" }))] }), !editing && (_jsxs("div", { children: [isTopLevel && onReply && (_jsx("button", { type: "button", onClick: function () { return onReply(); }, title: "Reply", "aria-label": "Reply", className: "p-1.5 cursor-pointer hover:bg-zinc-100 rounded-base", children: _jsx(MessageCircleIcon, { className: "size-3.5" }) })), isTopLevel && onResolve && (_jsx("button", { type: "button", onClick: function () { return onResolve(); }, title: "Resolve comment", "aria-label": "Resolve comment", className: "p-1.5 cursor-pointer hover:bg-zinc-100 rounded-base", children: _jsx(CheckIcon, { className: "size-3.5" }) })), (onDelete || onSave) && (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", title: "Comment options", "aria-label": "Comment options", className: "p-1.5 cursor-pointer hover:bg-zinc-100 rounded-base", children: _jsx(MoreHorizontalIcon, { className: "size-3.5" }) }) }), _jsxs(PopoverContent, { className: "p-0 gap-0 overflow-hidden min-w-32", children: [!!onSave && (_jsxs(PopoverItem, { onClick: function () {
140
+ setEditing(true);
141
+ }, className: "text-xs border-0", children: [_jsx(PencilLineIcon, { className: "size-3.5! mr-1" }), _jsx(Trans, { children: "formatting.comments.edit-comment" })] })), !!onDelete && (_jsxs(PopoverItem, { onClick: onDelete, className: "text-xs border-0", children: [_jsx(TrashIcon, { className: "size-3.5! mr-1" }), _jsx(Trans, { children: "formatting.comments.delete-comment" })] }))] })] }))] }))] }));
142
+ };
@@ -0,0 +1,6 @@
1
+ type FloatingMenuProps = {
2
+ children: React.ReactNode;
3
+ className?: string;
4
+ } & React.HTMLAttributes<HTMLDivElement>;
5
+ export declare const FloatingMenu: ({ children, className, ...props }: FloatingMenuProps) => import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,28 @@
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 __rest = (this && this.__rest) || function (s, e) {
13
+ var t = {};
14
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
15
+ t[p] = s[p];
16
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
17
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
18
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
19
+ t[p[i]] = s[p[i]];
20
+ }
21
+ return t;
22
+ };
23
+ import { jsx as _jsx } from "react/jsx-runtime";
24
+ import { cn } from "../../utils";
25
+ export var FloatingMenu = function (_a) {
26
+ var children = _a.children, _b = _a.className, className = _b === void 0 ? "" : _b, props = __rest(_a, ["children", "className"]);
27
+ return (_jsx("div", __assign({ className: cn("border border-zinc-600 p-0.5 bg-white rounded-lg shadow-md relative z-10 text-xs font-medium select-none", className) }, props, { children: children })));
28
+ };
@@ -28,3 +28,6 @@ export * from "./skeleton";
28
28
  export * from "./tabs";
29
29
  export * from "./context-menu";
30
30
  export * from "./avatar";
31
+ export * from "./text-selection-menu";
32
+ export * from "./floating-menu";
33
+ export * from "./comments";
@@ -28,3 +28,6 @@ export * from "./skeleton";
28
28
  export * from "./tabs";
29
29
  export * from "./context-menu";
30
30
  export * from "./avatar";
31
+ export * from "./text-selection-menu";
32
+ export * from "./floating-menu";
33
+ export * from "./comments";
@@ -0,0 +1,17 @@
1
+ import { Placement } from "@floating-ui/react";
2
+ type TextSelectionContainerProps = {
3
+ children: React.ReactNode;
4
+ enabled: boolean;
5
+ };
6
+ type TextSelectionMenuProps = {
7
+ children: (selection: Selection, setSelection: React.Dispatch<React.SetStateAction<Selection | null>>) => React.ReactNode;
8
+ placement?: Placement;
9
+ };
10
+ type Selection = {
11
+ text: string;
12
+ range: Range;
13
+ rect: DOMRect;
14
+ };
15
+ export declare const TextSelectionContainer: ({ children, enabled, }: TextSelectionContainerProps) => import("react/jsx-runtime").JSX.Element;
16
+ export declare const TextSelectionMenu: ({ children, placement, }: TextSelectionMenuProps) => import("react/jsx-runtime").JSX.Element;
17
+ export {};
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useEffect, useRef, useState, } from "react";
3
+ import { offset, useFloating } from "@floating-ui/react";
4
+ var TextSelectionContext = createContext(null);
5
+ export var TextSelectionContainer = function (_a) {
6
+ var children = _a.children, _b = _a.enabled, enabled = _b === void 0 ? true : _b;
7
+ var containerRef = useRef(null);
8
+ var _c = useState(null), selection = _c[0], setSelection = _c[1];
9
+ var handleSelection = useCallback(function () {
10
+ if (!enabled)
11
+ return;
12
+ var sel = window.getSelection();
13
+ // No selection or collapsed (just a cursor)
14
+ if (!sel || sel.isCollapsed || sel.rangeCount === 0) {
15
+ setSelection(null);
16
+ return;
17
+ }
18
+ var range = sel.getRangeAt(0);
19
+ // Only react to selections inside our editor
20
+ if (containerRef.current &&
21
+ !containerRef.current.contains(range.commonAncestorContainer)) {
22
+ setSelection(null);
23
+ return;
24
+ }
25
+ var rect = range.getBoundingClientRect();
26
+ if (rect.width === 0 && rect.height === 0) {
27
+ setSelection(null);
28
+ return;
29
+ }
30
+ setSelection({ text: sel.toString(), range: range, rect: rect });
31
+ }, [containerRef, enabled]);
32
+ useEffect(function () {
33
+ if (!enabled) {
34
+ // eslint-disable-next-line react-hooks/set-state-in-effect
35
+ setSelection(null);
36
+ return;
37
+ }
38
+ document.addEventListener("selectionchange", handleSelection);
39
+ return function () {
40
+ return document.removeEventListener("selectionchange", handleSelection);
41
+ };
42
+ }, [handleSelection, enabled]);
43
+ return (_jsx(TextSelectionContext.Provider, { value: { selection: selection, setSelection: setSelection }, children: _jsx("div", { ref: containerRef, children: children }) }));
44
+ };
45
+ export var TextSelectionMenu = function (_a) {
46
+ var children = _a.children, _b = _a.placement, placement = _b === void 0 ? "top" : _b;
47
+ var _c = useContext(TextSelectionContext), selection = _c.selection, setSelection = _c.setSelection;
48
+ var _d = useFloating({
49
+ placement: placement,
50
+ strategy: "absolute",
51
+ middleware: [offset(8)],
52
+ elements: {
53
+ // sadly mis-typed in the library
54
+ reference: selection === null || selection === void 0 ? void 0 : selection.range,
55
+ },
56
+ }), refs = _d.refs, floatingStyles = _d.floatingStyles;
57
+ if (!selection)
58
+ return null;
59
+ return (
60
+ // eslint-disable-next-line react-hooks/refs
61
+ _jsx("div", { ref: refs.setFloating, style: floatingStyles, className: "z-10", children: children(selection, setSelection) }));
62
+ };