{"version":3,"file":"Thread.cjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n  BaseMetadata,\n  CommentData,\n  DM,\n  ThreadData,\n} from \"@liveblocks/core\";\nimport { useThreadSubscription } from \"@liveblocks/react\";\nimport {\n  useMarkRoomThreadAsResolved,\n  useMarkRoomThreadAsUnresolved,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n  ComponentPropsWithoutRef,\n  ForwardedRef,\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 { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { ResolveIcon } from \"../icons/Resolve\";\nimport { ResolvedIcon } from \"../icons/Resolved\";\nimport type {\n  CommentOverrides,\n  ComposerOverrides,\n  GlobalOverrides,\n  ThreadOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { classNames } from \"../utils/class-names\";\nimport { findLastIndex } from \"../utils/find-last-index\";\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<M extends BaseMetadata = DM>\n  extends ComponentPropsWithoutRef<\"div\"> {\n  /**\n   * The thread to display.\n   */\n  thread: ThreadData<M>;\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   * 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<M>) => 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/**\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  <M extends BaseMetadata = DM>(\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      onResolvedChange,\n      onCommentEdit,\n      onCommentDelete,\n      onThreadDelete,\n      onAuthorClick,\n      onMentionClick,\n      onAttachmentClick,\n      onComposerSubmit,\n      overrides,\n      className,\n      ...props\n    }: ThreadProps<M>,\n    forwardedRef: ForwardedRef<HTMLDivElement>\n  ) => {\n    const markThreadAsResolved = useMarkRoomThreadAsResolved(thread.roomId);\n    const markThreadAsUnresolved = useMarkRoomThreadAsUnresolved(thread.roomId);\n    const $ = useOverrides(overrides);\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) => comment.body);\n    }, [showDeletedComments, thread.comments]);\n    const { status: subscriptionStatus, unreadSince } = useThreadSubscription(\n      thread.id\n    );\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 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    return (\n      <TooltipProvider>\n        <div\n          className={classNames(\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\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                  autoMarkReadThreadId={\n                    index === lastCommentIndex && isUnread\n                      ? thread.id\n                      : undefined\n                  }\n                  additionalActionsClassName={\n                    isFirstComment ? \"lb-thread-actions\" : undefined\n                  }\n                  additionalActions={\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                                <ResolvedIcon />\n                              ) : (\n                                <ResolveIcon />\n                              )\n                            }\n                          />\n                        </TogglePrimitive.Root>\n                      </Tooltip>\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              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 <M extends BaseMetadata = DM>(\n  props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2JO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACE;AAEuD;AAEzD;AACE;AAE4D;AAE9D;AAAoD;AAC3C;AAET;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;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;AACG;AACE;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEL;AAAC;AAAc;AAEX;AACA;AAGA;AACG;AAEC;AACU;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AAGA;AACiB;AACjB;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;AACV;AAEN;AACW;AACD;AAID;AAMS;AAGnB;AACF;AAEA;AAKV;AAGG;AACC;AAAC;AACW;AACI;AAEb;AAAe;AACd;AAAC;AAAwB;AAAqC;AAC3D;AAAA;AACL;AACF;AACC;AAAA;AAGH;AAEH;AACH;AAEG;AACW;AACO;AACuC;AACxD;AACwB;AACxB;AACW;AACe;AACP;AACd;AACL;AACe;AACjB;AAAA;AAEJ;AACF;AAGN;;"}