UNPKG

@liveblocks/react-ui

Version:

A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.

345 lines (338 loc) 14.4 kB
"use client"; 'use strict'; var jsxRuntime = require('react/jsx-runtime'); var core = require('@liveblocks/core'); var _private = require('@liveblocks/react/_private'); var TogglePrimitive = require('@radix-ui/react-toggle'); var react = require('react'); var ArrowDown = require('../icons/ArrowDown.cjs'); var Bell = require('../icons/Bell.cjs'); var BellCrossed = require('../icons/BellCrossed.cjs'); var CheckCircle = require('../icons/CheckCircle.cjs'); var CheckCircleFill = require('../icons/CheckCircleFill.cjs'); var overrides = require('../overrides.cjs'); var cn = require('../utils/cn.cjs'); var Comment = require('./Comment.cjs'); var Composer = require('./Composer.cjs'); var Button = require('./internal/Button.cjs'); var Tooltip = require('./internal/Tooltip.cjs'); var TooltipPrimitive = require('@radix-ui/react-tooltip'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var TogglePrimitive__namespace = /*#__PURE__*/_interopNamespaceDefault(TogglePrimitive); const Thread = react.forwardRef( ({ thread, indentCommentContent = true, showActions = "hover", showDeletedComments, showResolveAction = true, showReactions = true, showComposer = "collapsed", showAttachments = true, showComposerFormattingControls = true, maxVisibleComments, commentDropdownItems, onResolvedChange, onCommentEdit, onCommentDelete, onThreadDelete, onAuthorClick, onMentionClick, onAttachmentClick, onComposerSubmit, blurComposerOnSubmit, overrides: overrides$1, components, className, ...props }, forwardedRef) => { const markThreadAsResolved = _private.useMarkRoomThreadAsResolved(thread.roomId); const markThreadAsUnresolved = _private.useMarkRoomThreadAsUnresolved(thread.roomId); const $ = overrides.useOverrides(overrides$1); const [showAllComments, setShowAllComments] = react.useState(false); const firstCommentIndex = react.useMemo(() => { return showDeletedComments ? 0 : thread.comments.findIndex((comment) => comment.body); }, [showDeletedComments, thread.comments]); const lastCommentIndex = react.useMemo(() => { return showDeletedComments ? thread.comments.length - 1 : core.findLastIndex(thread.comments, (comment) => Boolean(comment.body)); }, [showDeletedComments, thread.comments]); const hiddenComments = react.useMemo(() => { const maxVisibleCommentsCount = typeof maxVisibleComments === "number" ? maxVisibleComments : maxVisibleComments?.max; const visibleCommentsShow = (typeof maxVisibleComments === "object" ? maxVisibleComments?.show : void 0) ?? "newest"; if (showAllComments || maxVisibleCommentsCount === void 0) { return; } const comments = thread.comments.map((comment, index) => ({ comment, index })).filter(({ comment }) => showDeletedComments || comment.body); if (comments.length <= Math.max(maxVisibleCommentsCount, 2)) { return; } const firstVisibleComment = comments[0]; const lastVisibleComment = comments[comments.length - 1]; if (maxVisibleCommentsCount <= 2) { const firstHiddenCommentIndex = comments[1]?.index ?? firstVisibleComment.index; const lastHiddenCommentIndex = comments[comments.length - 2]?.index ?? lastVisibleComment.index; return { firstIndex: firstHiddenCommentIndex, lastIndex: lastHiddenCommentIndex, count: comments.slice(1, comments.length - 1).length }; } const remainingVisibleCommentsCount = maxVisibleCommentsCount - 2; const beforeVisibleCommentsCount = visibleCommentsShow === "oldest" ? remainingVisibleCommentsCount : visibleCommentsShow === "newest" ? 0 : Math.floor(remainingVisibleCommentsCount / 2); const afterVisibleCommentsCount = visibleCommentsShow === "oldest" ? 0 : visibleCommentsShow === "newest" ? remainingVisibleCommentsCount : Math.ceil(remainingVisibleCommentsCount / 2); const firstHiddenComment = comments[1 + beforeVisibleCommentsCount]; const lastHiddenComment = comments[comments.length - 2 - afterVisibleCommentsCount]; if (!firstHiddenComment || !lastHiddenComment || firstHiddenComment.index > lastHiddenComment.index) { return; } return { firstIndex: firstHiddenComment.index, lastIndex: lastHiddenComment.index, count: thread.comments.slice(firstHiddenComment.index, lastHiddenComment.index + 1).filter((comment) => showDeletedComments || comment.body).length }; }, [ maxVisibleComments, showAllComments, showDeletedComments, thread.comments ]); const { status: subscriptionStatus, unreadSince, subscribe, unsubscribe } = _private.useRoomThreadSubscription(thread.roomId, thread.id); const unreadIndex = react.useMemo(() => { if (subscriptionStatus !== "subscribed") { return; } if (unreadSince === null) { return firstCommentIndex; } const unreadIndex2 = thread.comments.findIndex( (comment) => (showDeletedComments ? true : comment.body) && comment.createdAt > unreadSince ); return unreadIndex2 >= 0 && unreadIndex2 < thread.comments.length ? unreadIndex2 : void 0; }, [ firstCommentIndex, showDeletedComments, subscriptionStatus, thread.comments, unreadSince ]); const [newIndex, setNewIndex] = react.useState(); const newIndicatorIndex = newIndex === void 0 ? unreadIndex : newIndex; react.useEffect(() => { if (unreadIndex) { setNewIndex( (persistedUnreadIndex) => Math.min(persistedUnreadIndex ?? Infinity, unreadIndex) ); } }, [unreadIndex]); const permissions = _private.useRoomPermissions(thread.roomId); const canComment = permissions.size > 0 ? permissions.has(core.Permission.CommentsWrite) || permissions.has(core.Permission.Write) : true; const stopPropagation = react.useCallback((event) => { event.stopPropagation(); }, []); const handleResolvedChange = react.useCallback( (resolved) => { onResolvedChange?.(resolved); if (resolved) { markThreadAsResolved(thread.id); } else { markThreadAsUnresolved(thread.id); } }, [ markThreadAsResolved, markThreadAsUnresolved, onResolvedChange, thread.id ] ); const handleCommentDelete = react.useCallback( (comment) => { onCommentDelete?.(comment); const filteredComments = thread.comments.filter( (comment2) => comment2.body ); if (filteredComments.length <= 1) { onThreadDelete?.(thread); } }, [onCommentDelete, onThreadDelete, thread] ); const handleSubscribeChange = react.useCallback(() => { if (subscriptionStatus === "subscribed") { unsubscribe(); } else { subscribe(); } }, [subscriptionStatus, subscribe, unsubscribe]); return /* @__PURE__ */ jsxRuntime.jsx(TooltipPrimitive.TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs( "div", { className: cn.cn( "lb-root lb-thread", showActions === "hover" && "lb-thread:show-actions-hover", className ), "data-resolved": thread.resolved ? "" : void 0, "data-unread": unreadIndex !== void 0 ? "" : void 0, dir: $.dir, ...props, ref: forwardedRef, children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-thread-comments", children: thread.comments.map((comment, index) => { const isFirstComment = index === firstCommentIndex; const isUnread = unreadIndex !== void 0 && index >= unreadIndex; const isHidden = hiddenComments && index >= hiddenComments.firstIndex && index <= hiddenComments.lastIndex; const isFirstHiddenComment = isHidden && index === hiddenComments.firstIndex; if (isFirstHiddenComment) { return /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "lb-thread-show-more", children: /* @__PURE__ */ jsxRuntime.jsx( Button.Button, { variant: "ghost", className: "lb-thread-show-more-button", onClick: () => setShowAllComments(true), children: $.THREAD_SHOW_MORE_COMMENTS(hiddenComments.count) } ) }, `${comment.id}-show-more` ); } if (isHidden) { return null; } const children = /* @__PURE__ */ jsxRuntime.jsx( Comment.Comment, { overrides: overrides$1, className: "lb-thread-comment", "data-unread": isUnread ? "" : void 0, comment, indentContent: indentCommentContent, showDeleted: showDeletedComments, showActions, showReactions, showAttachments, showComposerFormattingControls, onCommentEdit, onCommentDelete: handleCommentDelete, onAuthorClick, onMentionClick, onAttachmentClick, components, autoMarkReadThreadId: index === lastCommentIndex && isUnread ? thread.id : void 0, actionsClassName: isFirstComment ? "lb-thread-actions" : void 0, actions: isFirstComment && showResolveAction ? /* @__PURE__ */ jsxRuntime.jsx( Tooltip.Tooltip, { content: thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE, children: /* @__PURE__ */ jsxRuntime.jsx( TogglePrimitive__namespace.Root, { pressed: thread.resolved, onPressedChange: handleResolvedChange, asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx( Button.Button, { className: "lb-comment-action", onClick: stopPropagation, "aria-label": thread.resolved ? $.THREAD_UNRESOLVE : $.THREAD_RESOLVE, icon: thread.resolved ? /* @__PURE__ */ jsxRuntime.jsx(CheckCircleFill.CheckCircleFillIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(CheckCircle.CheckCircleIcon, {}), disabled: !canComment } ) } ) } ) : null, dropdownItems: ({ children: children2 }) => { const threadDropdownItems = isFirstComment ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx( Comment.Comment.DropdownItem, { onSelect: handleSubscribeChange, icon: subscriptionStatus === "subscribed" ? /* @__PURE__ */ jsxRuntime.jsx(BellCrossed.BellCrossedIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(Bell.BellIcon, {}), children: subscriptionStatus === "subscribed" ? $.THREAD_UNSUBSCRIBE : $.THREAD_SUBSCRIBE } ) }) : null; if (typeof commentDropdownItems === "function") { return commentDropdownItems({ children: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ threadDropdownItems, children2 ] }), comment }); } return threadDropdownItems || commentDropdownItems || children2 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ threadDropdownItems, children2, commentDropdownItems ] }) : null; } }, comment.id ); return index === newIndicatorIndex && newIndicatorIndex !== firstCommentIndex && newIndicatorIndex <= lastCommentIndex ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx( "div", { className: "lb-thread-new-indicator", "aria-label": $.THREAD_NEW_INDICATOR_DESCRIPTION, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lb-thread-new-indicator-label", children: [ /* @__PURE__ */ jsxRuntime.jsx(ArrowDown.ArrowDownIcon, { className: "lb-thread-new-indicator-label-icon" }), $.THREAD_NEW_INDICATOR ] }) } ), children ] }, comment.id) : children; }) }), showComposer && /* @__PURE__ */ jsxRuntime.jsx( Composer.Composer, { className: "lb-thread-composer", threadId: thread.id, defaultCollapsed: showComposer === "collapsed" ? true : void 0, showAttachments, showFormattingControls: showComposerFormattingControls, onComposerSubmit, blurOnSubmit: blurComposerOnSubmit, overrides: { COMPOSER_PLACEHOLDER: $.THREAD_COMPOSER_PLACEHOLDER, COMPOSER_SEND: $.THREAD_COMPOSER_SEND, ...overrides$1 }, roomId: thread.roomId } ) ] } ) }); } ); exports.Thread = Thread; //# sourceMappingURL=Thread.cjs.map