@liveblocks/react-ui 3.14.0-types2 → 3.15.0-components1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/_private/index.cjs +5 -5
- package/dist/_private/index.d.cts +14 -4
- package/dist/_private/index.d.ts +14 -4
- package/dist/_private/index.js +3 -2
- package/dist/_private/index.js.map +1 -1
- package/dist/components/AvatarStack.cjs +115 -0
- package/dist/components/AvatarStack.cjs.map +1 -0
- package/dist/components/AvatarStack.js +113 -0
- package/dist/components/AvatarStack.js.map +1 -0
- package/dist/components/Comment.cjs +10 -28
- package/dist/components/Comment.cjs.map +1 -1
- package/dist/components/Comment.js +12 -11
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/CommentPin.cjs +27 -0
- package/dist/components/CommentPin.cjs.map +1 -0
- package/dist/components/CommentPin.js +25 -0
- package/dist/components/CommentPin.js.map +1 -0
- package/dist/components/Composer.cjs +2 -4
- package/dist/components/Composer.cjs.map +1 -1
- package/dist/components/Composer.js +3 -5
- package/dist/components/Composer.js.map +1 -1
- package/dist/components/Cursor.cjs +40 -0
- package/dist/components/Cursor.cjs.map +1 -0
- package/dist/components/Cursor.js +38 -0
- package/dist/components/Cursor.js.map +1 -0
- package/dist/components/Cursors.cjs +252 -0
- package/dist/components/Cursors.cjs.map +1 -0
- package/dist/components/Cursors.js +250 -0
- package/dist/components/Cursors.js.map +1 -0
- package/dist/components/FloatingComposer.cjs +82 -0
- package/dist/components/FloatingComposer.cjs.map +1 -0
- package/dist/components/FloatingComposer.js +80 -0
- package/dist/components/FloatingComposer.js.map +1 -0
- package/dist/components/FloatingThread.cjs +82 -0
- package/dist/components/FloatingThread.cjs.map +1 -0
- package/dist/components/FloatingThread.js +80 -0
- package/dist/components/FloatingThread.js.map +1 -0
- package/dist/components/InboxNotification.cjs +4 -6
- package/dist/components/InboxNotification.cjs.map +1 -1
- package/dist/components/InboxNotification.js +5 -7
- package/dist/components/InboxNotification.js.map +1 -1
- package/dist/components/Thread.cjs +19 -26
- package/dist/components/Thread.cjs.map +1 -1
- package/dist/components/Thread.js +19 -7
- package/dist/components/Thread.js.map +1 -1
- package/dist/components/internal/AiComposer.cjs +1 -2
- package/dist/components/internal/AiComposer.cjs.map +1 -1
- package/dist/components/internal/AiComposer.js +1 -2
- package/dist/components/internal/AiComposer.js.map +1 -1
- package/dist/components/internal/CodeBlock.cjs +1 -2
- package/dist/components/internal/CodeBlock.cjs.map +1 -1
- package/dist/components/internal/CodeBlock.js +1 -2
- package/dist/components/internal/CodeBlock.js.map +1 -1
- package/dist/components/internal/Dropdown.cjs +7 -28
- package/dist/components/internal/Dropdown.cjs.map +1 -1
- package/dist/components/internal/Dropdown.js +7 -7
- package/dist/components/internal/Dropdown.js.map +1 -1
- package/dist/components/internal/EmojiPicker.cjs +6 -27
- package/dist/components/internal/EmojiPicker.cjs.map +1 -1
- package/dist/components/internal/EmojiPicker.js +6 -6
- package/dist/components/internal/EmojiPicker.js.map +1 -1
- package/dist/components/internal/List.cjs +2 -2
- package/dist/components/internal/List.cjs.map +1 -1
- package/dist/components/internal/List.js +2 -2
- package/dist/components/internal/List.js.map +1 -1
- package/dist/components/internal/Tooltip.cjs +7 -28
- package/dist/components/internal/Tooltip.cjs.map +1 -1
- package/dist/components/internal/Tooltip.js +7 -7
- package/dist/components/internal/Tooltip.js.map +1 -1
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +214 -125
- package/dist/index.d.ts +214 -125
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/primitives/AiComposer/index.cjs +9 -6
- package/dist/primitives/AiComposer/index.cjs.map +1 -1
- package/dist/primitives/AiComposer/index.js +9 -6
- package/dist/primitives/AiComposer/index.js.map +1 -1
- package/dist/primitives/AiMessage/index.cjs +2 -2
- package/dist/primitives/AiMessage/index.cjs.map +1 -1
- package/dist/primitives/AiMessage/index.js +2 -2
- package/dist/primitives/AiMessage/index.js.map +1 -1
- package/dist/primitives/Collapsible/index.cjs +4 -4
- package/dist/primitives/Collapsible/index.cjs.map +1 -1
- package/dist/primitives/Collapsible/index.js +4 -4
- package/dist/primitives/Collapsible/index.js.map +1 -1
- package/dist/primitives/Comment/index.cjs +4 -4
- package/dist/primitives/Comment/index.cjs.map +1 -1
- package/dist/primitives/Comment/index.js +4 -4
- package/dist/primitives/Comment/index.js.map +1 -1
- package/dist/primitives/Composer/index.cjs +27 -42
- package/dist/primitives/Composer/index.cjs.map +1 -1
- package/dist/primitives/Composer/index.js +27 -23
- package/dist/primitives/Composer/index.js.map +1 -1
- package/dist/primitives/Duration.cjs +2 -2
- package/dist/primitives/Duration.cjs.map +1 -1
- package/dist/primitives/Duration.js +2 -2
- package/dist/primitives/Duration.js.map +1 -1
- package/dist/primitives/FileSize.cjs +2 -2
- package/dist/primitives/FileSize.cjs.map +1 -1
- package/dist/primitives/FileSize.js +2 -2
- package/dist/primitives/FileSize.js.map +1 -1
- package/dist/primitives/Markdown.cjs +2 -2
- package/dist/primitives/Markdown.cjs.map +1 -1
- package/dist/primitives/Markdown.js +2 -2
- package/dist/primitives/Markdown.js.map +1 -1
- package/dist/primitives/Timestamp.cjs +2 -2
- package/dist/primitives/Timestamp.cjs.map +1 -1
- package/dist/primitives/Timestamp.js +2 -2
- package/dist/primitives/Timestamp.js.map +1 -1
- package/dist/utils/Portal.cjs +6 -3
- package/dist/utils/Portal.cjs.map +1 -1
- package/dist/utils/Portal.js +6 -3
- package/dist/utils/Portal.js.map +1 -1
- package/dist/utils/animation-loop.cjs +44 -0
- package/dist/utils/animation-loop.cjs.map +1 -0
- package/dist/utils/animation-loop.js +42 -0
- package/dist/utils/animation-loop.js.map +1 -0
- package/dist/utils/use-pre-resolve-user.cjs +18 -0
- package/dist/utils/use-pre-resolve-user.cjs.map +1 -0
- package/dist/utils/use-pre-resolve-user.js +16 -0
- package/dist/utils/use-pre-resolve-user.js.map +1 -0
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +9 -13
- package/src/styles/dark/index.css +1 -1
- package/src/styles/index.css +252 -4
- package/styles/dark/attributes.css +1 -1
- package/styles/dark/attributes.css.map +1 -1
- package/styles/dark/media-query.css +1 -1
- package/styles/dark/media-query.css.map +1 -1
- package/styles.css +1 -1
- package/styles.css.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Comment.cjs","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type GroupMentionData,\n MENTION_CHARACTER,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useMarkRoomThreadAsRead,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\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 { EmojiPlusIcon } from \"../icons/EmojiPlus\";\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 as CommentPrimitiveMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useIsGroupMentionMember } from \"../utils/use-group-mention\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { Group } from \"./internal/Group\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } 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 show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\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?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => 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 * Add (or change) items to display in the comment's dropdown.\n */\n dropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n actionsClassName?: string;\n}\n\nexport interface CommentDropdownItemProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n /**\n * An optional icon displayed in this dropdown item.\n */\n icon?: ReactNode;\n\n /**\n * The event handler called when the dropdown item is selected.\n */\n onSelect?: (event: Event) => void;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\ninterface CommentMentionProps\n extends CommentBodyMentionProps,\n CommentPrimitiveMentionProps {\n overrides?: CommentProps[\"overrides\"];\n}\n\nfunction CommentUserMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nfunction CommentGroupMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const isMember = useIsGroupMentionMember(mention as GroupMentionData);\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={isMember ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <Group groupId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentMention({ mention, ...props }: CommentMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return <CommentUserMention mention={mention} {...props} />;\n\n case \"group\":\n return <CommentGroupMention mention={mention} {...props} />;\n\n default:\n return assertNever(mention, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\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={cn(\"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 <CustomButton\n className={cn(\"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 </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\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 roomId,\n commentRef,\n}: {\n threadId: string;\n roomId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n commentRef,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\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\nconst CommentDropdownItem = forwardRef<\n HTMLDivElement,\n CommentDropdownItemProps\n>(({ children, icon, onSelect, onClick, ...props }, forwardedRef) => {\n const handleClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n }\n },\n [onClick]\n );\n\n return (\n <DropdownItem\n icon={icon}\n onSelect={onSelect}\n onClick={handleClick}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </DropdownItem>\n );\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 = Object.assign(\n forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n dropdownItems,\n overrides,\n components,\n className,\n actions,\n actionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\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 if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\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 const defaultDropdownItems =\n comment.userId === currentUserId ? (\n <>\n <CommentDropdownItem onSelect={handleEdit} icon={<EditIcon />}>\n {$.COMMENT_EDIT}\n </CommentDropdownItem>\n <CommentDropdownItem onSelect={handleDelete} icon={<DeleteIcon />}>\n {$.COMMENT_DELETE}\n </CommentDropdownItem>\n </>\n ) : null;\n\n const dropdownContent =\n typeof dropdownItems === \"function\" ? (\n dropdownItems({ children: defaultDropdownItems, comment })\n ) : (\n <>\n {defaultDropdownItems}\n {dropdownItems}\n </>\n );\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n {autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n roomId={comment.roomId}\n />\n )}\n <div\n id={comment.id}\n className={cn(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div className={cn(\"lb-comment-actions\", actionsClassName)}>\n {actions ?? null}\n {showReactions && canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {dropdownContent ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={dropdownContent}\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 icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\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 showFormattingControls={showComposerFormattingControls}\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 icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"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 icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) =>\n onMentionClick?.(mention, event)\n }\n overrides={overrides}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 ||\n fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\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 </ComponentsProvider>\n </TooltipProvider>\n );\n }\n ),\n {\n /**\n * Displays a dropdown item in the comment's dropdown.\n */\n DropdownItem: CommentDropdownItem,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA;AA4IA;AAA4B;AAC1B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AACb;AACvC;AAEJ;AAAuD;AAC7B;AAAA;AAAA;AAGhC;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AAC7B;AACvB;AAEJ;AAAuD;AAC3B;AAAA;AAAA;AAGlC;AAEO;AACL;AAAsB;AAElB;AAAwD;AAGxD;AAAyD;AAGzD;AAAoD;AAE1D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AAC2C;AAC1C;AACI;AACG;AAEsB;AAAA;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AAKF;AAEA;AAIE;AACA;AACE;AAAC;AAAA;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAoE;AACD;AAAA;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACD;AAAC;AAAA;AAGE;AACkB;AACT;AACA;AAAA;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;AACE;AAAC;AAAA;AACU;AACA;AACC;AAEV;AAAiB;AAAhB;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEL;AAAC;AAAA;AAC4B;AAC3B;AACA;AACI;AAAA;AACN;AAAA;AACF;AAAA;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACE;AAAC;AAAA;AACc;AACc;AAC3B;AACA;AACI;AACC;AAAA;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACE;AAAC;AAAA;AACiD;AAC7B;AACf;AAAA;AAGV;AAMA;AAAqC;AACnC;AACA;AAEF;AAKE;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACF;AAEA;AAIE;AAAoB;AAEhB;AAEA;AACE;AAAsB;AACxB;AACF;AACQ;AAGV;AACE;AAAC;AAAA;AACC;AACA;AACS;AACL;AACC;AAEJ;AAAA;AAGP;AAYO;AAAuB;AAC5B;AAEI;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;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;AAG0C;AAChB;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;AAGM;AAEA;AAGA;AAIN;AAKO;AAAA;AACA;AAIP;AAGO;AACC;AAAC;AAAA;AACa;AACF;AACM;AAAA;AAClB;AAEF;AAAC;AAAA;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEL;AACE;AACE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACI;AACJ;AAAA;AACZ;AAGK;AAAA;AAGD;AACF;AAEJ;AACF;AACF;AAGK;AAAW;AAEV;AAAC;AAAA;AACgB;AACD;AAIV;AAAC;AAAA;AACW;AACD;AACK;AACO;AAAA;AAG3B;AAAA;AAEA;AAEF;AAAC;AAAA;AACO;AACQ;AACR;AACG;AAIL;AAAC;AAAA;AACW;AACS;AACV;AACK;AACM;AAAA;AAG1B;AAAA;AAEA;AACN;AAEJ;AAGI;AAAC;AAAA;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAGpB;AAAA;AAAC;AAAA;AACY;AACG;AAEd;AAAC;AAAA;AACW;AACD;AACQ;AAAA;AACnB;AAAA;AACF;AACA;AAAC;AAAA;AACY;AACF;AAGP;AAAC;AAAA;AACS;AACE;AACD;AACK;AACG;AAAA;AAErB;AAAA;AACF;AACF;AAES;AACe;AAC1B;AACgB;AAAA;AAIhB;AAAA;AAAkB;AAAjB;AACW;AACI;AACF;AAER;AAAC;AAAA;AACC;AAEiC;AAEjC;AAAA;AACF;AAEI;AACR;AAAA;AACF;AAKK;AAGK;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAIE;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAEJ;AAGC;AACC;AAAC;AAAA;AAEC;AACA;AACA;AACW;AAAA;AAJG;AAMjB;AAKO;AAAC;AAAA;AACW;AACF;AACC;AACK;AACO;AAAA;AAK3B;AACN;AAQR;AAAA;AAAA;AACF;AAEJ;AAEJ;AACF;AACA;AAAA;AAAA;AAAA;AAIgB;AAElB;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"Comment.cjs","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type GroupMentionData,\n MENTION_CHARACTER,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useMarkRoomThreadAsRead,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\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 { EmojiPlusIcon } from \"../icons/EmojiPlus\";\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 as CommentPrimitiveMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useIsGroupMentionMember } from \"../utils/use-group-mention\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { Group } from \"./internal/Group\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } 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 show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\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?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => 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 * Add (or change) items to display in the comment's dropdown.\n */\n dropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n actionsClassName?: string;\n}\n\nexport interface CommentDropdownItemProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n /**\n * An optional icon displayed in this dropdown item.\n */\n icon?: ReactNode;\n\n /**\n * The event handler called when the dropdown item is selected.\n */\n onSelect?: (event: Event) => void;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\ninterface CommentMentionProps\n extends CommentBodyMentionProps,\n CommentPrimitiveMentionProps {\n overrides?: CommentProps[\"overrides\"];\n}\n\nfunction CommentUserMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nfunction CommentGroupMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const isMember = useIsGroupMentionMember(mention as GroupMentionData);\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={isMember ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <Group groupId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentMention({ mention, ...props }: CommentMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return <CommentUserMention mention={mention} {...props} />;\n\n case \"group\":\n return <CommentGroupMention mention={mention} {...props} />;\n\n default:\n return assertNever(mention, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\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={cn(\"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 <CustomButton\n className={cn(\"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 </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\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 roomId,\n commentRef,\n}: {\n threadId: string;\n roomId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n commentRef,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\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\nconst CommentDropdownItem = forwardRef<\n HTMLDivElement,\n CommentDropdownItemProps\n>(({ children, icon, onSelect, onClick, ...props }, forwardedRef) => {\n const handleClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n }\n },\n [onClick]\n );\n\n return (\n <DropdownItem\n icon={icon}\n onSelect={onSelect}\n onClick={handleClick}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </DropdownItem>\n );\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 = Object.assign(\n forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n dropdownItems,\n overrides,\n components,\n className,\n actions,\n actionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const bodyId = `${comment.id}:body`;\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\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 if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\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 const defaultDropdownItems =\n comment.userId === currentUserId ? (\n <>\n <CommentDropdownItem onSelect={handleEdit} icon={<EditIcon />}>\n {$.COMMENT_EDIT}\n </CommentDropdownItem>\n <CommentDropdownItem onSelect={handleDelete} icon={<DeleteIcon />}>\n {$.COMMENT_DELETE}\n </CommentDropdownItem>\n </>\n ) : null;\n\n const dropdownContent =\n typeof dropdownItems === \"function\" ? (\n dropdownItems({ children: defaultDropdownItems, comment })\n ) : (\n <>\n {defaultDropdownItems}\n {dropdownItems}\n </>\n );\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n {autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n roomId={comment.roomId}\n />\n )}\n <div\n role=\"article\"\n id={comment.id}\n className={cn(\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 aria-labelledby={bodyId}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div className={cn(\"lb-comment-actions\", actionsClassName)}>\n {actions ?? null}\n {showReactions && canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {dropdownContent ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={dropdownContent}\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 icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\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 showFormattingControls={showComposerFormattingControls}\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 icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"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 icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n id={bodyId}\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) =>\n onMentionClick?.(mention, event)\n }\n overrides={overrides}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 ||\n fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\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 </ComponentsProvider>\n </TooltipProvider>\n );\n }\n ),\n {\n /**\n * Displays a dropdown item in the comment's dropdown.\n */\n DropdownItem: CommentDropdownItem,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA;AA4IA;AAA4B;AAC1B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AACb;AACvC;AAEJ;AAAuD;AAC7B;AAAA;AAAA;AAGhC;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AAC7B;AACvB;AAEJ;AAAuD;AAC3B;AAAA;AAAA;AAGlC;AAEO;AACL;AAAsB;AAElB;AAAwD;AAGxD;AAAyD;AAGzD;AAAoD;AAE1D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AAC2C;AAC1C;AACI;AACG;AAEsB;AAAA;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AAKF;AAEA;AAIE;AACA;AACE;AAAC;AAAA;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAoE;AACD;AAAA;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACD;AAAC;AAAA;AAGE;AACkB;AACT;AACA;AAAA;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;AACE;AAAC;AAAA;AACU;AACA;AACC;AAEV;AAAiB;AAAhB;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEL;AAAC;AAAA;AAC4B;AAC3B;AACA;AACI;AAAA;AACN;AAAA;AACF;AAAA;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACE;AAAC;AAAA;AACc;AACc;AAC3B;AACA;AACI;AACC;AAAA;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACE;AAAC;AAAA;AACiD;AAC7B;AACf;AAAA;AAGV;AAMA;AAAqC;AACnC;AACA;AAEF;AAKE;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACF;AAEA;AAIE;AAAoB;AAEhB;AAEA;AACE;AAAsB;AACxB;AACF;AACQ;AAGV;AACE;AAAC;AAAA;AACC;AACA;AACS;AACL;AACC;AAEJ;AAAA;AAGP;AAYO;AAAuB;AAC5B;AAEI;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;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;AAG0C;AAChB;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;AAGM;AAEA;AAGA;AAIN;AAKO;AAAA;AACA;AAIP;AAGO;AACC;AAAC;AAAA;AACa;AACF;AACM;AAAA;AAClB;AAEF;AAAC;AAAA;AACM;AACO;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACZ;AACV;AACH;AACC;AAEL;AACE;AACE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACI;AACJ;AAAA;AACZ;AAGK;AAAA;AAGD;AACF;AAEJ;AACF;AACF;AAGK;AAAW;AAEV;AAAC;AAAA;AACgB;AACD;AAIV;AAAC;AAAA;AACW;AACD;AACK;AACO;AAAA;AAG3B;AAAA;AAEA;AAEF;AAAC;AAAA;AACO;AACQ;AACR;AACG;AAIL;AAAC;AAAA;AACW;AACS;AACV;AACK;AACM;AAAA;AAG1B;AAAA;AAEA;AACN;AAEJ;AAGI;AAAC;AAAA;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAGpB;AAAA;AAAC;AAAA;AACY;AACG;AAEd;AAAC;AAAA;AACW;AACD;AACQ;AAAA;AACnB;AAAA;AACF;AACA;AAAC;AAAA;AACY;AACF;AAGP;AAAC;AAAA;AACS;AACE;AACD;AACK;AACG;AAAA;AAErB;AAAA;AACF;AACF;AAES;AACe;AAC1B;AACgB;AAAA;AAIhB;AAAA;AAAkB;AAAjB;AACW;AACN;AACU;AACF;AAER;AAAC;AAAA;AACC;AAEiC;AAEjC;AAAA;AACF;AAEI;AACR;AAAA;AACF;AAKK;AAGK;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAIE;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAEJ;AAGC;AACC;AAAC;AAAA;AAEC;AACA;AACA;AACW;AAAA;AAJG;AAMjB;AAKO;AAAC;AAAA;AACW;AACF;AACC;AACK;AACO;AAAA;AAK3B;AACN;AAQR;AAAA;AAAA;AACF;AAEJ;AAEJ;AACF;AACA;AAAA;AAAA;AAAA;AAIgB;AAElB;;;;;;;;"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { MENTION_CHARACTER, assertNever, Permission } from '@liveblocks/core';
|
|
4
4
|
import { useAddRoomCommentReaction, useRemoveRoomCommentReaction, useRoomAttachmentUrl, useMarkRoomThreadAsRead, useDeleteRoomComment, useEditRoomComment, useRoomPermissions } from '@liveblocks/react/_private';
|
|
5
|
-
import
|
|
5
|
+
import { Toggle } from 'radix-ui';
|
|
6
6
|
import { forwardRef, useMemo, useCallback, useRef, useState, useEffect } from 'react';
|
|
7
7
|
import { useComponents, ComponentsProvider } from '../components.js';
|
|
8
8
|
import { CheckIcon } from '../icons/Check.js';
|
|
@@ -26,16 +26,13 @@ import { Composer } from './Composer.js';
|
|
|
26
26
|
import { MediaAttachment, FileAttachment, separateMediaAttachments } from './internal/Attachment.js';
|
|
27
27
|
import { Avatar } from './internal/Avatar.js';
|
|
28
28
|
import { CustomButton, Button } from './internal/Button.js';
|
|
29
|
-
import { DropdownItem, Dropdown } from './internal/Dropdown.js';
|
|
29
|
+
import { DropdownItem, Dropdown, DropdownTrigger } from './internal/Dropdown.js';
|
|
30
30
|
import { Emoji } from './internal/Emoji.js';
|
|
31
|
-
import { EmojiPicker } from './internal/EmojiPicker.js';
|
|
31
|
+
import { EmojiPicker, EmojiPickerTrigger } from './internal/EmojiPicker.js';
|
|
32
32
|
import { Group } from './internal/Group.js';
|
|
33
33
|
import { List } from './internal/List.js';
|
|
34
|
-
import { Tooltip, ShortcutTooltip } from './internal/Tooltip.js';
|
|
34
|
+
import { Tooltip, TooltipProvider, ShortcutTooltip } from './internal/Tooltip.js';
|
|
35
35
|
import { User } from './internal/User.js';
|
|
36
|
-
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
|
37
|
-
import { PopoverTrigger } from '@radix-ui/react-popover';
|
|
38
|
-
import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
|
|
39
36
|
|
|
40
37
|
|
|
41
38
|
const REACTIONS_TRUNCATE = 5;
|
|
@@ -185,7 +182,7 @@ const CommentReaction = forwardRef(({ comment, reaction, overrides, disabled, ..
|
|
|
185
182
|
multiline: true,
|
|
186
183
|
className: "lb-comment-reaction-tooltip",
|
|
187
184
|
children: /* @__PURE__ */ jsx(
|
|
188
|
-
|
|
185
|
+
Toggle.Root,
|
|
189
186
|
{
|
|
190
187
|
asChild: true,
|
|
191
188
|
pressed: isActive,
|
|
@@ -381,6 +378,7 @@ const Comment = Object.assign(
|
|
|
381
378
|
autoMarkReadThreadId,
|
|
382
379
|
...props
|
|
383
380
|
}, forwardedRef) => {
|
|
381
|
+
const bodyId = `${comment.id}:body`;
|
|
384
382
|
const ref = useRef(null);
|
|
385
383
|
const mergedRefs = useRefs(forwardedRef, ref);
|
|
386
384
|
const currentUserId = useCurrentUserId();
|
|
@@ -505,6 +503,7 @@ const Comment = Object.assign(
|
|
|
505
503
|
/* @__PURE__ */ jsxs(
|
|
506
504
|
"div",
|
|
507
505
|
{
|
|
506
|
+
role: "article",
|
|
508
507
|
id: comment.id,
|
|
509
508
|
className: cn(
|
|
510
509
|
"lb-root lb-comment",
|
|
@@ -516,6 +515,7 @@ const Comment = Object.assign(
|
|
|
516
515
|
"data-deleted": !comment.body ? "" : void 0,
|
|
517
516
|
"data-editing": isEditing ? "" : void 0,
|
|
518
517
|
"data-target": isTarget ? "" : void 0,
|
|
518
|
+
"aria-labelledby": bodyId,
|
|
519
519
|
dir: $.dir,
|
|
520
520
|
...props,
|
|
521
521
|
ref: mergedRefs,
|
|
@@ -562,7 +562,7 @@ const Comment = Object.assign(
|
|
|
562
562
|
{
|
|
563
563
|
onEmojiSelect: handleReactionSelect,
|
|
564
564
|
onOpenChange: setReactionActionOpen,
|
|
565
|
-
children: /* @__PURE__ */ jsx(Tooltip, { content: $.COMMENT_ADD_REACTION, children: /* @__PURE__ */ jsx(
|
|
565
|
+
children: /* @__PURE__ */ jsx(Tooltip, { content: $.COMMENT_ADD_REACTION, children: /* @__PURE__ */ jsx(EmojiPickerTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
566
566
|
Button,
|
|
567
567
|
{
|
|
568
568
|
className: "lb-comment-action",
|
|
@@ -580,7 +580,7 @@ const Comment = Object.assign(
|
|
|
580
580
|
onOpenChange: setMoreActionOpen,
|
|
581
581
|
align: "end",
|
|
582
582
|
content: dropdownContent,
|
|
583
|
-
children: /* @__PURE__ */ jsx(Tooltip, { content: $.COMMENT_MORE, children: /* @__PURE__ */ jsx(
|
|
583
|
+
children: /* @__PURE__ */ jsx(Tooltip, { content: $.COMMENT_MORE, children: /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
584
584
|
Button,
|
|
585
585
|
{
|
|
586
586
|
className: "lb-comment-action",
|
|
@@ -649,6 +649,7 @@ const Comment = Object.assign(
|
|
|
649
649
|
CommentBody,
|
|
650
650
|
{
|
|
651
651
|
className: "lb-comment-body",
|
|
652
|
+
id: bodyId,
|
|
652
653
|
body: comment.body,
|
|
653
654
|
components: {
|
|
654
655
|
Mention: ({ mention }) => /* @__PURE__ */ jsx(
|
|
@@ -696,7 +697,7 @@ const Comment = Object.assign(
|
|
|
696
697
|
},
|
|
697
698
|
reaction.emoji
|
|
698
699
|
)),
|
|
699
|
-
canComment ? /* @__PURE__ */ jsx(EmojiPicker, { onEmojiSelect: handleReactionSelect, children: /* @__PURE__ */ jsx(Tooltip, { content: $.COMMENT_ADD_REACTION, children: /* @__PURE__ */ jsx(
|
|
700
|
+
canComment ? /* @__PURE__ */ jsx(EmojiPicker, { onEmojiSelect: handleReactionSelect, children: /* @__PURE__ */ jsx(Tooltip, { content: $.COMMENT_ADD_REACTION, children: /* @__PURE__ */ jsx(EmojiPickerTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
700
701
|
Button,
|
|
701
702
|
{
|
|
702
703
|
className: "lb-comment-reaction lb-comment-reaction-add",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Comment.js","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type GroupMentionData,\n MENTION_CHARACTER,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useMarkRoomThreadAsRead,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\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 { EmojiPlusIcon } from \"../icons/EmojiPlus\";\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 as CommentPrimitiveMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useIsGroupMentionMember } from \"../utils/use-group-mention\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { Group } from \"./internal/Group\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } 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 show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\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?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => 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 * Add (or change) items to display in the comment's dropdown.\n */\n dropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n actionsClassName?: string;\n}\n\nexport interface CommentDropdownItemProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n /**\n * An optional icon displayed in this dropdown item.\n */\n icon?: ReactNode;\n\n /**\n * The event handler called when the dropdown item is selected.\n */\n onSelect?: (event: Event) => void;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\ninterface CommentMentionProps\n extends CommentBodyMentionProps,\n CommentPrimitiveMentionProps {\n overrides?: CommentProps[\"overrides\"];\n}\n\nfunction CommentUserMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nfunction CommentGroupMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const isMember = useIsGroupMentionMember(mention as GroupMentionData);\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={isMember ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <Group groupId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentMention({ mention, ...props }: CommentMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return <CommentUserMention mention={mention} {...props} />;\n\n case \"group\":\n return <CommentGroupMention mention={mention} {...props} />;\n\n default:\n return assertNever(mention, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\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={cn(\"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 <CustomButton\n className={cn(\"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 </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\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 roomId,\n commentRef,\n}: {\n threadId: string;\n roomId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n commentRef,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\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\nconst CommentDropdownItem = forwardRef<\n HTMLDivElement,\n CommentDropdownItemProps\n>(({ children, icon, onSelect, onClick, ...props }, forwardedRef) => {\n const handleClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n }\n },\n [onClick]\n );\n\n return (\n <DropdownItem\n icon={icon}\n onSelect={onSelect}\n onClick={handleClick}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </DropdownItem>\n );\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 = Object.assign(\n forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n dropdownItems,\n overrides,\n components,\n className,\n actions,\n actionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\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 if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\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 const defaultDropdownItems =\n comment.userId === currentUserId ? (\n <>\n <CommentDropdownItem onSelect={handleEdit} icon={<EditIcon />}>\n {$.COMMENT_EDIT}\n </CommentDropdownItem>\n <CommentDropdownItem onSelect={handleDelete} icon={<DeleteIcon />}>\n {$.COMMENT_DELETE}\n </CommentDropdownItem>\n </>\n ) : null;\n\n const dropdownContent =\n typeof dropdownItems === \"function\" ? (\n dropdownItems({ children: defaultDropdownItems, comment })\n ) : (\n <>\n {defaultDropdownItems}\n {dropdownItems}\n </>\n );\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n {autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n roomId={comment.roomId}\n />\n )}\n <div\n id={comment.id}\n className={cn(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div className={cn(\"lb-comment-actions\", actionsClassName)}>\n {actions ?? null}\n {showReactions && canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {dropdownContent ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={dropdownContent}\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 icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\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 showFormattingControls={showComposerFormattingControls}\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 icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"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 icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) =>\n onMentionClick?.(mention, event)\n }\n overrides={overrides}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 ||\n fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\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 </ComponentsProvider>\n </TooltipProvider>\n );\n }\n ),\n {\n /**\n * Displays a dropdown item in the comment's dropdown.\n */\n DropdownItem: CommentDropdownItem,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA;AA4IA;AAA4B;AAC1B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AACb;AACvC;AAEJ;AAAuD;AAC7B;AAAA;AAAA;AAGhC;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AAC7B;AACvB;AAEJ;AAAuD;AAC3B;AAAA;AAAA;AAGlC;AAEO;AACL;AAAsB;AAElB;AAAwD;AAGxD;AAAyD;AAGzD;AAAoD;AAE1D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AAC2C;AAC1C;AACI;AACG;AAEsB;AAAA;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AAKF;AAEA;AAIE;AACA;AACE;AAAC;AAAA;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAoE;AACD;AAAA;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACD;AAAC;AAAA;AAGE;AACkB;AACT;AACA;AAAA;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;AACE;AAAC;AAAA;AACU;AACA;AACC;AAEV;AAAiB;AAAhB;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEL;AAAC;AAAA;AAC4B;AAC3B;AACA;AACI;AAAA;AACN;AAAA;AACF;AAAA;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACE;AAAC;AAAA;AACc;AACc;AAC3B;AACA;AACI;AACC;AAAA;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACE;AAAC;AAAA;AACiD;AAC7B;AACf;AAAA;AAGV;AAMA;AAAqC;AACnC;AACA;AAEF;AAKE;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACF;AAEA;AAIE;AAAoB;AAEhB;AAEA;AACE;AAAsB;AACxB;AACF;AACQ;AAGV;AACE;AAAC;AAAA;AACC;AACA;AACS;AACL;AACC;AAEJ;AAAA;AAGP;AAYO;AAAuB;AAC5B;AAEI;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;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;AAG0C;AAChB;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;AAGM;AAEA;AAGA;AAIN;AAKO;AAAA;AACA;AAIP;AAGO;AACC;AAAC;AAAA;AACa;AACF;AACM;AAAA;AAClB;AAEF;AAAC;AAAA;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEL;AACE;AACE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACI;AACJ;AAAA;AACZ;AAGK;AAAA;AAGD;AACF;AAEJ;AACF;AACF;AAGK;AAAW;AAEV;AAAC;AAAA;AACgB;AACD;AAIV;AAAC;AAAA;AACW;AACD;AACK;AACO;AAAA;AAG3B;AAAA;AAEA;AAEF;AAAC;AAAA;AACO;AACQ;AACR;AACG;AAIL;AAAC;AAAA;AACW;AACS;AACV;AACK;AACM;AAAA;AAG1B;AAAA;AAEA;AACN;AAEJ;AAGI;AAAC;AAAA;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAGpB;AAAA;AAAC;AAAA;AACY;AACG;AAEd;AAAC;AAAA;AACW;AACD;AACQ;AAAA;AACnB;AAAA;AACF;AACA;AAAC;AAAA;AACY;AACF;AAGP;AAAC;AAAA;AACS;AACE;AACD;AACK;AACG;AAAA;AAErB;AAAA;AACF;AACF;AAES;AACe;AAC1B;AACgB;AAAA;AAIhB;AAAA;AAAkB;AAAjB;AACW;AACI;AACF;AAER;AAAC;AAAA;AACC;AAEiC;AAEjC;AAAA;AACF;AAEI;AACR;AAAA;AACF;AAKK;AAGK;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAIE;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAEJ;AAGC;AACC;AAAC;AAAA;AAEC;AACA;AACA;AACW;AAAA;AAJG;AAMjB;AAKO;AAAC;AAAA;AACW;AACF;AACC;AACK;AACO;AAAA;AAK3B;AACN;AAQR;AAAA;AAAA;AACF;AAEJ;AAEJ;AACF;AACA;AAAA;AAAA;AAAA;AAIgB;AAElB;;"}
|
|
1
|
+
{"version":3,"file":"Comment.js","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type GroupMentionData,\n MENTION_CHARACTER,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useMarkRoomThreadAsRead,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n PropsWithChildren,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\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 { EmojiPlusIcon } from \"../icons/EmojiPlus\";\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 as CommentPrimitiveMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useIsGroupMentionMember } from \"../utils/use-group-mention\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { Group } from \"./internal/Group\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } 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 show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\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?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => 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 * Add (or change) items to display in the comment's dropdown.\n */\n dropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n actions?: ReactNode;\n\n /**\n * @internal\n */\n actionsClassName?: string;\n}\n\nexport interface CommentDropdownItemProps\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"onSelect\"> {\n /**\n * An optional icon displayed in this dropdown item.\n */\n icon?: ReactNode;\n\n /**\n * The event handler called when the dropdown item is selected.\n */\n onSelect?: (event: Event) => void;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\ninterface CommentMentionProps\n extends CommentBodyMentionProps,\n CommentPrimitiveMentionProps {\n overrides?: CommentProps[\"overrides\"];\n}\n\nfunction CommentUserMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nfunction CommentGroupMention({\n mention,\n className,\n ...props\n}: CommentMentionProps) {\n const isMember = useIsGroupMentionMember(mention as GroupMentionData);\n\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={isMember ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <Group groupId={mention.id} />\n </CommentPrimitive.Mention>\n );\n}\n\nexport function CommentMention({ mention, ...props }: CommentMentionProps) {\n switch (mention.kind) {\n case \"user\":\n return <CommentUserMention mention={mention} {...props} />;\n\n case \"group\":\n return <CommentGroupMention mention={mention} {...props} />;\n\n default:\n return assertNever(mention, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\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={cn(\"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 <CustomButton\n className={cn(\"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 </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\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 roomId,\n commentRef,\n}: {\n threadId: string;\n roomId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n commentRef,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\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\nconst CommentDropdownItem = forwardRef<\n HTMLDivElement,\n CommentDropdownItemProps\n>(({ children, icon, onSelect, onClick, ...props }, forwardedRef) => {\n const handleClick = useCallback(\n (event: MouseEvent<HTMLDivElement>) => {\n onClick?.(event);\n\n if (!event.isDefaultPrevented()) {\n event.stopPropagation();\n }\n },\n [onClick]\n );\n\n return (\n <DropdownItem\n icon={icon}\n onSelect={onSelect}\n onClick={handleClick}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </DropdownItem>\n );\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 = Object.assign(\n forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n dropdownItems,\n overrides,\n components,\n className,\n actions,\n actionsClassName,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const bodyId = `${comment.id}:body`;\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\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 if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\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 const defaultDropdownItems =\n comment.userId === currentUserId ? (\n <>\n <CommentDropdownItem onSelect={handleEdit} icon={<EditIcon />}>\n {$.COMMENT_EDIT}\n </CommentDropdownItem>\n <CommentDropdownItem onSelect={handleDelete} icon={<DeleteIcon />}>\n {$.COMMENT_DELETE}\n </CommentDropdownItem>\n </>\n ) : null;\n\n const dropdownContent =\n typeof dropdownItems === \"function\" ? (\n dropdownItems({ children: defaultDropdownItems, comment })\n ) : (\n <>\n {defaultDropdownItems}\n {dropdownItems}\n </>\n );\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n {autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n roomId={comment.roomId}\n />\n )}\n <div\n role=\"article\"\n id={comment.id}\n className={cn(\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 aria-labelledby={bodyId}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div className={cn(\"lb-comment-actions\", actionsClassName)}>\n {actions ?? null}\n {showReactions && canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {dropdownContent ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={dropdownContent}\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 icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\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 showFormattingControls={showComposerFormattingControls}\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 icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"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 icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n id={bodyId}\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) =>\n onMentionClick?.(mention, event)\n }\n overrides={overrides}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 ||\n fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\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 icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\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 </ComponentsProvider>\n </TooltipProvider>\n );\n }\n ),\n {\n /**\n * Displays a dropdown item in the comment's dropdown.\n */\n DropdownItem: CommentDropdownItem,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FA;AA4IA;AAA4B;AAC1B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AACb;AACvC;AAEJ;AAAuD;AAC7B;AAAA;AAAA;AAGhC;AAEA;AAA6B;AAC3B;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AACyD;AAC7B;AACvB;AAEJ;AAAuD;AAC3B;AAAA;AAAA;AAGlC;AAEO;AACL;AAAsB;AAElB;AAAwD;AAGxD;AAAyD;AAGzD;AAAoD;AAE1D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACE;AAAkB;AAAjB;AAC2C;AAC1C;AACI;AACG;AAEsB;AAAA;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AAKF;AAEA;AAIE;AACA;AACE;AAAC;AAAA;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAoE;AACD;AAAA;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAGd;AACD;AAAC;AAAA;AAGE;AACkB;AACT;AACA;AAAA;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;AACE;AAAC;AAAA;AACU;AACA;AACC;AAEV;AAAiB;AAAhB;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEL;AAAC;AAAA;AAC4B;AAC3B;AACA;AACI;AAAA;AACN;AAAA;AACF;AAAA;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACE;AAAC;AAAA;AACc;AACc;AAC3B;AACA;AACI;AACC;AAAA;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACE;AAAC;AAAA;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAAA;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACE;AAAC;AAAA;AACiD;AAC7B;AACf;AAAA;AAGV;AAMA;AAAqC;AACnC;AACA;AAEF;AAKE;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACF;AAEA;AAIE;AAAoB;AAEhB;AAEA;AACE;AAAsB;AACxB;AACF;AACQ;AAGV;AACE;AAAC;AAAA;AACC;AACA;AACS;AACL;AACC;AAEJ;AAAA;AAGP;AAYO;AAAuB;AAC5B;AAEI;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;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;AAG0C;AAChB;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;AAGM;AAEA;AAGA;AAIN;AAKO;AAAA;AACA;AAIP;AAGO;AACC;AAAC;AAAA;AACa;AACF;AACM;AAAA;AAClB;AAEF;AAAC;AAAA;AACM;AACO;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACZ;AACV;AACH;AACC;AAEL;AACE;AACE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACM;AACP;AAAA;AACX;AAEE;AAAA;AAAC;AAAA;AACW;AACI;AACJ;AAAA;AACZ;AAGK;AAAA;AAGD;AACF;AAEJ;AACF;AACF;AAGK;AAAW;AAEV;AAAC;AAAA;AACgB;AACD;AAIV;AAAC;AAAA;AACW;AACD;AACK;AACO;AAAA;AAG3B;AAAA;AAEA;AAEF;AAAC;AAAA;AACO;AACQ;AACR;AACG;AAIL;AAAC;AAAA;AACW;AACS;AACV;AACK;AACM;AAAA;AAG1B;AAAA;AAEA;AACN;AAEJ;AAGI;AAAC;AAAA;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAGpB;AAAA;AAAC;AAAA;AACY;AACG;AAEd;AAAC;AAAA;AACW;AACD;AACQ;AAAA;AACnB;AAAA;AACF;AACA;AAAC;AAAA;AACY;AACF;AAGP;AAAC;AAAA;AACS;AACE;AACD;AACK;AACG;AAAA;AAErB;AAAA;AACF;AACF;AAES;AACe;AAC1B;AACgB;AAAA;AAIhB;AAAA;AAAkB;AAAjB;AACW;AACN;AACU;AACF;AAER;AAAC;AAAA;AACC;AAEiC;AAEjC;AAAA;AACF;AAEI;AACR;AAAA;AACF;AAKK;AAGK;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAIE;AAAC;AAAA;AAEC;AACA;AACA;AACgB;AAAA;AAJA;AAQpB;AAEJ;AAGC;AACC;AAAC;AAAA;AAEC;AACA;AACA;AACW;AAAA;AAJG;AAMjB;AAKO;AAAC;AAAA;AACW;AACF;AACC;AACK;AACO;AAAA;AAK3B;AACN;AAQR;AAAA;AAAA;AACF;AAEJ;AAEJ;AACF;AACA;AAAA;AAAA;AAAA;AAIgB;AAElB;;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var cn = require('../utils/cn.cjs');
|
|
7
|
+
var Avatar = require('./internal/Avatar.cjs');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const CommentPin = react.forwardRef(
|
|
11
|
+
({ corner = "bottom-left", userId, type = "button", className, ...props }, forwardedRef) => {
|
|
12
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13
|
+
"button",
|
|
14
|
+
{
|
|
15
|
+
className: cn.cn("lb-root lb-comment-pin", className),
|
|
16
|
+
"data-corner": corner,
|
|
17
|
+
type,
|
|
18
|
+
...props,
|
|
19
|
+
ref: forwardedRef,
|
|
20
|
+
children: userId ? /* @__PURE__ */ jsxRuntime.jsx(Avatar.Avatar, { className: "lb-comment-pin-avatar", userId }) : null
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
exports.CommentPin = CommentPin;
|
|
27
|
+
//# sourceMappingURL=CommentPin.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommentPin.cjs","sources":["../../src/components/CommentPin.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport { forwardRef } from \"react\";\n\nimport { cn } from \"../utils/cn\";\nimport { Avatar } from \"./internal/Avatar\";\n\nexport interface CommentPinProps extends ComponentPropsWithoutRef<\"button\"> {\n /**\n * The corner that points to the comment position.\n */\n corner?: \"top-left\" | \"top-right\" | \"bottom-right\" | \"bottom-left\";\n\n /**\n * The user ID to optionally display an avatar for.\n */\n userId?: string;\n}\n\n/**\n * Displays a comment pin that can be used as a trigger\n * for `FloatingComposer` and `FloatingThread`.\n */\nexport const CommentPin = forwardRef<HTMLButtonElement, CommentPinProps>(\n (\n { corner = \"bottom-left\", userId, type = \"button\", className, ...props },\n forwardedRef\n ) => {\n return (\n <button\n className={cn(\"lb-root lb-comment-pin\", className)}\n data-corner={corner}\n type={type}\n {...props}\n ref={forwardedRef}\n >\n {userId ? (\n <Avatar className=\"lb-comment-pin-avatar\" userId={userId} />\n ) : null}\n </button>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;;;AAwBO;AAAmB;AAKtB;AACE;AAAC;AAAA;AACkD;AACpC;AACb;AACI;AACC;AAID;AAAA;AACN;AAGN;;"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { cn } from '../utils/cn.js';
|
|
5
|
+
import { Avatar } from './internal/Avatar.js';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const CommentPin = forwardRef(
|
|
9
|
+
({ corner = "bottom-left", userId, type = "button", className, ...props }, forwardedRef) => {
|
|
10
|
+
return /* @__PURE__ */ jsx(
|
|
11
|
+
"button",
|
|
12
|
+
{
|
|
13
|
+
className: cn("lb-root lb-comment-pin", className),
|
|
14
|
+
"data-corner": corner,
|
|
15
|
+
type,
|
|
16
|
+
...props,
|
|
17
|
+
ref: forwardedRef,
|
|
18
|
+
children: userId ? /* @__PURE__ */ jsx(Avatar, { className: "lb-comment-pin-avatar", userId }) : null
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export { CommentPin };
|
|
25
|
+
//# sourceMappingURL=CommentPin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommentPin.js","sources":["../../src/components/CommentPin.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport { forwardRef } from \"react\";\n\nimport { cn } from \"../utils/cn\";\nimport { Avatar } from \"./internal/Avatar\";\n\nexport interface CommentPinProps extends ComponentPropsWithoutRef<\"button\"> {\n /**\n * The corner that points to the comment position.\n */\n corner?: \"top-left\" | \"top-right\" | \"bottom-right\" | \"bottom-left\";\n\n /**\n * The user ID to optionally display an avatar for.\n */\n userId?: string;\n}\n\n/**\n * Displays a comment pin that can be used as a trigger\n * for `FloatingComposer` and `FloatingThread`.\n */\nexport const CommentPin = forwardRef<HTMLButtonElement, CommentPinProps>(\n (\n { corner = \"bottom-left\", userId, type = \"button\", className, ...props },\n forwardedRef\n ) => {\n return (\n <button\n className={cn(\"lb-root lb-comment-pin\", className)}\n data-corner={corner}\n type={type}\n {...props}\n ref={forwardedRef}\n >\n {userId ? (\n <Avatar className=\"lb-comment-pin-avatar\" userId={userId} />\n ) : null}\n </button>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;AAwBO;AAAmB;AAKtB;AACE;AAAC;AAAA;AACkD;AACpC;AACb;AACI;AACC;AAID;AAAA;AACN;AAGN;;"}
|
|
@@ -33,9 +33,7 @@ var Group = require('./internal/Group.cjs');
|
|
|
33
33
|
var GroupDescription = require('./internal/GroupDescription.cjs');
|
|
34
34
|
var Tooltip = require('./internal/Tooltip.cjs');
|
|
35
35
|
var User = require('./internal/User.cjs');
|
|
36
|
-
var PopoverPrimitive = require('@radix-ui/react-popover');
|
|
37
36
|
var Users = require('../icons/Users.cjs');
|
|
38
|
-
var TooltipPrimitive = require('@radix-ui/react-tooltip');
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
function ComposerInsertMentionEditorAction({
|
|
@@ -85,7 +83,7 @@ function ComposerInsertEmojiEditorAction({
|
|
|
85
83
|
const stopPropagation = react.useCallback((event) => {
|
|
86
84
|
event.stopPropagation();
|
|
87
85
|
}, []);
|
|
88
|
-
return /* @__PURE__ */ jsxRuntime.jsx(EmojiPicker.EmojiPicker, { onEmojiSelect: insertText, onOpenChange: onPickerOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: tooltipLabel ?? label, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
86
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EmojiPicker.EmojiPicker, { onEmojiSelect: insertText, onOpenChange: onPickerOpenChange, children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: tooltipLabel ?? label, children: /* @__PURE__ */ jsxRuntime.jsx(EmojiPicker.EmojiPickerTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
89
87
|
Button.Button,
|
|
90
88
|
{
|
|
91
89
|
className: cn.cn("lb-composer-editor-action", className),
|
|
@@ -537,7 +535,7 @@ const Composer = react.forwardRef(
|
|
|
537
535
|
threadId
|
|
538
536
|
]
|
|
539
537
|
);
|
|
540
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
538
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Tooltip.TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
541
539
|
index.Form,
|
|
542
540
|
{
|
|
543
541
|
onComposerSubmit: handleComposerSubmit,
|