{"version":3,"file":"Thread.mjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type { BaseMetadata, CommentData, ThreadData } from \"@liveblocks/core\";\nimport { useRoomContextBundle } from \"@liveblocks/react\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n  ComponentPropsWithoutRef,\n  ForwardedRef,\n  RefAttributes,\n  SyntheticEvent,\n} from \"react\";\nimport React, {\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 type { ThreadMetadata } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { findLastIndex } from \"../utils/find-last-index\";\nimport type { CommentProps } from \"./Comment\";\nimport { Comment } from \"./Comment\";\nimport { Composer } from \"./Composer\";\nimport { Button } from \"./internal/Button\";\nimport { Tooltip, TooltipProvider } from \"./internal/Tooltip\";\n\nexport interface ThreadProps<\n  TThreadMetadata extends BaseMetadata = ThreadMetadata,\n> extends ComponentPropsWithoutRef<\"div\"> {\n  /**\n   * The thread to display.\n   */\n  thread: ThreadData<TThreadMetadata>;\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 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   * 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<TThreadMetadata>) => 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   * 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  <TThreadMetadata extends BaseMetadata = ThreadMetadata>(\n    {\n      thread,\n      indentCommentContent = true,\n      showActions = \"hover\",\n      showDeletedComments,\n      showResolveAction = true,\n      showReactions = true,\n      showComposer = \"collapsed\",\n      onResolvedChange,\n      onCommentEdit,\n      onCommentDelete,\n      onThreadDelete,\n      onAuthorClick,\n      onMentionClick,\n      overrides,\n      className,\n      ...props\n    }: ThreadProps<TThreadMetadata>,\n    forwardedRef: ForwardedRef<HTMLDivElement>\n  ) => {\n    const { useEditThreadMetadata, useThreadSubscription } =\n      useRoomContextBundle();\n    const editThreadMetadata = useEditThreadMetadata();\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        editThreadMetadata({ threadId: thread.id, metadata: { resolved } });\n      },\n      [editThreadMetadata, onResolvedChange, thread.id]\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={\n            (thread.metadata as ThreadMetadata).resolved ? \"\" : undefined\n          }\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                  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                  onCommentEdit={onCommentEdit}\n                  onCommentDelete={handleCommentDelete}\n                  onAuthorClick={onAuthorClick}\n                  onMentionClick={onMentionClick}\n                  markThreadAsReadWhenVisible={\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.metadata as ThreadMetadata).resolved\n                            ? $.THREAD_UNRESOLVE\n                            : $.THREAD_RESOLVE\n                        }\n                      >\n                        <TogglePrimitive.Root\n                          pressed={(thread.metadata as ThreadMetadata).resolved}\n                          onPressedChange={handleResolvedChange}\n                          asChild\n                        >\n                          <Button\n                            className=\"lb-comment-action\"\n                            onClick={stopPropagation}\n                            aria-label={\n                              (thread.metadata as ThreadMetadata).resolved\n                                ? $.THREAD_UNRESOLVE\n                                : $.THREAD_RESOLVE\n                            }\n                          >\n                            {(thread.metadata as ThreadMetadata).resolved ? (\n                              <ResolvedIcon className=\"lb-button-icon\" />\n                            ) : (\n                              <ResolveIcon className=\"lb-button-icon\" />\n                            )}\n                          </Button>\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              overrides={{\n                COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER,\n                COMPOSER_SEND: $.THREAD_COMPOSER_SEND,\n              }}\n            />\n          )}\n        </div>\n      </TooltipProvider>\n    );\n  }\n) as <TThreadMetadata extends BaseMetadata = ThreadMetadata>(\n  props: ThreadProps<TThreadMetadata> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA+HO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AAEA;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;AAAkE;AACpE;AACgD;AAGlD;AAA4B;AAExB;AAEA;AAAyC;AAClB;AAGvB;AACE;AAAuB;AACzB;AACF;AACwC;AAG1C;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AAEsD;AAER;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACiB;AACjB;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AAC8C;AAC5B;AACV;AAEN;AACW;AACD;AAID;AAIL;AAAuB;AAEvB;AAAsB;AAK7B;AAKV;AAGG;AAAsB;AACpB;AACW;AACI;AAEb;AAAe;AACb;AAAwB;AAO/B;AAKH;AACW;AACO;AACuC;AAC7C;AACe;AACP;AACnB;AAIR;AAGN;;"}