@liveblocks/react-ui 2.5.2 → 2.7.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.
Files changed (104) hide show
  1. package/dist/components/Comment.js +61 -3
  2. package/dist/components/Comment.js.map +1 -1
  3. package/dist/components/Comment.mjs +62 -5
  4. package/dist/components/Comment.mjs.map +1 -1
  5. package/dist/components/Composer.js +217 -101
  6. package/dist/components/Composer.js.map +1 -1
  7. package/dist/components/Composer.mjs +220 -104
  8. package/dist/components/Composer.mjs.map +1 -1
  9. package/dist/components/InboxNotification.js +17 -4
  10. package/dist/components/InboxNotification.js.map +1 -1
  11. package/dist/components/InboxNotification.mjs +17 -4
  12. package/dist/components/InboxNotification.mjs.map +1 -1
  13. package/dist/components/Thread.js +2 -0
  14. package/dist/components/Thread.js.map +1 -1
  15. package/dist/components/Thread.mjs +2 -0
  16. package/dist/components/Thread.mjs.map +1 -1
  17. package/dist/components/internal/Attachment.js +226 -0
  18. package/dist/components/internal/Attachment.js.map +1 -0
  19. package/dist/components/internal/Attachment.mjs +224 -0
  20. package/dist/components/internal/Attachment.mjs.map +1 -0
  21. package/dist/components/internal/InboxNotificationThread.js +10 -2
  22. package/dist/components/internal/InboxNotificationThread.js.map +1 -1
  23. package/dist/components/internal/InboxNotificationThread.mjs +11 -3
  24. package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
  25. package/dist/icons/Attachment.js +15 -0
  26. package/dist/icons/Attachment.js.map +1 -0
  27. package/dist/icons/Attachment.mjs +13 -0
  28. package/dist/icons/Attachment.mjs.map +1 -0
  29. package/dist/icons/Spinner.js +3 -9
  30. package/dist/icons/Spinner.js.map +1 -1
  31. package/dist/icons/Spinner.mjs +4 -10
  32. package/dist/icons/Spinner.mjs.map +1 -1
  33. package/dist/icons/{Missing.js → Warning.js} +3 -3
  34. package/dist/icons/{Missing.js.map → Warning.js.map} +1 -1
  35. package/dist/icons/{Missing.mjs → Warning.mjs} +3 -3
  36. package/dist/icons/{Missing.mjs.map → Warning.mjs.map} +1 -1
  37. package/dist/index.d.mts +70 -4
  38. package/dist/index.d.ts +70 -4
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/overrides.js +5 -0
  42. package/dist/overrides.js.map +1 -1
  43. package/dist/overrides.mjs +5 -0
  44. package/dist/overrides.mjs.map +1 -1
  45. package/dist/primitives/Composer/contexts.js +17 -3
  46. package/dist/primitives/Composer/contexts.js.map +1 -1
  47. package/dist/primitives/Composer/contexts.mjs +15 -4
  48. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  49. package/dist/primitives/Composer/index.js +185 -26
  50. package/dist/primitives/Composer/index.js.map +1 -1
  51. package/dist/primitives/Composer/index.mjs +188 -31
  52. package/dist/primitives/Composer/index.mjs.map +1 -1
  53. package/dist/primitives/Composer/utils.js +224 -0
  54. package/dist/primitives/Composer/utils.js.map +1 -1
  55. package/dist/primitives/Composer/utils.mjs +222 -1
  56. package/dist/primitives/Composer/utils.mjs.map +1 -1
  57. package/dist/primitives/EmojiPicker/utils.js +2 -2
  58. package/dist/primitives/EmojiPicker/utils.js.map +1 -1
  59. package/dist/primitives/EmojiPicker/utils.mjs +1 -1
  60. package/dist/primitives/EmojiPicker/utils.mjs.map +1 -1
  61. package/dist/primitives/FileSize.js +33 -0
  62. package/dist/primitives/FileSize.js.map +1 -0
  63. package/dist/primitives/FileSize.mjs +31 -0
  64. package/dist/primitives/FileSize.mjs.map +1 -0
  65. package/dist/primitives/index.d.mts +83 -3
  66. package/dist/primitives/index.d.ts +83 -3
  67. package/dist/primitives/index.js +4 -0
  68. package/dist/primitives/index.js.map +1 -1
  69. package/dist/primitives/index.mjs +2 -0
  70. package/dist/primitives/index.mjs.map +1 -1
  71. package/dist/utils/download.js +14 -0
  72. package/dist/utils/download.js.map +1 -0
  73. package/dist/utils/download.mjs +12 -0
  74. package/dist/utils/download.mjs.map +1 -0
  75. package/dist/utils/format-file-size.js +45 -0
  76. package/dist/utils/format-file-size.js.map +1 -0
  77. package/dist/utils/format-file-size.mjs +43 -0
  78. package/dist/utils/format-file-size.mjs.map +1 -0
  79. package/dist/utils/intl.js +6 -0
  80. package/dist/utils/intl.js.map +1 -1
  81. package/dist/utils/intl.mjs +6 -1
  82. package/dist/utils/intl.mjs.map +1 -1
  83. package/dist/utils/use-initial.js +2 -1
  84. package/dist/utils/use-initial.js.map +1 -1
  85. package/dist/utils/use-initial.mjs +3 -2
  86. package/dist/utils/use-initial.mjs.map +1 -1
  87. package/dist/version.js +1 -1
  88. package/dist/version.js.map +1 -1
  89. package/dist/version.mjs +1 -1
  90. package/dist/version.mjs.map +1 -1
  91. package/package.json +4 -4
  92. package/src/styles/dark/index.css +1 -0
  93. package/src/styles/index.css +296 -62
  94. package/src/styles/utils.css +44 -0
  95. package/styles/dark/attributes.css +1 -1
  96. package/styles/dark/attributes.css.map +1 -1
  97. package/styles/dark/media-query.css +1 -1
  98. package/styles/dark/media-query.css.map +1 -1
  99. package/styles.css +1 -1
  100. package/styles.css.map +1 -1
  101. package/dist/utils/chunk.js +0 -12
  102. package/dist/utils/chunk.js.map +0 -1
  103. package/dist/utils/chunk.mjs +0 -10
  104. package/dist/utils/chunk.mjs.map +0 -1
