@liveblocks/react-ui 3.15.0-feeds2 → 3.15.0-rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_private/index.cjs +3 -5
- package/dist/_private/index.cjs.map +1 -1
- package/dist/_private/index.d.cts +10 -4
- package/dist/_private/index.d.ts +10 -4
- package/dist/_private/index.js +2 -2
- package/dist/components/AiChat.cjs +10 -2
- package/dist/components/AiChat.cjs.map +1 -1
- package/dist/components/AiChat.js +10 -2
- package/dist/components/AiChat.js.map +1 -1
- package/dist/components/AvatarStack.cjs +117 -0
- package/dist/components/AvatarStack.cjs.map +1 -0
- package/dist/components/AvatarStack.js +115 -0
- package/dist/components/AvatarStack.js.map +1 -0
- package/dist/components/Comment.cjs +259 -250
- package/dist/components/Comment.cjs.map +1 -1
- package/dist/components/Comment.js +264 -236
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/CommentPin.cjs +36 -0
- package/dist/components/CommentPin.cjs.map +1 -0
- package/dist/components/CommentPin.js +34 -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 +256 -0
- package/dist/components/Cursors.cjs.map +1 -0
- package/dist/components/Cursors.js +254 -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 +61 -51
- package/dist/components/Thread.cjs.map +1 -1
- package/dist/components/Thread.js +66 -37
- 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/Avatar.cjs +10 -13
- package/dist/components/internal/Avatar.cjs.map +1 -1
- package/dist/components/internal/Avatar.js +11 -14
- package/dist/components/internal/Avatar.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 +298 -130
- package/dist/index.d.ts +298 -130
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/primitives/AiComposer/index.cjs +5 -4
- package/dist/primitives/AiComposer/index.cjs.map +1 -1
- package/dist/primitives/AiComposer/index.js +5 -4
- 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 +23 -35
- package/dist/primitives/Composer/index.cjs.map +1 -1
- package/dist/primitives/Composer/index.js +23 -16
- 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 +2 -2
- package/dist/utils/Portal.cjs.map +1 -1
- package/dist/utils/Portal.js +2 -2
- 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/utils/use-stable-component.cjs +32 -0
- package/dist/utils/use-stable-component.cjs.map +1 -0
- package/dist/utils/use-stable-component.js +30 -0
- package/dist/utils/use-stable-component.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 +6 -10
- 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":"Thread.cjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport { findLastIndex } from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.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 handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}-show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isHidden) {\n return null;\n }\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n dropdownItems={({ children }) => {\n const threadDropdownItems = isFirstComment ? (\n <>\n <Comment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </Comment.DropdownItem>\n </>\n ) : null;\n\n if (typeof commentDropdownItems === \"function\") {\n return commentDropdownItems({\n children: (\n <>\n {threadDropdownItems}\n {children}\n </>\n ),\n comment,\n });\n }\n\n return threadDropdownItems ||\n commentDropdownItems ||\n children ? (\n <>\n {threadDropdownItems}\n {children}\n {commentDropdownItems}\n </>\n ) : null;\n }}\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2MO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AAEA;AAIA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAO;AAGT;AACE;AAAC;AAAA;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAGJ;AAEI;AAAS;AAAR;AACW;AAKI;AAMR;AAAA;AAKZ;AACE;AAA4B;AAGrB;AAAA;AACA;AACH;AAEF;AACD;AAGH;AAIK;AAAA;AACA;AACA;AAED;AACN;AAAA;AAvGa;AA2GjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAEE;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AAAA;AACjB;AAAA;AAAA;AAGN;AAGN;;"}
|
|
1
|
+
{"version":3,"file":"Thread.cjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n findLastIndex,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsRead,\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport {\n type ComponentPropsWithoutRef,\n type ComponentType,\n type ForwardedRef,\n forwardRef,\n Fragment,\n type PropsWithChildren,\n type ReactNode,\n type RefAttributes,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { useStableComponent } from \"../utils/use-stable-component\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Comment as DefaultComment, type CommentProps } from \"./Comment\";\nimport { Composer, type ComposerProps } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\n// An invisible marker used to mark a thread as read only when it becomes visible.\nfunction MarkThreadAsReadMarker({ thread }: { thread: ThreadData }) {\n const { id: threadId, roomId } = thread;\n const ref = useRef<HTMLDivElement>(null);\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n ref,\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 (\n <div\n ref={ref}\n style={{ height: 0 }}\n aria-hidden\n data-mark-as-read-marker=\"\"\n />\n );\n}\n\nexport interface ThreadComponents<\n // Future components might reference thread metadata so we declare it\n // already to respect the `TM` → `CM` order used everywhere else.\n _TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> {\n /**\n * The component used to display comments.\n */\n Comment: ComponentType<CommentProps<CM>>;\n}\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & ThreadComponents<TM, CM>>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n autoFocus,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const Comment = useStableComponent(components?.Comment, DefaultComment);\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const commentsCount = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length\n : thread.comments.filter((comment) => comment.body).length;\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.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 handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n let currentCommentIndex = 0;\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\" role=\"feed\">\n {thread.comments.map((comment, index) => {\n const isNonDeletedComment = showDeletedComments || comment.body;\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isHiddenBecauseDeleted =\n !showDeletedComments && !comment.body;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}:show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isHidden || isHiddenBecauseDeleted) {\n return null;\n }\n\n if (isNonDeletedComment) {\n currentCommentIndex++;\n }\n\n const children = (\n <Comment\n key={comment.id}\n tabIndex={0}\n aria-posinset={currentCommentIndex}\n aria-setsize={commentsCount}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n internalDropdownItems={\n isFirstComment ? (\n <DefaultComment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DefaultComment.DropdownItem>\n ) : undefined\n }\n dropdownItems={\n commentDropdownItems as CommentProps[\"dropdownItems\"]\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {unreadIndex !== undefined && (\n <MarkThreadAsReadMarker thread={thread} />\n )}\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n autoFocus={autoFocus}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA2DA;AACE;AACA;AACA;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACE;AAAC;AAAA;AACC;AACmB;AACR;AACc;AAAA;AAG/B;AAsKO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAEsD;AAExD;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEA;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AACA;AAEA;AAIA;AAEA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAO;AAGT;AACE;AAAA;AAGF;AACE;AAAC;AAAA;AAEW;AACK;AACD;AACd;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAEyC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAIF;AAAgB;AAAf;AACW;AAKI;AAMR;AAAA;AAEN;AAGJ;AAAA;AA/EW;AAoFjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAE0C;AAGxC;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AACf;AAAA;AACF;AAAA;AAAA;AAGN;AAGN;;"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
3
|
import { findLastIndex, Permission } from '@liveblocks/core';
|
|
4
|
-
import { useMarkRoomThreadAsResolved, useMarkRoomThreadAsUnresolved, useRoomThreadSubscription, useRoomPermissions } from '@liveblocks/react/_private';
|
|
5
|
-
import
|
|
6
|
-
import { forwardRef, useState, useMemo, useEffect, useCallback, Fragment
|
|
4
|
+
import { useMarkRoomThreadAsRead, useMarkRoomThreadAsResolved, useMarkRoomThreadAsUnresolved, useRoomThreadSubscription, useRoomPermissions } from '@liveblocks/react/_private';
|
|
5
|
+
import { Toggle } from 'radix-ui';
|
|
6
|
+
import { useRef, forwardRef, useState, useMemo, useEffect, useCallback, Fragment } from 'react';
|
|
7
7
|
import { ArrowDownIcon } from '../icons/ArrowDown.js';
|
|
8
8
|
import { BellIcon } from '../icons/Bell.js';
|
|
9
9
|
import { BellCrossedIcon } from '../icons/BellCrossed.js';
|
|
@@ -11,13 +11,42 @@ import { CheckCircleIcon } from '../icons/CheckCircle.js';
|
|
|
11
11
|
import { CheckCircleFillIcon } from '../icons/CheckCircleFill.js';
|
|
12
12
|
import { useOverrides } from '../overrides.js';
|
|
13
13
|
import { cn } from '../utils/cn.js';
|
|
14
|
+
import { useStableComponent } from '../utils/use-stable-component.js';
|
|
15
|
+
import { useIntersectionCallback } from '../utils/use-visible.js';
|
|
16
|
+
import { useWindowFocus } from '../utils/use-window-focus.js';
|
|
14
17
|
import { Comment } from './Comment.js';
|
|
15
18
|
import { Composer } from './Composer.js';
|
|
16
19
|
import { Button } from './internal/Button.js';
|
|
17
|
-
import { Tooltip } from './internal/Tooltip.js';
|
|
18
|
-
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
|
20
|
+
import { TooltipProvider, Tooltip } from './internal/Tooltip.js';
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
function MarkThreadAsReadMarker({ thread }) {
|
|
24
|
+
const { id: threadId, roomId } = thread;
|
|
25
|
+
const ref = useRef(null);
|
|
26
|
+
const markThreadAsRead = useMarkRoomThreadAsRead(roomId);
|
|
27
|
+
const isWindowFocused = useWindowFocus();
|
|
28
|
+
useIntersectionCallback(
|
|
29
|
+
ref,
|
|
30
|
+
(isIntersecting) => {
|
|
31
|
+
if (isIntersecting) {
|
|
32
|
+
markThreadAsRead(threadId);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
// The underlying IntersectionObserver is only enabled when the window is focused
|
|
37
|
+
enabled: isWindowFocused
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
return /* @__PURE__ */ jsx(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
ref,
|
|
44
|
+
style: { height: 0 },
|
|
45
|
+
"aria-hidden": true,
|
|
46
|
+
"data-mark-as-read-marker": ""
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
21
50
|
const Thread = forwardRef(
|
|
22
51
|
({
|
|
23
52
|
thread,
|
|
@@ -40,11 +69,13 @@ const Thread = forwardRef(
|
|
|
40
69
|
onAttachmentClick,
|
|
41
70
|
onComposerSubmit,
|
|
42
71
|
blurComposerOnSubmit,
|
|
72
|
+
autoFocus,
|
|
43
73
|
overrides,
|
|
44
74
|
components,
|
|
45
75
|
className,
|
|
46
76
|
...props
|
|
47
77
|
}, forwardedRef) => {
|
|
78
|
+
const Comment$1 = useStableComponent(components?.Comment, Comment);
|
|
48
79
|
const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);
|
|
49
80
|
const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);
|
|
50
81
|
const $ = useOverrides(overrides);
|
|
@@ -55,6 +86,9 @@ const Thread = forwardRef(
|
|
|
55
86
|
const lastCommentIndex = useMemo(() => {
|
|
56
87
|
return showDeletedComments ? thread.comments.length - 1 : findLastIndex(thread.comments, (comment) => Boolean(comment.body));
|
|
57
88
|
}, [showDeletedComments, thread.comments]);
|
|
89
|
+
const commentsCount = useMemo(() => {
|
|
90
|
+
return showDeletedComments ? thread.comments.length : thread.comments.filter((comment) => comment.body).length;
|
|
91
|
+
}, [showDeletedComments, thread.comments]);
|
|
58
92
|
const hiddenComments = useMemo(() => {
|
|
59
93
|
const maxVisibleCommentsCount = typeof maxVisibleComments === "number" ? maxVisibleComments : maxVisibleComments?.max;
|
|
60
94
|
const visibleCommentsShow = (typeof maxVisibleComments === "object" ? maxVisibleComments?.show : void 0) ?? "newest";
|
|
@@ -168,6 +202,7 @@ const Thread = forwardRef(
|
|
|
168
202
|
subscribe();
|
|
169
203
|
}
|
|
170
204
|
}, [subscriptionStatus, subscribe, unsubscribe]);
|
|
205
|
+
let currentCommentIndex = 0;
|
|
171
206
|
return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(
|
|
172
207
|
"div",
|
|
173
208
|
{
|
|
@@ -182,10 +217,12 @@ const Thread = forwardRef(
|
|
|
182
217
|
...props,
|
|
183
218
|
ref: forwardedRef,
|
|
184
219
|
children: [
|
|
185
|
-
/* @__PURE__ */ jsx("div", { className: "lb-thread-comments", children: thread.comments.map((comment, index) => {
|
|
220
|
+
/* @__PURE__ */ jsx("div", { className: "lb-thread-comments", role: "feed", children: thread.comments.map((comment, index) => {
|
|
221
|
+
const isNonDeletedComment = showDeletedComments || comment.body;
|
|
186
222
|
const isFirstComment = index === firstCommentIndex;
|
|
187
223
|
const isUnread = unreadIndex !== void 0 && index >= unreadIndex;
|
|
188
224
|
const isHidden = hiddenComments && index >= hiddenComments.firstIndex && index <= hiddenComments.lastIndex;
|
|
225
|
+
const isHiddenBecauseDeleted = !showDeletedComments && !comment.body;
|
|
189
226
|
const isFirstHiddenComment = isHidden && index === hiddenComments.firstIndex;
|
|
190
227
|
if (isFirstHiddenComment) {
|
|
191
228
|
return /* @__PURE__ */ jsx(
|
|
@@ -202,15 +239,21 @@ const Thread = forwardRef(
|
|
|
202
239
|
}
|
|
203
240
|
)
|
|
204
241
|
},
|
|
205
|
-
`${comment.id}
|
|
242
|
+
`${comment.id}:show-more`
|
|
206
243
|
);
|
|
207
244
|
}
|
|
208
|
-
if (isHidden) {
|
|
245
|
+
if (isHidden || isHiddenBecauseDeleted) {
|
|
209
246
|
return null;
|
|
210
247
|
}
|
|
248
|
+
if (isNonDeletedComment) {
|
|
249
|
+
currentCommentIndex++;
|
|
250
|
+
}
|
|
211
251
|
const children = /* @__PURE__ */ jsx(
|
|
212
|
-
Comment,
|
|
252
|
+
Comment$1,
|
|
213
253
|
{
|
|
254
|
+
tabIndex: 0,
|
|
255
|
+
"aria-posinset": currentCommentIndex,
|
|
256
|
+
"aria-setsize": commentsCount,
|
|
214
257
|
overrides,
|
|
215
258
|
className: "lb-thread-comment",
|
|
216
259
|
"data-unread": isUnread ? "" : void 0,
|
|
@@ -227,14 +270,13 @@ const Thread = forwardRef(
|
|
|
227
270
|
onMentionClick,
|
|
228
271
|
onAttachmentClick,
|
|
229
272
|
components,
|
|
230
|
-
autoMarkReadThreadId: index === lastCommentIndex && isUnread ? thread.id : void 0,
|
|
231
273
|
actionsClassName: isFirstComment ? "lb-thread-actions" : void 0,
|
|
232
274
|
actions: isFirstComment && showResolveAction ? /* @__PURE__ */ jsx(
|
|
233
275
|
Tooltip,
|
|
234
276
|
{
|
|
235
277
|
content: thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE,
|
|
236
278
|
children: /* @__PURE__ */ jsx(
|
|
237
|
-
|
|
279
|
+
Toggle.Root,
|
|
238
280
|
{
|
|
239
281
|
pressed: thread.resolved,
|
|
240
282
|
onPressedChange: handleResolvedChange,
|
|
@@ -253,34 +295,19 @@ const Thread = forwardRef(
|
|
|
253
295
|
)
|
|
254
296
|
}
|
|
255
297
|
) : null,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
children: subscriptionStatus === "subscribed" ? $.THREAD_UNSUBSCRIBE : $.THREAD_SUBSCRIBE
|
|
263
|
-
}
|
|
264
|
-
) }) : null;
|
|
265
|
-
if (typeof commentDropdownItems === "function") {
|
|
266
|
-
return commentDropdownItems({
|
|
267
|
-
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
268
|
-
threadDropdownItems,
|
|
269
|
-
children2
|
|
270
|
-
] }),
|
|
271
|
-
comment
|
|
272
|
-
});
|
|
298
|
+
internalDropdownItems: isFirstComment ? /* @__PURE__ */ jsx(
|
|
299
|
+
Comment.DropdownItem,
|
|
300
|
+
{
|
|
301
|
+
onSelect: handleSubscribeChange,
|
|
302
|
+
icon: subscriptionStatus === "subscribed" ? /* @__PURE__ */ jsx(BellCrossedIcon, {}) : /* @__PURE__ */ jsx(BellIcon, {}),
|
|
303
|
+
children: subscriptionStatus === "subscribed" ? $.THREAD_UNSUBSCRIBE : $.THREAD_SUBSCRIBE
|
|
273
304
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
children2,
|
|
277
|
-
commentDropdownItems
|
|
278
|
-
] }) : null;
|
|
279
|
-
}
|
|
305
|
+
) : void 0,
|
|
306
|
+
dropdownItems: commentDropdownItems
|
|
280
307
|
},
|
|
281
308
|
comment.id
|
|
282
309
|
);
|
|
283
|
-
return index === newIndicatorIndex && newIndicatorIndex !== firstCommentIndex && newIndicatorIndex <= lastCommentIndex ? /* @__PURE__ */ jsxs(Fragment
|
|
310
|
+
return index === newIndicatorIndex && newIndicatorIndex !== firstCommentIndex && newIndicatorIndex <= lastCommentIndex ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
284
311
|
/* @__PURE__ */ jsx(
|
|
285
312
|
"div",
|
|
286
313
|
{
|
|
@@ -295,6 +322,7 @@ const Thread = forwardRef(
|
|
|
295
322
|
children
|
|
296
323
|
] }, comment.id) : children;
|
|
297
324
|
}) }),
|
|
325
|
+
unreadIndex !== void 0 && /* @__PURE__ */ jsx(MarkThreadAsReadMarker, { thread }),
|
|
298
326
|
showComposer && /* @__PURE__ */ jsx(
|
|
299
327
|
Composer,
|
|
300
328
|
{
|
|
@@ -310,7 +338,8 @@ const Thread = forwardRef(
|
|
|
310
338
|
COMPOSER_SEND: $.THREAD_COMPOSER_SEND,
|
|
311
339
|
...overrides
|
|
312
340
|
},
|
|
313
|
-
roomId: thread.roomId
|
|
341
|
+
roomId: thread.roomId,
|
|
342
|
+
autoFocus
|
|
314
343
|
}
|
|
315
344
|
)
|
|
316
345
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport { findLastIndex } from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentPropsWithoutRef,\n ForwardedRef,\n PropsWithChildren,\n ReactNode,\n RefAttributes,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n Fragment,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.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 handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\">\n {thread.comments.map((comment, index) => {\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}-show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isHidden) {\n return null;\n }\n\n const children = (\n <Comment\n key={comment.id}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n autoMarkReadThreadId={\n index === lastCommentIndex && isUnread\n ? thread.id\n : undefined\n }\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n dropdownItems={({ children }) => {\n const threadDropdownItems = isFirstComment ? (\n <>\n <Comment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </Comment.DropdownItem>\n </>\n ) : null;\n\n if (typeof commentDropdownItems === \"function\") {\n return commentDropdownItems({\n children: (\n <>\n {threadDropdownItems}\n {children}\n </>\n ),\n comment,\n });\n }\n\n return threadDropdownItems ||\n commentDropdownItems ||\n children ? (\n <>\n {threadDropdownItems}\n {children}\n {commentDropdownItems}\n </>\n ) : null;\n }}\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA2MO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AAEA;AAIA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAO;AAGT;AACE;AAAC;AAAA;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAIM;AAGmC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAGJ;AAEI;AAAS;AAAR;AACW;AAKI;AAMR;AAAA;AAKZ;AACE;AAA4B;AAGrB;AAAA;AACA;AACH;AAEF;AACD;AAGH;AAIK;AAAA;AACA;AACA;AAED;AACN;AAAA;AAvGa;AA2GjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAEE;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AAAA;AACjB;AAAA;AAAA;AAGN;AAGN;;"}
|
|
1
|
+
{"version":3,"file":"Thread.js","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n type BaseMetadata,\n type CommentData,\n type DCM,\n type DTM,\n findLastIndex,\n Permission,\n type ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkRoomThreadAsRead,\n useMarkRoomThreadAsResolved,\n useMarkRoomThreadAsUnresolved,\n useRoomPermissions,\n useRoomThreadSubscription,\n} from \"@liveblocks/react/_private\";\nimport { Toggle as TogglePrimitive } from \"radix-ui\";\nimport {\n type ComponentPropsWithoutRef,\n type ComponentType,\n type ForwardedRef,\n forwardRef,\n Fragment,\n type PropsWithChildren,\n type ReactNode,\n type RefAttributes,\n type SyntheticEvent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { BellIcon } from \"../icons/Bell\";\nimport { BellCrossedIcon } from \"../icons/BellCrossed\";\nimport { CheckCircleIcon } from \"../icons/CheckCircle\";\nimport { CheckCircleFillIcon } from \"../icons/CheckCircleFill\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { cn } from \"../utils/cn\";\nimport { useStableComponent } from \"../utils/use-stable-component\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport { Comment as DefaultComment, type CommentProps } from \"./Comment\";\nimport { Composer, type ComposerProps } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\n// An invisible marker used to mark a thread as read only when it becomes visible.\nfunction MarkThreadAsReadMarker({ thread }: { thread: ThreadData }) {\n const { id: threadId, roomId } = thread;\n const ref = useRef<HTMLDivElement>(null);\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n ref,\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 (\n <div\n ref={ref}\n style={{ height: 0 }}\n aria-hidden\n data-mark-as-read-marker=\"\"\n />\n );\n}\n\nexport interface ThreadComponents<\n // Future components might reference thread metadata so we declare it\n // already to respect the `TM` → `CM` order used everywhere else.\n _TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> {\n /**\n * The component used to display comments.\n */\n Comment: ComponentType<CommentProps<CM>>;\n}\n\nexport interface ThreadProps<\n TM extends BaseMetadata = DTM,\n CM extends BaseMetadata = DCM,\n> extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The thread to display.\n */\n thread: ThreadData<TM, CM>;\n\n /**\n * How to show or hide the composer to reply to the thread.\n */\n showComposer?: boolean | \"collapsed\";\n\n /**\n * Whether to show the action to resolve the thread.\n */\n showResolveAction?: boolean;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: CommentProps[\"showActions\"];\n\n /**\n * Whether to show reactions.\n */\n showReactions?: CommentProps[\"showReactions\"];\n\n /**\n * Whether to show the composer's formatting controls.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Add (or change) items to display in a comment's dropdown.\n */\n commentDropdownItems?:\n | ReactNode\n | ((props: PropsWithChildren<{ comment: CommentData<CM> }>) => ReactNode);\n\n /**\n * The maximum number of comments to show.\n *\n * The first and last comments are always shown and by default if some comments\n * are hidden, only the first comment will be shown before the \"show more\" button\n * and after it will be shown all the newest comments to fit the limit set.\n *\n * It's possible to customize this by setting `maxVisibleComments` to an object:\n *\n * @example\n * // Only show the last comment, and all the older ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"oldest\" }} />\n *\n * @example\n * // Show as many old comments as new ones to fit the limit.\n * <Thread maxVisibleComments={{ max: 5, show: \"both\" }} />\n */\n maxVisibleComments?:\n | number\n | { max: number; show: \"oldest\" | \"both\" | \"newest\" };\n\n /**\n * Whether to blur the composer editor when the composer is submitted.\n */\n blurComposerOnSubmit?: ComposerProps[\"blurOnSubmit\"];\n\n /**\n * Whether to indent the comments' content.\n */\n indentCommentContent?: CommentProps[\"indentContent\"];\n\n /**\n * Whether to show deleted comments.\n */\n showDeletedComments?: CommentProps[\"showDeleted\"];\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * The event handler called when changing the resolved status.\n */\n onResolvedChange?: (resolved: boolean) => void;\n\n /**\n * The event handler called when a comment is edited.\n */\n onCommentEdit?: CommentProps[\"onCommentEdit\"];\n\n /**\n * The event handler called when a comment is deleted.\n */\n onCommentDelete?: CommentProps[\"onCommentDelete\"];\n\n /**\n * The event handler called when the thread is deleted.\n * A thread is deleted when all its comments are deleted.\n */\n onThreadDelete?: (thread: ThreadData<TM, CM>) => void;\n\n /**\n * The event handler called when clicking on a comment's author.\n */\n onAuthorClick?: CommentProps[\"onAuthorClick\"];\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: CommentProps[\"onMentionClick\"];\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: ComposerProps[\"onComposerSubmit\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: ComposerProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & ThreadOverrides & CommentOverrides & ComposerOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & ThreadComponents<TM, CM>>;\n}\n\n/**\n * Displays a thread of comments, with a composer to reply\n * to it.\n *\n * @example\n * <>\n * {threads.map((thread) => (\n * <Thread key={thread.id} thread={thread} />\n * ))}\n * </>\n */\nexport const Thread = forwardRef(\n <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n {\n thread,\n indentCommentContent = true,\n showActions = \"hover\",\n showDeletedComments,\n showResolveAction = true,\n showReactions = true,\n showComposer = \"collapsed\",\n showAttachments = true,\n showComposerFormattingControls = true,\n maxVisibleComments,\n commentDropdownItems,\n onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onComposerSubmit,\n blurComposerOnSubmit,\n autoFocus,\n overrides,\n components,\n className,\n ...props\n }: ThreadProps<TM, CM>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const Comment = useStableComponent(components?.Comment, DefaultComment);\n const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n const $ = useOverrides(overrides);\n const [showAllComments, setShowAllComments] = useState(false);\n const firstCommentIndex = useMemo(() => {\n return showDeletedComments\n ? 0\n : thread.comments.findIndex((comment) => comment.body);\n }, [showDeletedComments, thread.comments]);\n const lastCommentIndex = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length - 1\n : findLastIndex(thread.comments, (comment) => Boolean(comment.body));\n }, [showDeletedComments, thread.comments]);\n const commentsCount = useMemo(() => {\n return showDeletedComments\n ? thread.comments.length\n : thread.comments.filter((comment) => comment.body).length;\n }, [showDeletedComments, thread.comments]);\n const hiddenComments = useMemo(() => {\n const maxVisibleCommentsCount =\n typeof maxVisibleComments === \"number\"\n ? maxVisibleComments\n : maxVisibleComments?.max;\n const visibleCommentsShow =\n (typeof maxVisibleComments === \"object\"\n ? maxVisibleComments?.show\n : undefined) ?? \"newest\";\n\n // If we explicitly want to show all comments or there's no limit set,\n // no need to hide any comments.\n if (showAllComments || maxVisibleCommentsCount === undefined) {\n return;\n }\n\n const comments = thread.comments\n .map((comment, index) => ({ comment, index }))\n .filter(({ comment }) => showDeletedComments || comment.body);\n\n // There aren't enough comments so no need to hide any.\n if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) {\n return;\n }\n\n const firstVisibleComment = comments[0]!;\n const lastVisibleComment = comments[comments.length - 1]!;\n\n // Always show the first and last comments even if the limit is set to lower than 2.\n if (maxVisibleCommentsCount <= 2) {\n const firstHiddenCommentIndex =\n comments[1]?.index ?? firstVisibleComment.index;\n const lastHiddenCommentIndex =\n comments[comments.length - 2]?.index ?? lastVisibleComment.index;\n\n return {\n firstIndex: firstHiddenCommentIndex,\n lastIndex: lastHiddenCommentIndex,\n count: comments.slice(1, comments.length - 1).length,\n };\n }\n\n const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2;\n\n // Split the remaining visible comments before, after, or equally.\n const beforeVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? remainingVisibleCommentsCount\n : visibleCommentsShow === \"newest\"\n ? 0\n : Math.floor(remainingVisibleCommentsCount / 2);\n const afterVisibleCommentsCount =\n visibleCommentsShow === \"oldest\"\n ? 0\n : visibleCommentsShow === \"newest\"\n ? remainingVisibleCommentsCount\n : Math.ceil(remainingVisibleCommentsCount / 2);\n\n // The first comment is always visible so `+ 1` to skip it.\n const firstHiddenComment = comments[1 + beforeVisibleCommentsCount];\n // The last comment is always visible so `- 2` to skip it.\n const lastHiddenComment =\n comments[comments.length - 2 - afterVisibleCommentsCount];\n\n // There aren't any comments to hide besides the first and last ones.\n if (\n !firstHiddenComment ||\n !lastHiddenComment ||\n firstHiddenComment.index > lastHiddenComment.index\n ) {\n return;\n }\n\n return {\n firstIndex: firstHiddenComment.index,\n lastIndex: lastHiddenComment.index,\n count: thread.comments\n .slice(firstHiddenComment.index, lastHiddenComment.index + 1)\n .filter((comment) => showDeletedComments || comment.body).length,\n };\n }, [\n maxVisibleComments,\n showAllComments,\n showDeletedComments,\n thread.comments,\n ]);\n const {\n status: subscriptionStatus,\n unreadSince,\n subscribe,\n unsubscribe,\n } = useRoomThreadSubscription(thread.roomId, thread.id);\n const unreadIndex = useMemo(() => {\n // The user is not subscribed to this thread.\n if (subscriptionStatus !== \"subscribed\") {\n return;\n }\n\n // The user hasn't read the thread yet, so all comments are unread.\n if (unreadSince === null) {\n return firstCommentIndex;\n }\n\n // The user has read the thread, so we find the first unread comment.\n const unreadIndex = thread.comments.findIndex(\n (comment) =>\n (showDeletedComments ? true : comment.body) &&\n comment.createdAt > unreadSince\n );\n\n return unreadIndex >= 0 && unreadIndex < thread.comments.length\n ? unreadIndex\n : undefined;\n }, [\n firstCommentIndex,\n showDeletedComments,\n subscriptionStatus,\n thread.comments,\n unreadSince,\n ]);\n const [newIndex, setNewIndex] = useState<number>();\n const newIndicatorIndex = newIndex === undefined ? unreadIndex : newIndex;\n\n useEffect(() => {\n if (unreadIndex) {\n // Keep the \"new\" indicator at the lowest unread index.\n setNewIndex((persistedUnreadIndex) =>\n Math.min(persistedUnreadIndex ?? Infinity, unreadIndex)\n );\n }\n }, [unreadIndex]);\n\n const permissions = useRoomPermissions(thread.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 handleResolvedChange = useCallback(\n (resolved: boolean) => {\n onResolvedChange?.(resolved);\n\n if (resolved) {\n markThreadAsResolved(thread.id);\n } else {\n markThreadAsUnresolved(thread.id);\n }\n },\n [\n markThreadAsResolved,\n markThreadAsUnresolved,\n onResolvedChange,\n thread.id,\n ]\n );\n\n const handleCommentDelete = useCallback(\n (comment: CommentData) => {\n onCommentDelete?.(comment);\n\n const filteredComments = thread.comments.filter(\n (comment) => comment.body\n );\n\n if (filteredComments.length <= 1) {\n onThreadDelete?.(thread);\n }\n },\n [onCommentDelete, onThreadDelete, thread]\n );\n\n const handleSubscribeChange = useCallback(() => {\n if (subscriptionStatus === \"subscribed\") {\n unsubscribe();\n } else {\n subscribe();\n }\n }, [subscriptionStatus, subscribe, unsubscribe]);\n\n let currentCommentIndex = 0;\n\n return (\n <TooltipProvider>\n <div\n className={cn(\n \"lb-root lb-thread\",\n showActions === \"hover\" && \"lb-thread:show-actions-hover\",\n className\n )}\n data-resolved={thread.resolved ? \"\" : undefined}\n data-unread={unreadIndex !== undefined ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={forwardedRef}\n >\n <div className=\"lb-thread-comments\" role=\"feed\">\n {thread.comments.map((comment, index) => {\n const isNonDeletedComment = showDeletedComments || comment.body;\n const isFirstComment = index === firstCommentIndex;\n const isUnread =\n unreadIndex !== undefined && index >= unreadIndex;\n const isHidden =\n hiddenComments &&\n index >= hiddenComments.firstIndex &&\n index <= hiddenComments.lastIndex;\n const isHiddenBecauseDeleted =\n !showDeletedComments && !comment.body;\n const isFirstHiddenComment =\n isHidden && index === hiddenComments.firstIndex;\n\n if (isFirstHiddenComment) {\n return (\n <div\n key={`${comment.id}:show-more`}\n className=\"lb-thread-show-more\"\n >\n <Button\n variant=\"ghost\"\n className=\"lb-thread-show-more-button\"\n onClick={() => setShowAllComments(true)}\n >\n {$.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count)}\n </Button>\n </div>\n );\n }\n\n if (isHidden || isHiddenBecauseDeleted) {\n return null;\n }\n\n if (isNonDeletedComment) {\n currentCommentIndex++;\n }\n\n const children = (\n <Comment\n key={comment.id}\n tabIndex={0}\n aria-posinset={currentCommentIndex}\n aria-setsize={commentsCount}\n overrides={overrides}\n className=\"lb-thread-comment\"\n data-unread={isUnread ? \"\" : undefined}\n comment={comment}\n indentContent={indentCommentContent}\n showDeleted={showDeletedComments}\n showActions={showActions}\n showReactions={showReactions}\n showAttachments={showAttachments}\n showComposerFormattingControls={\n showComposerFormattingControls\n }\n onCommentEdit={onCommentEdit}\n onCommentDelete={handleCommentDelete}\n onAuthorClick={onAuthorClick}\n onMentionClick={onMentionClick}\n onAttachmentClick={onAttachmentClick}\n components={components}\n actionsClassName={\n isFirstComment ? \"lb-thread-actions\" : undefined\n }\n actions={\n isFirstComment && showResolveAction ? (\n <Tooltip\n content={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n >\n <TogglePrimitive.Root\n pressed={thread.resolved}\n onPressedChange={handleResolvedChange}\n asChild\n >\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={\n thread.resolved\n ? $.THREAD_UNRESOLVE\n : $.THREAD_RESOLVE\n }\n icon={\n thread.resolved ? (\n <CheckCircleFillIcon />\n ) : (\n <CheckCircleIcon />\n )\n }\n disabled={!canComment}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n ) : null\n }\n internalDropdownItems={\n isFirstComment ? (\n <DefaultComment.DropdownItem\n onSelect={handleSubscribeChange}\n icon={\n subscriptionStatus === \"subscribed\" ? (\n <BellCrossedIcon />\n ) : (\n <BellIcon />\n )\n }\n >\n {subscriptionStatus === \"subscribed\"\n ? $.THREAD_UNSUBSCRIBE\n : $.THREAD_SUBSCRIBE}\n </DefaultComment.DropdownItem>\n ) : undefined\n }\n dropdownItems={\n commentDropdownItems as CommentProps[\"dropdownItems\"]\n }\n />\n );\n\n return index === newIndicatorIndex &&\n newIndicatorIndex !== firstCommentIndex &&\n newIndicatorIndex <= lastCommentIndex ? (\n <Fragment key={comment.id}>\n <div\n className=\"lb-thread-new-indicator\"\n aria-label={$.THREAD_NEW_INDICATOR_DESCRIPTION}\n >\n <span className=\"lb-thread-new-indicator-label\">\n <ArrowDownIcon className=\"lb-thread-new-indicator-label-icon\" />\n {$.THREAD_NEW_INDICATOR}\n </span>\n </div>\n {children}\n </Fragment>\n ) : (\n children\n );\n })}\n </div>\n {unreadIndex !== undefined && (\n <MarkThreadAsReadMarker thread={thread} />\n )}\n {showComposer && (\n <Composer\n className=\"lb-thread-composer\"\n threadId={thread.id}\n defaultCollapsed={showComposer === \"collapsed\" ? true : undefined}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n onComposerSubmit={onComposerSubmit}\n blurOnSubmit={blurComposerOnSubmit}\n overrides={{\n COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n ...overrides,\n }}\n roomId={thread.roomId}\n autoFocus={autoFocus}\n />\n )}\n </div>\n </TooltipProvider>\n );\n }\n) as <TM extends BaseMetadata = DTM, CM extends BaseMetadata = DCM>(\n props: ThreadProps<TM, CM> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2DA;AACE;AACA;AACA;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAAA;AAEW;AACX;AAGF;AACE;AAAC;AAAA;AACC;AACmB;AACR;AACc;AAAA;AAG/B;AAsKO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAEqE;AAEvE;AACE;AAEsD;AAExD;AACE;AAIA;AAOA;AACE;AAAA;AAGF;AAKA;AACE;AAAA;AAGF;AACA;AAGA;AACE;AAEA;AAGA;AAAO;AACO;AACD;AACmC;AAChD;AAGF;AAGA;AAMA;AAQA;AAEA;AAIA;AAKE;AAAA;AAGF;AAAO;AAC0B;AACF;AAG+B;AAC9D;AACC;AACD;AACA;AACA;AACO;AAET;AAAM;AACI;AACR;AACA;AACA;AAEF;AAEE;AACE;AAAA;AAIF;AACE;AAAO;AAIT;AAAoC;AAGZ;AAGxB;AAEI;AACH;AACD;AACA;AACA;AACO;AACP;AAEF;AACA;AAEA;AACE;AAEE;AAAA;AACwD;AACxD;AACF;AAGF;AACA;AAMA;AACE;AAAsB;AAGxB;AAA6B;AAEzB;AAEA;AACE;AAA8B;AAE9B;AAAgC;AAClC;AACF;AACA;AACE;AACA;AACA;AACO;AACT;AAGF;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AACE;AACE;AAAY;AAEZ;AAAU;AACZ;AAGF;AAEA;AAEI;AAAC;AAAA;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAEI;AACA;AACA;AAEA;AAIA;AAEA;AAGA;AACE;AACE;AAAC;AAAA;AAEW;AAEV;AAAC;AAAA;AACS;AACE;AAC4B;AAEW;AAAA;AACnD;AAAA;AATkB;AAUpB;AAIJ;AACE;AAAO;AAGT;AACE;AAAA;AAGF;AACE;AAAC;AAAA;AAEW;AACK;AACD;AACd;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AACA;AAEyC;AAIrC;AAAC;AAAA;AAIS;AAGR;AAAiB;AAAhB;AACiB;AACC;AACV;AAEP;AAAC;AAAA;AACW;AACD;AAID;AAMa;AAGV;AAAA;AACb;AAAA;AACF;AAAA;AAEA;AAIF;AAAgB;AAAf;AACW;AAKI;AAMR;AAAA;AAEN;AAGJ;AAAA;AA/EW;AAoFjB;AAII;AAAA;AAAC;AAAA;AACW;AACI;AAGZ;AAA8D;AAC3D;AACL;AAAA;AACF;AACC;AAGH;AAGN;AAE0C;AAGxC;AAAC;AAAA;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACc;AACH;AACe;AACP;AACd;AACL;AACe;AACf;AAAA;AACF;AAAA;AAAA;AAGN;AAGN;;"}
|
|
@@ -11,7 +11,6 @@ var contexts = require('../../primitives/AiComposer/contexts.cjs');
|
|
|
11
11
|
var cn = require('../../utils/cn.cjs');
|
|
12
12
|
var Button = require('./Button.cjs');
|
|
13
13
|
var Tooltip = require('./Tooltip.cjs');
|
|
14
|
-
var TooltipPrimitive = require('@radix-ui/react-tooltip');
|
|
15
14
|
|
|
16
15
|
function AiComposerAction({
|
|
17
16
|
overrides: overrides$1
|
|
@@ -78,7 +77,7 @@ const AiComposer = react.forwardRef(
|
|
|
78
77
|
},
|
|
79
78
|
[onComposerSubmit, sendAiMessage, onComposerSubmitted]
|
|
80
79
|
);
|
|
81
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
80
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Tooltip.TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
82
81
|
index.AiComposerForm,
|
|
83
82
|
{
|
|
84
83
|
className: cn.cn(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiComposer.cjs","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["overrides","useAiComposer","useOverrides","useCallback","jsx","ShortcutTooltip","AiComposerPrimitive.Abort","Button","StopIcon","AiComposerPrimitive.Submit","SendIcon","forwardRef","useSendAiMessage","TooltipProvider","AiComposerPrimitive.Form","cn","jsxs","AiComposerPrimitive.Editor"],"mappings":"
|
|
1
|
+
{"version":3,"file":"AiComposer.cjs","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["overrides","useAiComposer","useOverrides","useCallback","jsx","ShortcutTooltip","AiComposerPrimitive.Abort","Button","StopIcon","AiComposerPrimitive.Submit","SendIcon","forwardRef","useSendAiMessage","TooltipProvider","AiComposerPrimitive.Form","cn","jsxs","AiComposerPrimitive.Editor"],"mappings":"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"}
|
|
@@ -8,8 +8,7 @@ import { AiComposerAbort, AiComposerSubmit, AiComposerForm, Editor as AiComposer
|
|
|
8
8
|
import { useAiComposer } from '../../primitives/AiComposer/contexts.js';
|
|
9
9
|
import { cn } from '../../utils/cn.js';
|
|
10
10
|
import { Button } from './Button.js';
|
|
11
|
-
import { ShortcutTooltip } from './Tooltip.js';
|
|
12
|
-
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
|
11
|
+
import { ShortcutTooltip, TooltipProvider } from './Tooltip.js';
|
|
13
12
|
|
|
14
13
|
function AiComposerAction({
|
|
15
14
|
overrides
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiComposer.js","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["AiComposerPrimitive.Abort","AiComposerPrimitive.Submit","AiComposerPrimitive.Form","AiComposerPrimitive.Editor"],"mappings":"
|
|
1
|
+
{"version":3,"file":"AiComposer.js","sources":["../../../src/components/internal/AiComposer.tsx"],"sourcesContent":["import {\n type AiChatMessage,\n type CopilotId,\n type MessageId,\n} from \"@liveblocks/core\";\nimport { useSendAiMessage } from \"@liveblocks/react\";\nimport {\n type ComponentProps,\n type FormEvent,\n forwardRef,\n type SyntheticEvent,\n useCallback,\n} from \"react\";\n\nimport { SendIcon } from \"../../icons/Send\";\nimport { StopIcon } from \"../../icons/Stop\";\nimport {\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as AiComposerPrimitive from \"../../primitives/AiComposer\";\nimport { useAiComposer } from \"../../primitives/AiComposer/contexts\";\nimport type {\n AiComposerEditorProps,\n AiComposerFormProps,\n AiComposerSubmitMessage,\n} from \"../../primitives/AiComposer/types\";\nimport { cn } from \"../../utils/cn\";\nimport { Button } from \"./Button\";\nimport { ShortcutTooltip, TooltipProvider } from \"./Tooltip\";\n\n/* -------------------------------------------------------------------------------------------------\n * AiComposer\n * -----------------------------------------------------------------------------------------------*/\nexport interface AiComposerProps\n extends Omit<ComponentProps<\"form\">, \"defaultValue\"> {\n /**\n * The composer's initial value.\n */\n defaultValue?: string;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: (\n message: AiComposerSubmitMessage,\n event: FormEvent<HTMLFormElement>\n ) => void;\n\n /**\n * The event handler called after the composer is submitted.\n *\n * @internal This API will change, and is not considered stable. DO NOT RELY on it.\n */\n onComposerSubmitted?: (message: AiChatMessage) => void;\n\n /**\n * Whether the composer is disabled.\n */\n disabled?: AiComposerFormProps[\"disabled\"];\n\n /**\n * Whether to focus the composer on mount.\n */\n autoFocus?: AiComposerEditorProps[\"autoFocus\"];\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiComposerOverrides>;\n\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: CopilotId;\n\n /**\n * The time, in milliseconds, before an AI response will timeout.\n */\n responseTimeout?: number;\n\n /**\n * @internal\n */\n branchId?: MessageId;\n\n /**\n * @internal\n */\n stream?: boolean;\n}\n\nfunction AiComposerAction({\n overrides,\n}: {\n overrides?: AiComposerProps[\"overrides\"];\n}) {\n const { canAbort } = useAiComposer();\n const $ = useOverrides(overrides);\n\n const preventDefault = useCallback((event: SyntheticEvent) => {\n event.preventDefault();\n }, []);\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n return canAbort ? (\n <ShortcutTooltip content={$.AI_COMPOSER_ABORT}>\n <AiComposerPrimitive.Abort asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"secondary\"\n aria-label={$.AI_COMPOSER_ABORT}\n icon={<StopIcon />}\n />\n </AiComposerPrimitive.Abort>\n </ShortcutTooltip>\n ) : (\n <ShortcutTooltip content={$.AI_COMPOSER_SEND} shortcut=\"Enter\">\n <AiComposerPrimitive.Submit asChild>\n <Button\n onPointerDown={preventDefault}\n onClick={stopPropagation}\n className=\"lb-ai-composer-action\"\n variant=\"primary\"\n aria-label={$.AI_COMPOSER_SEND}\n icon={<SendIcon />}\n />\n </AiComposerPrimitive.Submit>\n </ShortcutTooltip>\n );\n}\n\nexport const AiComposer = forwardRef<HTMLFormElement, AiComposerProps>(\n (\n {\n defaultValue,\n onComposerSubmit,\n disabled,\n autoFocus,\n overrides,\n className,\n chatId,\n branchId,\n copilotId,\n responseTimeout,\n stream = true,\n onComposerSubmitted,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const sendAiMessage = useSendAiMessage(chatId, {\n stream,\n copilotId,\n timeout: responseTimeout,\n });\n\n const handleComposerSubmit = useCallback(\n (message: AiComposerSubmitMessage, event: FormEvent<HTMLFormElement>) => {\n onComposerSubmit?.(message, event);\n\n if (event.isDefaultPrevented()) return;\n\n const newMessage = sendAiMessage(message.text);\n\n onComposerSubmitted?.(newMessage);\n },\n [onComposerSubmit, sendAiMessage, onComposerSubmitted]\n );\n\n return (\n <TooltipProvider>\n <AiComposerPrimitive.Form\n className={cn(\n \"lb-root lb-ai-composer lb-ai-composer-form\",\n className\n )}\n dir={$.dir}\n {...props}\n disabled={disabled}\n ref={forwardedRef}\n onComposerSubmit={handleComposerSubmit}\n chatId={chatId}\n branchId={branchId}\n >\n <div className=\"lb-ai-composer-editor-container\">\n <AiComposerPrimitive.Editor\n autoFocus={autoFocus}\n className=\"lb-ai-composer-editor\"\n placeholder={$.AI_COMPOSER_PLACEHOLDER}\n defaultValue={defaultValue}\n />\n\n <div className=\"lb-ai-composer-footer\">\n <div className=\"lb-ai-composer-editor-actions\">\n {/* No actions for now but it makes sense to keep the DOM structure */}\n </div>\n\n <div className=\"lb-ai-composer-actions\">\n <AiComposerAction overrides={overrides} />\n </div>\n </div>\n </div>\n </AiComposerPrimitive.Form>\n </TooltipProvider>\n );\n }\n);\n"],"names":["AiComposerPrimitive.Abort","AiComposerPrimitive.Submit","AiComposerPrimitive.Form","AiComposerPrimitive.Editor"],"mappings":"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"}
|
|
@@ -24,24 +24,21 @@ function AvatarLayout({
|
|
|
24
24
|
() => !isLoading && fallback && !name ? getInitials.getInitials(fallback) : void 0,
|
|
25
25
|
[isLoading, fallback, name]
|
|
26
26
|
);
|
|
27
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
27
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28
28
|
"div",
|
|
29
29
|
{
|
|
30
30
|
className: cn.cn("lb-avatar", className),
|
|
31
31
|
"data-loading": isLoading ? "" : void 0,
|
|
32
32
|
...props,
|
|
33
|
-
children:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
) : null
|
|
44
|
-
]
|
|
33
|
+
children: src ? /* @__PURE__ */ jsxRuntime.jsx("img", { className: "lb-avatar-image", src, alt: name }) : nameInitials ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-avatar-fallback", "aria-hidden": true, children: nameInitials }) : fallbackInitials ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
34
|
+
"span",
|
|
35
|
+
{
|
|
36
|
+
className: "lb-avatar-fallback",
|
|
37
|
+
"aria-label": fallback,
|
|
38
|
+
title: fallback,
|
|
39
|
+
children: fallbackInitials
|
|
40
|
+
}
|
|
41
|
+
) : null
|
|
45
42
|
}
|
|
46
43
|
);
|
|
47
44
|
}
|