@liveblocks/react-ui 2.7.1 → 2.8.0-beta1
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.
- package/dist/_private/index.d.mts +5 -3
- package/dist/_private/index.d.ts +5 -3
- package/dist/_private/index.js +4 -2
- package/dist/_private/index.js.map +1 -1
- package/dist/_private/index.mjs +2 -1
- package/dist/_private/index.mjs.map +1 -1
- package/dist/components/Comment.js +106 -3
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/Comment.mjs +107 -5
- package/dist/components/Comment.mjs.map +1 -1
- package/dist/components/Composer.js +216 -103
- package/dist/components/Composer.js.map +1 -1
- package/dist/components/Composer.mjs +220 -107
- package/dist/components/Composer.mjs.map +1 -1
- package/dist/components/HistoryVersionSummary.js +5 -0
- package/dist/components/HistoryVersionSummary.js.map +1 -1
- package/dist/components/HistoryVersionSummary.mjs +5 -0
- package/dist/components/HistoryVersionSummary.mjs.map +1 -1
- package/dist/components/InboxNotification.js +20 -4
- package/dist/components/InboxNotification.js.map +1 -1
- package/dist/components/InboxNotification.mjs +20 -4
- package/dist/components/InboxNotification.mjs.map +1 -1
- package/dist/components/Thread.js +5 -0
- package/dist/components/Thread.js.map +1 -1
- package/dist/components/Thread.mjs +5 -0
- package/dist/components/Thread.mjs.map +1 -1
- package/dist/components/internal/Attachment.js +313 -0
- package/dist/components/internal/Attachment.js.map +1 -0
- package/dist/components/internal/Attachment.mjs +309 -0
- package/dist/components/internal/Attachment.mjs.map +1 -0
- package/dist/components/internal/InboxNotificationThread.js +12 -2
- package/dist/components/internal/InboxNotificationThread.js.map +1 -1
- package/dist/components/internal/InboxNotificationThread.mjs +13 -3
- package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
- package/dist/icons/Attachment.js +15 -0
- package/dist/icons/Attachment.js.map +1 -0
- package/dist/icons/Attachment.mjs +13 -0
- package/dist/icons/Attachment.mjs.map +1 -0
- package/dist/icons/Spinner.js +3 -9
- package/dist/icons/Spinner.js.map +1 -1
- package/dist/icons/Spinner.mjs +4 -10
- package/dist/icons/Spinner.mjs.map +1 -1
- package/dist/icons/{Missing.js → Warning.js} +3 -3
- package/dist/icons/{Missing.js.map → Warning.js.map} +1 -1
- package/dist/icons/{Missing.mjs → Warning.mjs} +3 -3
- package/dist/icons/{Missing.mjs.map → Warning.mjs.map} +1 -1
- package/dist/index.d.mts +73 -4
- package/dist/index.d.ts +73 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/overrides.js +5 -0
- package/dist/overrides.js.map +1 -1
- package/dist/overrides.mjs +5 -0
- package/dist/overrides.mjs.map +1 -1
- package/dist/primitives/Composer/contexts.js +17 -3
- package/dist/primitives/Composer/contexts.js.map +1 -1
- package/dist/primitives/Composer/contexts.mjs +15 -4
- package/dist/primitives/Composer/contexts.mjs.map +1 -1
- package/dist/primitives/Composer/index.js +206 -30
- package/dist/primitives/Composer/index.js.map +1 -1
- package/dist/primitives/Composer/index.mjs +209 -35
- package/dist/primitives/Composer/index.mjs.map +1 -1
- package/dist/primitives/Composer/utils.js +231 -0
- package/dist/primitives/Composer/utils.js.map +1 -1
- package/dist/primitives/Composer/utils.mjs +229 -1
- package/dist/primitives/Composer/utils.mjs.map +1 -1
- package/dist/primitives/EmojiPicker/utils.js +2 -2
- package/dist/primitives/EmojiPicker/utils.js.map +1 -1
- package/dist/primitives/EmojiPicker/utils.mjs +1 -1
- package/dist/primitives/EmojiPicker/utils.mjs.map +1 -1
- package/dist/primitives/FileSize.js +33 -0
- package/dist/primitives/FileSize.js.map +1 -0
- package/dist/primitives/FileSize.mjs +31 -0
- package/dist/primitives/FileSize.mjs.map +1 -0
- package/dist/primitives/index.d.mts +87 -3
- package/dist/primitives/index.d.ts +87 -3
- package/dist/primitives/index.js +4 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/primitives/index.mjs +2 -0
- package/dist/primitives/index.mjs.map +1 -1
- package/dist/slate/plugins/{paste-html.js → paste.js} +16 -5
- package/dist/slate/plugins/paste.js.map +1 -0
- package/dist/slate/plugins/{paste-html.mjs → paste.mjs} +16 -5
- package/dist/slate/plugins/paste.mjs.map +1 -0
- package/dist/utils/data-transfer.js +20 -0
- package/dist/utils/data-transfer.js.map +1 -0
- package/dist/utils/data-transfer.mjs +18 -0
- package/dist/utils/data-transfer.mjs.map +1 -0
- package/dist/utils/download.js +14 -0
- package/dist/utils/download.js.map +1 -0
- package/dist/utils/download.mjs +12 -0
- package/dist/utils/download.mjs.map +1 -0
- package/dist/utils/format-file-size.js +45 -0
- package/dist/utils/format-file-size.js.map +1 -0
- package/dist/utils/format-file-size.mjs +43 -0
- package/dist/utils/format-file-size.mjs.map +1 -0
- package/dist/utils/intl.js +6 -0
- package/dist/utils/intl.js.map +1 -1
- package/dist/utils/intl.mjs +6 -1
- package/dist/utils/intl.mjs.map +1 -1
- package/dist/utils/use-initial.js +2 -1
- package/dist/utils/use-initial.js.map +1 -1
- package/dist/utils/use-initial.mjs +3 -2
- package/dist/utils/use-initial.mjs.map +1 -1
- package/dist/utils/use-latest.js.map +1 -1
- package/dist/utils/use-latest.mjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/package.json +4 -4
- package/src/styles/dark/index.css +1 -0
- package/src/styles/index.css +343 -62
- package/src/styles/utils.css +44 -0
- package/styles/dark/attributes.css +1 -1
- package/styles/dark/attributes.css.map +1 -1
- package/styles/dark/media-query.css +1 -1
- package/styles/dark/media-query.css.map +1 -1
- package/styles.css +1 -1
- package/styles.css.map +1 -1
- package/dist/slate/plugins/paste-html.js.map +0 -1
- package/dist/slate/plugins/paste-html.mjs.map +0 -1
- package/dist/utils/chunk.js +0 -12
- package/dist/utils/chunk.js.map +0 -1
- package/dist/utils/chunk.mjs +0 -10
- package/dist/utils/chunk.mjs.map +0 -1
|
@@ -39,6 +39,8 @@ declare function ArrowDownIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
|
39
39
|
|
|
40
40
|
declare function ArrowUpIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
41
41
|
|
|
42
|
+
declare function AttachmentIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
43
|
+
|
|
42
44
|
declare function CheckIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
43
45
|
|
|
44
46
|
declare function CrossIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
@@ -55,8 +57,6 @@ declare function EmojiAddIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
|
55
57
|
|
|
56
58
|
declare function MentionIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
57
59
|
|
|
58
|
-
declare function MissingIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
59
|
-
|
|
60
60
|
declare function ResolveIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
61
61
|
|
|
62
62
|
declare function ResolvedIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
@@ -69,4 +69,6 @@ declare function SendIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
|
69
69
|
|
|
70
70
|
declare function SpinnerIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
declare function WarningIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
73
|
+
|
|
74
|
+
export { ArrowDownIcon, ArrowUpIcon, AttachmentIcon, Button, CheckIcon, CrossIcon, DeleteIcon, EditIcon, EllipsisIcon, EmojiAddIcon, EmojiIcon, List, MentionIcon, ResolveIcon, ResolvedIcon, RestoreIcon, SearchIcon, SendIcon, SpinnerIcon, User, WarningIcon };
|
package/dist/_private/index.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ declare function ArrowDownIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
|
39
39
|
|
|
40
40
|
declare function ArrowUpIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
41
41
|
|
|
42
|
+
declare function AttachmentIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
43
|
+
|
|
42
44
|
declare function CheckIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
43
45
|
|
|
44
46
|
declare function CrossIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
@@ -55,8 +57,6 @@ declare function EmojiAddIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
|
55
57
|
|
|
56
58
|
declare function MentionIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
57
59
|
|
|
58
|
-
declare function MissingIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
59
|
-
|
|
60
60
|
declare function ResolveIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
61
61
|
|
|
62
62
|
declare function ResolvedIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
@@ -69,4 +69,6 @@ declare function SendIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
|
69
69
|
|
|
70
70
|
declare function SpinnerIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
declare function WarningIcon(props: ComponentProps<"svg">): React.JSX.Element;
|
|
73
|
+
|
|
74
|
+
export { ArrowDownIcon, ArrowUpIcon, AttachmentIcon, Button, CheckIcon, CrossIcon, DeleteIcon, EditIcon, EllipsisIcon, EmojiAddIcon, EmojiIcon, List, MentionIcon, ResolveIcon, ResolvedIcon, RestoreIcon, SearchIcon, SendIcon, SpinnerIcon, User, WarningIcon };
|
package/dist/_private/index.js
CHANGED
|
@@ -5,6 +5,7 @@ var List = require('../components/internal/List.js');
|
|
|
5
5
|
var User = require('../components/internal/User.js');
|
|
6
6
|
var ArrowDown = require('../icons/ArrowDown.js');
|
|
7
7
|
var ArrowUp = require('../icons/ArrowUp.js');
|
|
8
|
+
var Attachment = require('../icons/Attachment.js');
|
|
8
9
|
var Check = require('../icons/Check.js');
|
|
9
10
|
var Cross = require('../icons/Cross.js');
|
|
10
11
|
var Delete = require('../icons/Delete.js');
|
|
@@ -13,13 +14,13 @@ var Ellipsis = require('../icons/Ellipsis.js');
|
|
|
13
14
|
var Emoji = require('../icons/Emoji.js');
|
|
14
15
|
var EmojiAdd = require('../icons/EmojiAdd.js');
|
|
15
16
|
var Mention = require('../icons/Mention.js');
|
|
16
|
-
var Missing = require('../icons/Missing.js');
|
|
17
17
|
var Resolve = require('../icons/Resolve.js');
|
|
18
18
|
var Resolved = require('../icons/Resolved.js');
|
|
19
19
|
var Restore = require('../icons/Restore.js');
|
|
20
20
|
var Search = require('../icons/Search.js');
|
|
21
21
|
var Send = require('../icons/Send.js');
|
|
22
22
|
var Spinner = require('../icons/Spinner.js');
|
|
23
|
+
var Warning = require('../icons/Warning.js');
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
|
|
@@ -28,6 +29,7 @@ exports.List = List.List;
|
|
|
28
29
|
exports.User = User.User;
|
|
29
30
|
exports.ArrowDownIcon = ArrowDown.ArrowDownIcon;
|
|
30
31
|
exports.ArrowUpIcon = ArrowUp.ArrowUpIcon;
|
|
32
|
+
exports.AttachmentIcon = Attachment.AttachmentIcon;
|
|
31
33
|
exports.CheckIcon = Check.CheckIcon;
|
|
32
34
|
exports.CrossIcon = Cross.CrossIcon;
|
|
33
35
|
exports.DeleteIcon = Delete.DeleteIcon;
|
|
@@ -36,11 +38,11 @@ exports.EllipsisIcon = Ellipsis.EllipsisIcon;
|
|
|
36
38
|
exports.EmojiIcon = Emoji.EmojiIcon;
|
|
37
39
|
exports.EmojiAddIcon = EmojiAdd.EmojiAddIcon;
|
|
38
40
|
exports.MentionIcon = Mention.MentionIcon;
|
|
39
|
-
exports.MissingIcon = Missing.MissingIcon;
|
|
40
41
|
exports.ResolveIcon = Resolve.ResolveIcon;
|
|
41
42
|
exports.ResolvedIcon = Resolved.ResolvedIcon;
|
|
42
43
|
exports.RestoreIcon = Restore.RestoreIcon;
|
|
43
44
|
exports.SearchIcon = Search.SearchIcon;
|
|
44
45
|
exports.SendIcon = Send.SendIcon;
|
|
45
46
|
exports.SpinnerIcon = Spinner.SpinnerIcon;
|
|
47
|
+
exports.WarningIcon = Warning.WarningIcon;
|
|
46
48
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/_private/index.mjs
CHANGED
|
@@ -3,6 +3,7 @@ export { List } from '../components/internal/List.mjs';
|
|
|
3
3
|
export { User } from '../components/internal/User.mjs';
|
|
4
4
|
export { ArrowDownIcon } from '../icons/ArrowDown.mjs';
|
|
5
5
|
export { ArrowUpIcon } from '../icons/ArrowUp.mjs';
|
|
6
|
+
export { AttachmentIcon } from '../icons/Attachment.mjs';
|
|
6
7
|
export { CheckIcon } from '../icons/Check.mjs';
|
|
7
8
|
export { CrossIcon } from '../icons/Cross.mjs';
|
|
8
9
|
export { DeleteIcon } from '../icons/Delete.mjs';
|
|
@@ -11,11 +12,11 @@ export { EllipsisIcon } from '../icons/Ellipsis.mjs';
|
|
|
11
12
|
export { EmojiIcon } from '../icons/Emoji.mjs';
|
|
12
13
|
export { EmojiAddIcon } from '../icons/EmojiAdd.mjs';
|
|
13
14
|
export { MentionIcon } from '../icons/Mention.mjs';
|
|
14
|
-
export { MissingIcon } from '../icons/Missing.mjs';
|
|
15
15
|
export { ResolveIcon } from '../icons/Resolve.mjs';
|
|
16
16
|
export { ResolvedIcon } from '../icons/Resolved.mjs';
|
|
17
17
|
export { RestoreIcon } from '../icons/Restore.mjs';
|
|
18
18
|
export { SearchIcon } from '../icons/Search.mjs';
|
|
19
19
|
export { SendIcon } from '../icons/Send.mjs';
|
|
20
20
|
export { SpinnerIcon } from '../icons/Spinner.mjs';
|
|
21
|
+
export { WarningIcon } from '../icons/Warning.mjs';
|
|
21
22
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -17,10 +17,12 @@ var Timestamp = require('../primitives/Timestamp.js');
|
|
|
17
17
|
var shared = require('../shared.js');
|
|
18
18
|
var mentions = require('../slate/plugins/mentions.js');
|
|
19
19
|
var classNames = require('../utils/class-names.js');
|
|
20
|
+
var download = require('../utils/download.js');
|
|
20
21
|
var useRefs = require('../utils/use-refs.js');
|
|
21
22
|
var useVisible = require('../utils/use-visible.js');
|
|
22
23
|
var useWindowFocus = require('../utils/use-window-focus.js');
|
|
23
24
|
var Composer = require('./Composer.js');
|
|
25
|
+
var Attachment = require('./internal/Attachment.js');
|
|
24
26
|
var Avatar = require('./internal/Avatar.js');
|
|
25
27
|
var Button = require('./internal/Button.js');
|
|
26
28
|
var Dropdown = require('./internal/Dropdown.js');
|
|
@@ -186,6 +188,82 @@ const CommentNonInteractiveReaction = React.forwardRef(({ reaction, overrides, .
|
|
|
186
188
|
ref: forwardedRef
|
|
187
189
|
});
|
|
188
190
|
});
|
|
191
|
+
function openAttachment({ attachment, url }) {
|
|
192
|
+
if (attachment.mimeType === "application/pdf" || attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/") || attachment.mimeType.startsWith("audio/")) {
|
|
193
|
+
window.open(url, "_blank");
|
|
194
|
+
} else {
|
|
195
|
+
download.download(url, attachment.name);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function CommentMediaAttachment({
|
|
199
|
+
attachment,
|
|
200
|
+
onAttachmentClick,
|
|
201
|
+
className,
|
|
202
|
+
overrides,
|
|
203
|
+
...props
|
|
204
|
+
}) {
|
|
205
|
+
const { url } = react.useAttachmentUrl(attachment.id);
|
|
206
|
+
const handleClick = React.useCallback(
|
|
207
|
+
(event) => {
|
|
208
|
+
if (!url) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const args = { attachment, url };
|
|
212
|
+
onAttachmentClick?.(args, event);
|
|
213
|
+
if (event.isDefaultPrevented()) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
openAttachment(args);
|
|
217
|
+
},
|
|
218
|
+
[attachment, onAttachmentClick, url]
|
|
219
|
+
);
|
|
220
|
+
return /* @__PURE__ */ React.createElement(Attachment.MediaAttachment, {
|
|
221
|
+
className: classNames.classNames("lb-comment-attachment", className),
|
|
222
|
+
...props,
|
|
223
|
+
attachment,
|
|
224
|
+
overrides,
|
|
225
|
+
onClick: url ? handleClick : void 0
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function CommentFileAttachment({
|
|
229
|
+
attachment,
|
|
230
|
+
onAttachmentClick,
|
|
231
|
+
className,
|
|
232
|
+
overrides,
|
|
233
|
+
...props
|
|
234
|
+
}) {
|
|
235
|
+
const { url } = react.useAttachmentUrl(attachment.id);
|
|
236
|
+
const handleClick = React.useCallback(
|
|
237
|
+
(event) => {
|
|
238
|
+
if (!url) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const args = { attachment, url };
|
|
242
|
+
onAttachmentClick?.(args, event);
|
|
243
|
+
if (event.isDefaultPrevented()) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
openAttachment(args);
|
|
247
|
+
},
|
|
248
|
+
[attachment, onAttachmentClick, url]
|
|
249
|
+
);
|
|
250
|
+
return /* @__PURE__ */ React.createElement(Attachment.FileAttachment, {
|
|
251
|
+
className: classNames.classNames("lb-comment-attachment", className),
|
|
252
|
+
...props,
|
|
253
|
+
attachment,
|
|
254
|
+
overrides,
|
|
255
|
+
onClick: url ? handleClick : void 0
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function CommentNonInteractiveFileAttachment({
|
|
259
|
+
className,
|
|
260
|
+
...props
|
|
261
|
+
}) {
|
|
262
|
+
return /* @__PURE__ */ React.createElement(Attachment.FileAttachment, {
|
|
263
|
+
className: classNames.classNames("lb-comment-attachment", className),
|
|
264
|
+
...props
|
|
265
|
+
});
|
|
266
|
+
}
|
|
189
267
|
function AutoMarkReadThreadIdHandler({
|
|
190
268
|
threadId,
|
|
191
269
|
commentRef
|
|
@@ -210,8 +288,10 @@ const Comment = React.forwardRef(
|
|
|
210
288
|
showDeleted,
|
|
211
289
|
showActions = "hover",
|
|
212
290
|
showReactions = true,
|
|
291
|
+
showAttachments = true,
|
|
213
292
|
onAuthorClick,
|
|
214
293
|
onMentionClick,
|
|
294
|
+
onAttachmentClick,
|
|
215
295
|
onCommentEdit,
|
|
216
296
|
onCommentDelete,
|
|
217
297
|
overrides: overrides$1,
|
|
@@ -234,6 +314,9 @@ const Comment = React.forwardRef(
|
|
|
234
314
|
const [isTarget, setTarget] = React.useState(false);
|
|
235
315
|
const [isMoreActionOpen, setMoreActionOpen] = React.useState(false);
|
|
236
316
|
const [isReactionActionOpen, setReactionActionOpen] = React.useState(false);
|
|
317
|
+
const { mediaAttachments, fileAttachments } = React.useMemo(() => {
|
|
318
|
+
return Attachment.separateMediaAttachments(comment.attachments);
|
|
319
|
+
}, [comment.attachments]);
|
|
237
320
|
const stopPropagation = React.useCallback((event) => {
|
|
238
321
|
event.stopPropagation();
|
|
239
322
|
}, []);
|
|
@@ -248,14 +331,15 @@ const Comment = React.forwardRef(
|
|
|
248
331
|
[]
|
|
249
332
|
);
|
|
250
333
|
const handleEditSubmit = React.useCallback(
|
|
251
|
-
({ body }, event) => {
|
|
334
|
+
({ body, attachments }, event) => {
|
|
252
335
|
onCommentEdit?.(comment);
|
|
253
336
|
event.preventDefault();
|
|
254
337
|
setEditing(false);
|
|
255
338
|
editComment({
|
|
256
339
|
commentId: comment.id,
|
|
257
340
|
threadId: comment.threadId,
|
|
258
|
-
body
|
|
341
|
+
body,
|
|
342
|
+
attachments
|
|
259
343
|
});
|
|
260
344
|
},
|
|
261
345
|
[comment, editComment, onCommentEdit]
|
|
@@ -406,8 +490,10 @@ const Comment = React.forwardRef(
|
|
|
406
490
|
className: "lb-comment-composer",
|
|
407
491
|
onComposerSubmit: handleEditSubmit,
|
|
408
492
|
defaultValue: comment.body,
|
|
493
|
+
defaultAttachments: comment.attachments,
|
|
409
494
|
autoFocus: true,
|
|
410
495
|
showAttribution: false,
|
|
496
|
+
showAttachments,
|
|
411
497
|
actions: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
|
|
412
498
|
content: $.COMMENT_EDIT_COMPOSER_CANCEL,
|
|
413
499
|
"aria-label": $.COMMENT_EDIT_COMPOSER_CANCEL
|
|
@@ -444,7 +530,23 @@ const Comment = React.forwardRef(
|
|
|
444
530
|
}),
|
|
445
531
|
Link: CommentLink
|
|
446
532
|
}
|
|
447
|
-
}),
|
|
533
|
+
}), showAttachments && (mediaAttachments.length > 0 || fileAttachments.length > 0) ? /* @__PURE__ */ React.createElement("div", {
|
|
534
|
+
className: "lb-comment-attachments"
|
|
535
|
+
}, mediaAttachments.length > 0 ? /* @__PURE__ */ React.createElement("div", {
|
|
536
|
+
className: "lb-attachments"
|
|
537
|
+
}, mediaAttachments.map((attachment) => /* @__PURE__ */ React.createElement(CommentMediaAttachment, {
|
|
538
|
+
key: attachment.id,
|
|
539
|
+
attachment,
|
|
540
|
+
overrides: overrides$1,
|
|
541
|
+
onAttachmentClick
|
|
542
|
+
}))) : null, fileAttachments.length > 0 ? /* @__PURE__ */ React.createElement("div", {
|
|
543
|
+
className: "lb-attachments"
|
|
544
|
+
}, fileAttachments.map((attachment) => /* @__PURE__ */ React.createElement(CommentFileAttachment, {
|
|
545
|
+
key: attachment.id,
|
|
546
|
+
attachment,
|
|
547
|
+
overrides: overrides$1,
|
|
548
|
+
onAttachmentClick
|
|
549
|
+
}))) : null) : null, showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React.createElement("div", {
|
|
448
550
|
className: "lb-comment-reactions"
|
|
449
551
|
}, comment.reactions.map((reaction) => /* @__PURE__ */ React.createElement(CommentReaction, {
|
|
450
552
|
key: reaction.emoji,
|
|
@@ -475,6 +577,7 @@ const Comment = React.forwardRef(
|
|
|
475
577
|
exports.Comment = Comment;
|
|
476
578
|
exports.CommentLink = CommentLink;
|
|
477
579
|
exports.CommentMention = CommentMention;
|
|
580
|
+
exports.CommentNonInteractiveFileAttachment = CommentNonInteractiveFileAttachment;
|
|
478
581
|
exports.CommentNonInteractiveLink = CommentNonInteractiveLink;
|
|
479
582
|
exports.CommentNonInteractiveReaction = CommentNonInteractiveReaction;
|
|
480
583
|
exports.CommentReaction = CommentReaction;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Comment.js","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n CommentData,\n CommentReaction as CommentReactionData,\n} from \"@liveblocks/core\";\nimport {\n RoomContext,\n useAddReaction,\n useDeleteComment,\n useEditComment,\n useMarkThreadAsRead,\n useRemoveReaction,\n} from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiAddIcon } from \"../icons/EmojiAdd\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport { classNames } from \"../utils/class-names\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useVisibleCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Composer } from \"./Composer\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { List } from \"./internal/List\";\nimport {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The comment to display.\n */\n comment: CommentData;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n additionalActions?: ReactNode;\n\n /**\n * @internal\n */\n additionalActionsClassName?: string;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\nexport function CommentMention({\n userId,\n className,\n ...props\n}: CommentBodyMentionProps & CommentMentionProps) {\n const currentId = useCurrentUserId();\n return (\n <CommentPrimitive.Mention\n className={classNames(\"lb-comment-mention\", className)}\n data-self={userId === currentId ? \"\" : undefined}\n {...props}\n >\n {MENTION_CHARACTER}\n <User userId={userId} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <CommentPrimitive.Link\n className={classNames(\"lb-comment-link\", className)}\n href={href}\n {...props}\n >\n {children}\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={classNames(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <Button\n className={classNames(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </Button>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\n// A void component (which doesn't render anything) responsible for marking a thread\n// as read when the comment it's used in becomes visible.\n// Moving this logic into a separate component allows us to use the visibility\n// and focus hooks \"conditionally\" by conditionally rendering this component.\nfunction AutoMarkReadThreadIdHandler({\n threadId,\n commentRef,\n}: {\n threadId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkThreadAsRead();\n const isWindowFocused = useWindowFocus();\n\n useVisibleCallback(\n commentRef,\n () => {\n markThreadAsRead(threadId);\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return null;\n}\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n onAuthorClick,\n onMentionClick,\n onCommentEdit,\n onCommentDelete,\n overrides,\n className,\n additionalActions,\n additionalActionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const isInRoom = Boolean(useContext(RoomContext));\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteComment();\n const editComment = useEditComment();\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n ({ body }: ComposerSubmitComment, event: FormEvent<HTMLFormElement>) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n event.preventDefault();\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex].users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n return (\n <TooltipProvider>\n {isInRoom && autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n />\n )}\n <div\n id={comment.id}\n className={classNames(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div\n className={classNames(\n \"lb-comment-actions\",\n additionalActionsClassName\n )}\n >\n {additionalActions ?? null}\n {showReactions && (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n )}\n {comment.userId === currentUserId && (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n <DropdownItem\n onSelect={handleEdit}\n onClick={stopPropagation}\n >\n <EditIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_EDIT}\n </DropdownItem>\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n >\n <DeleteIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_DELETE}\n </DropdownItem>\n </>\n }\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n >\n <EllipsisIcon className=\"lb-button-icon\" />\n </Button>\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n )}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">\n {isEditing ? (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n autoFocus\n showAttribution={false}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n >\n <CrossIcon className=\"lb-button-icon\" />\n </Button>\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n >\n <CheckIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ userId }) => (\n <CommentMention\n userId={userId}\n onClick={(event) => onMentionClick?.(userId, event)}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n />\n ))}\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n </TooltipProvider>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA;AAmFO;AAAwB;AAC7B;AACA;AAEF;AACE;AACA;AACG;AACsD;AACd;AACnC;AAGH;AAAK;AAGZ;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AACG;AACmD;AAClD;AACI;AAKV;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AACG;AAAuD;AAAO;AAInE;AAEA;AAIE;AACA;AACG;AACuD;AAC9C;AACM;AACH;AACM;AACjB;AACI;AACC;AAEJ;AAAgB;AAA4C;AAC5D;AAAe;AAGtB;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACA;AAEI;AAAgB;AAAkB;AAAe;AACnD;AACkB;AACT;AACA;AACZ;AACS;AACM;AAEnB;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACG;AACU;AACA;AACC;AAET;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEJ;AAC4B;AAC3B;AACA;AACI;AAKd;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACG;AACc;AACc;AAC3B;AACA;AACI;AACC;AAGX;AAMA;AAAqC;AACnC;AAEF;AAIE;AACA;AAEA;AAAA;AACE;AAEE;AAAyB;AAC3B;AACA;AAEW;AACX;AAGF;AACF;AAYO;AAAgB;AAEnB;AACE;AACgB;AAChB;AACc;AACE;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAGrB;AAEA;AACA;AACA;AAAY;AACS;AACD;AAClB;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAGyC;AACf;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AAGO;AACa;AACF;AAGb;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEJ;AAAc;AACZ;AAAc;AACZ;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACI;AACJ;AAKP;AAAe;AASvB;AACY;AACT;AACA;AACF;AAIG;AACgB;AACD;AAEb;AAAmB;AACjB;AAA0B;AACxB;AACW;AACD;AACK;AAEb;AAAuB;AAO/B;AACO;AACQ;AACR;AAGD;AACW;AACD;AAER;AAAmB;AAGrB;AACW;AACD;AAER;AAAqB;AAG1B;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACS;AACV;AACK;AAEb;AAAuB;AASvC;AAAc;AAEV;AACW;AACQ;AACI;AACb;AACQ;AAGZ;AACY;AACG;AAEb;AACW;AACD;AAER;AAAoB;AAGxB;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACS;AACE;AACD;AACK;AAEb;AAAoB;AAI7B;AAES;AACe;AAC1B;AAIC;AACW;AACI;AACF;AAEP;AACC;AACkD;AACpD;AAEI;AACR;AAGC;AAAc;AAEV;AACe;AACd;AACA;AACA;AAGH;AAA2B;AACzB;AAAmB;AACjB;AAA0B;AACxB;AACW;AACF;AACC;AACK;AAEb;AAAuB;AASrC;AAAc;AACZ;AAAY;AAKvB;AAGN;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"Comment.js","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n CommentAttachment,\n CommentData,\n CommentReaction as CommentReactionData,\n} from \"@liveblocks/core\";\nimport {\n RoomContext,\n useAddReaction,\n useAttachmentUrl,\n useDeleteComment,\n useEditComment,\n useMarkThreadAsRead,\n useRemoveReaction,\n} from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport React, {\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiAddIcon } from \"../icons/EmojiAdd\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport { MENTION_CHARACTER } from \"../slate/plugins/mentions\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { download } from \"../utils/download\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useVisibleCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { List } from \"./internal/List\";\nimport {\n ShortcutTooltip,\n ShortcutTooltipKey,\n Tooltip,\n TooltipProvider,\n} from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The comment to display.\n */\n comment: CommentData;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: (\n args: CommentAttachmentArgs,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n additionalActions?: ReactNode;\n\n /**\n * @internal\n */\n additionalActionsClassName?: string;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\nexport function CommentMention({\n userId,\n className,\n ...props\n}: CommentBodyMentionProps & CommentMentionProps) {\n const currentId = useCurrentUserId();\n return (\n <CommentPrimitive.Mention\n className={classNames(\"lb-comment-mention\", className)}\n data-self={userId === currentId ? \"\" : undefined}\n {...props}\n >\n {MENTION_CHARACTER}\n <User userId={userId} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <CommentPrimitive.Link\n className={classNames(\"lb-comment-link\", className)}\n href={href}\n {...props}\n >\n {children}\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={classNames(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <Button\n className={classNames(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </Button>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps) {\n const { url } = useAttachmentUrl(attachment.id);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={classNames(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps) {\n const { url } = useAttachmentUrl(attachment.id);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={classNames(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={classNames(\"lb-comment-attachment\", className)}\n {...props}\n />\n );\n}\n\n// A void component (which doesn't render anything) responsible for marking a thread\n// as read when the comment it's used in becomes visible.\n// Moving this logic into a separate component allows us to use the visibility\n// and focus hooks \"conditionally\" by conditionally rendering this component.\nfunction AutoMarkReadThreadIdHandler({\n threadId,\n commentRef,\n}: {\n threadId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkThreadAsRead();\n const isWindowFocused = useWindowFocus();\n\n useVisibleCallback(\n commentRef,\n () => {\n markThreadAsRead(threadId);\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return null;\n}\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n overrides,\n className,\n additionalActions,\n additionalActionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const isInRoom = Boolean(useContext(RoomContext));\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteComment();\n const editComment = useEditComment();\n const addReaction = useAddReaction();\n const removeReaction = useRemoveReaction();\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n (\n { body, attachments }: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n event.preventDefault();\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n attachments,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex].users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n return (\n <TooltipProvider>\n {isInRoom && autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n />\n )}\n <div\n id={comment.id}\n className={classNames(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div\n className={classNames(\n \"lb-comment-actions\",\n additionalActionsClassName\n )}\n >\n {additionalActions ?? null}\n {showReactions && (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n )}\n {comment.userId === currentUserId && (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n <DropdownItem\n onSelect={handleEdit}\n onClick={stopPropagation}\n >\n <EditIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_EDIT}\n </DropdownItem>\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n >\n <DeleteIcon className=\"lb-dropdown-item-icon\" />\n {$.COMMENT_DELETE}\n </DropdownItem>\n </>\n }\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n >\n <EllipsisIcon className=\"lb-button-icon\" />\n </Button>\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n )}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">\n {isEditing ? (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n defaultAttachments={comment.attachments}\n autoFocus\n showAttribution={false}\n showAttachments={showAttachments}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n >\n <CrossIcon className=\"lb-button-icon\" />\n </Button>\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut={<ShortcutTooltipKey name=\"enter\" />}\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n >\n <CheckIcon className=\"lb-button-icon\" />\n </Button>\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ userId }) => (\n <CommentMention\n userId={userId}\n onClick={(event) => onMentionClick?.(userId, event)}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 || fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n />\n ))}\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n >\n <EmojiAddIcon className=\"lb-button-icon\" />\n </Button>\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n </TooltipProvider>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA;AAqGO;AAAwB;AAC7B;AACA;AAEF;AACE;AACA;AACG;AACsD;AACd;AACnC;AAGH;AAAK;AAGZ;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AACG;AACmD;AAClD;AACI;AAKV;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AACG;AAAuD;AAAO;AAInE;AAEA;AAIE;AACA;AACG;AACuD;AAC9C;AACM;AACH;AACM;AACjB;AACI;AACC;AAEJ;AAAgB;AAA4C;AAC5D;AAAe;AAGtB;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACA;AAEI;AAAgB;AAAkB;AAAe;AACnD;AACkB;AACT;AACA;AACZ;AACS;AACM;AAEnB;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACG;AACU;AACA;AACC;AAET;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEJ;AAC4B;AAC3B;AACA;AACI;AAKd;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACG;AACc;AACc;AAC3B;AACA;AACI;AACC;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AAEF;AACE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACyD;AACpD;AACJ;AACA;AAC6B;AAGnC;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AAEF;AACE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACyD;AACpD;AACJ;AACA;AAC6B;AAGnC;AAEO;AAA6C;AAClD;AAEF;AACE;AACG;AACyD;AACpD;AAGV;AAMA;AAAqC;AACnC;AAEF;AAIE;AACA;AAEA;AAAA;AACE;AAEE;AAAyB;AAC3B;AACA;AAEW;AACX;AAGF;AACF;AAYO;AAAgB;AAEnB;AACE;AACgB;AAChB;AACc;AACE;AACE;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACA;AACA;AAAY;AACS;AACD;AAClB;AACA;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAGyC;AACf;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AAGO;AACa;AACF;AAGb;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEJ;AAAc;AACZ;AAAc;AACZ;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACM;AACP;AAEV;AAAe;AACb;AACW;AACI;AACJ;AAKP;AAAe;AASvB;AACY;AACT;AACA;AACF;AAIG;AACgB;AACD;AAEb;AAAmB;AACjB;AAA0B;AACxB;AACW;AACD;AACK;AAEb;AAAuB;AAO/B;AACO;AACQ;AACR;AAGD;AACW;AACD;AAER;AAAmB;AAGrB;AACW;AACD;AAER;AAAqB;AAG1B;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACS;AACV;AACK;AAEb;AAAuB;AASvC;AAAc;AAEV;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AAGK;AACY;AACG;AAEb;AACW;AACD;AAER;AAAoB;AAGxB;AACY;AACA;AAAwB;AAAQ;AAE1C;AAAgC;AAC9B;AACS;AACE;AACD;AACK;AAEb;AAAoB;AAI7B;AAES;AACe;AAC1B;AAIC;AACW;AACI;AACF;AAEP;AACC;AACkD;AACpD;AAEI;AACR;AAIC;AAAc;AAEV;AAAc;AAEV;AACiB;AAChB;AACA;AACA;AAML;AAAc;AAEV;AACiB;AAChB;AACA;AACA;AAQT;AAAc;AAEV;AACe;AACd;AACA;AACA;AAGH;AAA2B;AACzB;AAAmB;AACjB;AAA0B;AACxB;AACW;AACF;AACC;AACK;AAEb;AAAuB;AASrC;AAAc;AACZ;AAAY;AAKvB;AAGN;;;;;;;;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useAddReaction, useRemoveReaction, RoomContext, useDeleteComment, useEditComment, useMarkThreadAsRead } from '@liveblocks/react';
|
|
2
|
+
import { useAddReaction, useRemoveReaction, RoomContext, useDeleteComment, useEditComment, useAttachmentUrl, useMarkThreadAsRead } from '@liveblocks/react';
|
|
3
3
|
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
|
4
4
|
import React__default, { forwardRef, useMemo, useCallback, useContext, useRef, useState, useEffect } from 'react';
|
|
5
5
|
import { CheckIcon } from '../icons/Check.mjs';
|
|
@@ -15,10 +15,12 @@ import { Timestamp } from '../primitives/Timestamp.mjs';
|
|
|
15
15
|
import { useCurrentUserId } from '../shared.mjs';
|
|
16
16
|
import { MENTION_CHARACTER } from '../slate/plugins/mentions.mjs';
|
|
17
17
|
import { classNames } from '../utils/class-names.mjs';
|
|
18
|
+
import { download } from '../utils/download.mjs';
|
|
18
19
|
import { useRefs } from '../utils/use-refs.mjs';
|
|
19
20
|
import { useVisibleCallback } from '../utils/use-visible.mjs';
|
|
20
21
|
import { useWindowFocus } from '../utils/use-window-focus.mjs';
|
|
21
22
|
import { Composer } from './Composer.mjs';
|
|
23
|
+
import { separateMediaAttachments, MediaAttachment, FileAttachment } from './internal/Attachment.mjs';
|
|
22
24
|
import { Avatar } from './internal/Avatar.mjs';
|
|
23
25
|
import { Button } from './internal/Button.mjs';
|
|
24
26
|
import { Dropdown, DropdownItem } from './internal/Dropdown.mjs';
|
|
@@ -165,6 +167,82 @@ const CommentNonInteractiveReaction = forwardRef(({ reaction, overrides, ...prop
|
|
|
165
167
|
ref: forwardedRef
|
|
166
168
|
});
|
|
167
169
|
});
|
|
170
|
+
function openAttachment({ attachment, url }) {
|
|
171
|
+
if (attachment.mimeType === "application/pdf" || attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/") || attachment.mimeType.startsWith("audio/")) {
|
|
172
|
+
window.open(url, "_blank");
|
|
173
|
+
} else {
|
|
174
|
+
download(url, attachment.name);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function CommentMediaAttachment({
|
|
178
|
+
attachment,
|
|
179
|
+
onAttachmentClick,
|
|
180
|
+
className,
|
|
181
|
+
overrides,
|
|
182
|
+
...props
|
|
183
|
+
}) {
|
|
184
|
+
const { url } = useAttachmentUrl(attachment.id);
|
|
185
|
+
const handleClick = useCallback(
|
|
186
|
+
(event) => {
|
|
187
|
+
if (!url) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const args = { attachment, url };
|
|
191
|
+
onAttachmentClick?.(args, event);
|
|
192
|
+
if (event.isDefaultPrevented()) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
openAttachment(args);
|
|
196
|
+
},
|
|
197
|
+
[attachment, onAttachmentClick, url]
|
|
198
|
+
);
|
|
199
|
+
return /* @__PURE__ */ React__default.createElement(MediaAttachment, {
|
|
200
|
+
className: classNames("lb-comment-attachment", className),
|
|
201
|
+
...props,
|
|
202
|
+
attachment,
|
|
203
|
+
overrides,
|
|
204
|
+
onClick: url ? handleClick : void 0
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function CommentFileAttachment({
|
|
208
|
+
attachment,
|
|
209
|
+
onAttachmentClick,
|
|
210
|
+
className,
|
|
211
|
+
overrides,
|
|
212
|
+
...props
|
|
213
|
+
}) {
|
|
214
|
+
const { url } = useAttachmentUrl(attachment.id);
|
|
215
|
+
const handleClick = useCallback(
|
|
216
|
+
(event) => {
|
|
217
|
+
if (!url) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const args = { attachment, url };
|
|
221
|
+
onAttachmentClick?.(args, event);
|
|
222
|
+
if (event.isDefaultPrevented()) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
openAttachment(args);
|
|
226
|
+
},
|
|
227
|
+
[attachment, onAttachmentClick, url]
|
|
228
|
+
);
|
|
229
|
+
return /* @__PURE__ */ React__default.createElement(FileAttachment, {
|
|
230
|
+
className: classNames("lb-comment-attachment", className),
|
|
231
|
+
...props,
|
|
232
|
+
attachment,
|
|
233
|
+
overrides,
|
|
234
|
+
onClick: url ? handleClick : void 0
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function CommentNonInteractiveFileAttachment({
|
|
238
|
+
className,
|
|
239
|
+
...props
|
|
240
|
+
}) {
|
|
241
|
+
return /* @__PURE__ */ React__default.createElement(FileAttachment, {
|
|
242
|
+
className: classNames("lb-comment-attachment", className),
|
|
243
|
+
...props
|
|
244
|
+
});
|
|
245
|
+
}
|
|
168
246
|
function AutoMarkReadThreadIdHandler({
|
|
169
247
|
threadId,
|
|
170
248
|
commentRef
|
|
@@ -189,8 +267,10 @@ const Comment = forwardRef(
|
|
|
189
267
|
showDeleted,
|
|
190
268
|
showActions = "hover",
|
|
191
269
|
showReactions = true,
|
|
270
|
+
showAttachments = true,
|
|
192
271
|
onAuthorClick,
|
|
193
272
|
onMentionClick,
|
|
273
|
+
onAttachmentClick,
|
|
194
274
|
onCommentEdit,
|
|
195
275
|
onCommentDelete,
|
|
196
276
|
overrides,
|
|
@@ -213,6 +293,9 @@ const Comment = forwardRef(
|
|
|
213
293
|
const [isTarget, setTarget] = useState(false);
|
|
214
294
|
const [isMoreActionOpen, setMoreActionOpen] = useState(false);
|
|
215
295
|
const [isReactionActionOpen, setReactionActionOpen] = useState(false);
|
|
296
|
+
const { mediaAttachments, fileAttachments } = useMemo(() => {
|
|
297
|
+
return separateMediaAttachments(comment.attachments);
|
|
298
|
+
}, [comment.attachments]);
|
|
216
299
|
const stopPropagation = useCallback((event) => {
|
|
217
300
|
event.stopPropagation();
|
|
218
301
|
}, []);
|
|
@@ -227,14 +310,15 @@ const Comment = forwardRef(
|
|
|
227
310
|
[]
|
|
228
311
|
);
|
|
229
312
|
const handleEditSubmit = useCallback(
|
|
230
|
-
({ body }, event) => {
|
|
313
|
+
({ body, attachments }, event) => {
|
|
231
314
|
onCommentEdit?.(comment);
|
|
232
315
|
event.preventDefault();
|
|
233
316
|
setEditing(false);
|
|
234
317
|
editComment({
|
|
235
318
|
commentId: comment.id,
|
|
236
319
|
threadId: comment.threadId,
|
|
237
|
-
body
|
|
320
|
+
body,
|
|
321
|
+
attachments
|
|
238
322
|
});
|
|
239
323
|
},
|
|
240
324
|
[comment, editComment, onCommentEdit]
|
|
@@ -385,8 +469,10 @@ const Comment = forwardRef(
|
|
|
385
469
|
className: "lb-comment-composer",
|
|
386
470
|
onComposerSubmit: handleEditSubmit,
|
|
387
471
|
defaultValue: comment.body,
|
|
472
|
+
defaultAttachments: comment.attachments,
|
|
388
473
|
autoFocus: true,
|
|
389
474
|
showAttribution: false,
|
|
475
|
+
showAttachments,
|
|
390
476
|
actions: /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(Tooltip, {
|
|
391
477
|
content: $.COMMENT_EDIT_COMPOSER_CANCEL,
|
|
392
478
|
"aria-label": $.COMMENT_EDIT_COMPOSER_CANCEL
|
|
@@ -423,7 +509,23 @@ const Comment = forwardRef(
|
|
|
423
509
|
}),
|
|
424
510
|
Link: CommentLink
|
|
425
511
|
}
|
|
426
|
-
}),
|
|
512
|
+
}), showAttachments && (mediaAttachments.length > 0 || fileAttachments.length > 0) ? /* @__PURE__ */ React__default.createElement("div", {
|
|
513
|
+
className: "lb-comment-attachments"
|
|
514
|
+
}, mediaAttachments.length > 0 ? /* @__PURE__ */ React__default.createElement("div", {
|
|
515
|
+
className: "lb-attachments"
|
|
516
|
+
}, mediaAttachments.map((attachment) => /* @__PURE__ */ React__default.createElement(CommentMediaAttachment, {
|
|
517
|
+
key: attachment.id,
|
|
518
|
+
attachment,
|
|
519
|
+
overrides,
|
|
520
|
+
onAttachmentClick
|
|
521
|
+
}))) : null, fileAttachments.length > 0 ? /* @__PURE__ */ React__default.createElement("div", {
|
|
522
|
+
className: "lb-attachments"
|
|
523
|
+
}, fileAttachments.map((attachment) => /* @__PURE__ */ React__default.createElement(CommentFileAttachment, {
|
|
524
|
+
key: attachment.id,
|
|
525
|
+
attachment,
|
|
526
|
+
overrides,
|
|
527
|
+
onAttachmentClick
|
|
528
|
+
}))) : null) : null, showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React__default.createElement("div", {
|
|
427
529
|
className: "lb-comment-reactions"
|
|
428
530
|
}, comment.reactions.map((reaction) => /* @__PURE__ */ React__default.createElement(CommentReaction, {
|
|
429
531
|
key: reaction.emoji,
|
|
@@ -451,5 +553,5 @@ const Comment = forwardRef(
|
|
|
451
553
|
}
|
|
452
554
|
);
|
|
453
555
|
|
|
454
|
-
export { Comment, CommentLink, CommentMention, CommentNonInteractiveLink, CommentNonInteractiveReaction, CommentReaction };
|
|
556
|
+
export { Comment, CommentLink, CommentMention, CommentNonInteractiveFileAttachment, CommentNonInteractiveLink, CommentNonInteractiveReaction, CommentReaction };
|
|
455
557
|
//# sourceMappingURL=Comment.mjs.map
|