@@ -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,49 @@ const CommentNonInteractiveReaction = React.forwardRef(({ reaction, overrides, .
186
188
  ref: forwardedRef
187
189
  });
188
190
  });
191
+ function CommentFileAttachment({
192
+ attachment,
193
+ onAttachmentClick,
194
+ className,
195
+ overrides,
196
+ ...props
197
+ }) {
198
+ const { url } = react.useAttachmentUrl(attachment.id);
199
+ const handleClick = React.useCallback(
200
+ (event) => {
201
+ if (!url) {
202
+ return;
203
+ }
204
+ const args = { attachment, url };
205
+ onAttachmentClick?.(args, event);
206
+ if (event.isDefaultPrevented()) {
207
+ return;
208
+ }
209
+ if (attachment.mimeType === "application/pdf" || attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/") || attachment.mimeType.startsWith("audio/")) {
210
+ window.open(url, "_blank");
211
+ } else {
212
+ download.download(url, attachment.name);
213
+ }
214
+ },
215
+ [attachment, onAttachmentClick, url]
216
+ );
217
+ return /* @__PURE__ */ React.createElement(Attachment.FileAttachment, {
218
+ className: classNames.classNames("lb-comment-attachment", className),
219
+ ...props,
220
+ attachment,
221
+ overrides,
222
+ onClick: url ? handleClick : void 0
223
+ });
224
+ }
225
+ function CommentNonInteractiveFileAttachment({
226
+ className,
227
+ ...props
228
+ }) {
229
+ return /* @__PURE__ */ React.createElement(Attachment.FileAttachment, {
230
+ className: classNames.classNames("lb-comment-attachment", className),
231
+ ...props
232
+ });
233
+ }
189
234
  function AutoMarkReadThreadIdHandler({
190
235
  threadId,
191
236
  commentRef
@@ -210,8 +255,10 @@ const Comment = React.forwardRef(
210
255
  showDeleted,
211
256
  showActions = "hover",
212
257
  showReactions = true,
258
+ showAttachments = true,
213
259
  onAuthorClick,
214
260
  onMentionClick,
261
+ onAttachmentClick,
215
262
  onCommentEdit,
216
263
  onCommentDelete,
217
264
  overrides: overrides$1,
@@ -248,14 +295,15 @@ const Comment = React.forwardRef(
248
295
  []
249
296
  );
250
297
  const handleEditSubmit = React.useCallback(
251
- ({ body }, event) => {
298
+ ({ body, attachments }, event) => {
252
299
  onCommentEdit?.(comment);
253
300
  event.preventDefault();
254
301
  setEditing(false);
255
302
  editComment({
256
303
  commentId: comment.id,
257
304
  threadId: comment.threadId,
258
- body
305
+ body,
306
+ attachments
259
307
  });
260
308
  },
261
309
  [comment, editComment, onCommentEdit]
@@ -406,8 +454,10 @@ const Comment = React.forwardRef(
406
454
  className: "lb-comment-composer",
407
455
  onComposerSubmit: handleEditSubmit,
408
456
  defaultValue: comment.body,
457
+ defaultAttachments: comment.attachments,
409
458
  autoFocus: true,
410
459
  showAttribution: false,
460
+ showAttachments,
411
461
  actions: /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
412
462
  content: $.COMMENT_EDIT_COMPOSER_CANCEL,
413
463
  "aria-label": $.COMMENT_EDIT_COMPOSER_CANCEL
@@ -444,7 +494,14 @@ const Comment = React.forwardRef(
444
494
  }),
445
495
  Link: CommentLink
446
496
  }
447
- }), showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React.createElement("div", {
497
+ }), showAttachments && comment.attachments.length > 0 ? /* @__PURE__ */ React.createElement("div", {
498
+ className: "lb-comment-attachments"
499
+ }, comment.attachments.map((attachment) => /* @__PURE__ */ React.createElement(CommentFileAttachment, {
500
+ key: attachment.id,
501
+ attachment,
502
+ overrides: overrides$1,
503
+ onAttachmentClick
504
+ }))) : null, showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React.createElement("div", {
448
505
  className: "lb-comment-reactions"
449
506
  }, comment.reactions.map((reaction) => /* @__PURE__ */ React.createElement(CommentReaction, {
450
507
  key: reaction.emoji,
@@ -475,6 +532,7 @@ const Comment = React.forwardRef(
475
532
  exports.Comment = Comment;
476
533
  exports.CommentLink = CommentLink;
477
534
  exports.CommentMention = CommentMention;
535
+ exports.CommentNonInteractiveFileAttachment = CommentNonInteractiveFileAttachment;
478
536
  exports.CommentNonInteractiveLink = CommentNonInteractiveLink;
479
537
  exports.CommentNonInteractiveReaction = CommentNonInteractiveReaction;
480
538
  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, index) => (\n <User\n key={users.id}\n userId={users.id}\n capitalize={index === 0}\n replaceSelf\n />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\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-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;AACY;AACG;AACQ;AACX;AAEd;AACkB;AACT;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 { FileAttachment } 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 CommentFileAttachmentProps\n 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, index) => (\n <User\n key={users.id}\n userId={users.id}\n capitalize={index === 0}\n replaceSelf\n />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\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 CommentFileAttachment({\n attachment,\n onAttachmentClick,\n className,\n overrides,\n ...props\n}: CommentFileAttachmentProps) {\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 // 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 [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}: CommentFileAttachmentProps) {\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\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-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 && comment.attachments.length > 0 ? (\n <div className=\"lb-comment-attachments\">\n {comment.attachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n />\n ))}\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFA;AAsGO;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;AACY;AACG;AACQ;AACX;AAEd;AACkB;AACT;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;AAA+B;AAC7B;AACA;AACA;AACA;AAEF;AACE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAKF;AAME;AAAyB;AAEzB;AAA6B;AAC/B;AACF;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;AAEA;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;AAGC;AAAc;AAEV;AACiB;AAChB;AACA;AACA;AAML;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 { 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,49 @@ const CommentNonInteractiveReaction = forwardRef(({ reaction, overrides, ...prop
165
167
  ref: forwardedRef
166
168
  });
167
169
  });
170
+ function CommentFileAttachment({
171
+ attachment,
172
+ onAttachmentClick,
173
+ className,
174
+ overrides,
175
+ ...props
176
+ }) {
177
+ const { url } = useAttachmentUrl(attachment.id);
178
+ const handleClick = useCallback(
179
+ (event) => {
180
+ if (!url) {
181
+ return;
182
+ }
183
+ const args = { attachment, url };
184
+ onAttachmentClick?.(args, event);
185
+ if (event.isDefaultPrevented()) {
186
+ return;
187
+ }
188
+ if (attachment.mimeType === "application/pdf" || attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/") || attachment.mimeType.startsWith("audio/")) {
189
+ window.open(url, "_blank");
190
+ } else {
191
+ download(url, attachment.name);
192
+ }
193
+ },
194
+ [attachment, onAttachmentClick, url]
195
+ );
196
+ return /* @__PURE__ */ React__default.createElement(FileAttachment, {
197
+ className: classNames("lb-comment-attachment", className),
198
+ ...props,
199
+ attachment,
200
+ overrides,
201
+ onClick: url ? handleClick : void 0
202
+ });
203
+ }
204
+ function CommentNonInteractiveFileAttachment({
205
+ className,
206
+ ...props
207
+ }) {
208
+ return /* @__PURE__ */ React__default.createElement(FileAttachment, {
209
+ className: classNames("lb-comment-attachment", className),
210
+ ...props
211
+ });
212
+ }
168
213
  function AutoMarkReadThreadIdHandler({
169
214
  threadId,
170
215
  commentRef
@@ -189,8 +234,10 @@ const Comment = forwardRef(
189
234
  showDeleted,
190
235
  showActions = "hover",
191
236
  showReactions = true,
237
+ showAttachments = true,
192
238
  onAuthorClick,
193
239
  onMentionClick,
240
+ onAttachmentClick,
194
241
  onCommentEdit,
195
242
  onCommentDelete,
196
243
  overrides,
@@ -227,14 +274,15 @@ const Comment = forwardRef(
227
274
  []
228
275
  );
229
276
  const handleEditSubmit = useCallback(
230
- ({ body }, event) => {
277
+ ({ body, attachments }, event) => {
231
278
  onCommentEdit?.(comment);
232
279
  event.preventDefault();
233
280
  setEditing(false);
234
281
  editComment({
235
282
  commentId: comment.id,
236
283
  threadId: comment.threadId,
237
- body
284
+ body,
285
+ attachments
238
286
  });
239
287
  },
240
288
  [comment, editComment, onCommentEdit]
@@ -385,8 +433,10 @@ const Comment = forwardRef(
385
433
  className: "lb-comment-composer",
386
434
  onComposerSubmit: handleEditSubmit,
387
435
  defaultValue: comment.body,
436
+ defaultAttachments: comment.attachments,
388
437
  autoFocus: true,
389
438
  showAttribution: false,
439
+ showAttachments,
390
440
  actions: /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement(Tooltip, {
391
441
  content: $.COMMENT_EDIT_COMPOSER_CANCEL,
392
442
  "aria-label": $.COMMENT_EDIT_COMPOSER_CANCEL
@@ -423,7 +473,14 @@ const Comment = forwardRef(
423
473
  }),
424
474
  Link: CommentLink
425
475
  }
426
- }), showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React__default.createElement("div", {
476
+ }), showAttachments && comment.attachments.length > 0 ? /* @__PURE__ */ React__default.createElement("div", {
477
+ className: "lb-comment-attachments"
478
+ }, comment.attachments.map((attachment) => /* @__PURE__ */ React__default.createElement(CommentFileAttachment, {
479
+ key: attachment.id,
480
+ attachment,
481
+ overrides,
482
+ onAttachmentClick
483
+ }))) : null, showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React__default.createElement("div", {
427
484
  className: "lb-comment-reactions"
428
485
  }, comment.reactions.map((reaction) => /* @__PURE__ */ React__default.createElement(CommentReaction, {
429
486
  key: reaction.emoji,
@@ -451,5 +508,5 @@ const Comment = forwardRef(
451
508
  }
452
509
  );
453
510
 
454
- export { Comment, CommentLink, CommentMention, CommentNonInteractiveLink, CommentNonInteractiveReaction, CommentReaction };
511
+ export { Comment, CommentLink, CommentMention, CommentNonInteractiveFileAttachment, CommentNonInteractiveLink, CommentNonInteractiveReaction, CommentReaction };
455
512
  //# sourceMappingURL=Comment.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Comment.mjs","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, index) => (\n <User\n key={users.id}\n userId={users.id}\n capitalize={index === 0}\n replaceSelf\n />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\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-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;AACY;AACG;AACQ;AACX;AAEd;AACkB;AACT;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.mjs","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 { FileAttachment } 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 CommentFileAttachmentProps\n 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, index) => (\n <User\n key={users.id}\n userId={users.id}\n capitalize={index === 0}\n replaceSelf\n />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\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 CommentFileAttachment({\n attachment,\n onAttachmentClick,\n className,\n overrides,\n ...props\n}: CommentFileAttachmentProps) {\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 // 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 [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}: CommentFileAttachmentProps) {\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\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-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 && comment.attachments.length > 0 ? (\n <div className=\"lb-comment-attachments\">\n {comment.attachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n />\n ))}\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkFA;AAsGO;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;AACY;AACG;AACQ;AACX;AAEd;AACkB;AACT;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;AAA+B;AAC7B;AACA;AACA;AACA;AAEF;AACE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAKF;AAME;AAAyB;AAEzB;AAA6B;AAC/B;AACF;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;AAEA;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;AAGC;AAAc;AAEV;AACiB;AAChB;AACA;AACA;AAML;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;;"}