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.

683 lines (676 loc) 25.7 kB
"use client"; 'use strict'; var jsxRuntime = require('react/jsx-runtime'); var _private = require('@liveblocks/react/_private'); var TogglePrimitive = require('@radix-ui/react-toggle'); var react = require('react'); var Check = require('../icons/Check.cjs'); var Cross = require('../icons/Cross.cjs'); var Delete = require('../icons/Delete.cjs'); var Edit = require('../icons/Edit.cjs'); var Ellipsis = require('../icons/Ellipsis.cjs'); var EmojiPlus = require('../icons/EmojiPlus.cjs'); var overrides = require('../overrides.cjs'); var index = require('../primitives/Comment/index.cjs'); var index$1 = require('../primitives/Composer/index.cjs'); var Timestamp = require('../primitives/Timestamp.cjs'); var shared = require('../shared.cjs'); var mentions = require('../slate/plugins/mentions.cjs'); var classNames = require('../utils/class-names.cjs'); var download = require('../utils/download.cjs'); var useRefs = require('../utils/use-refs.cjs'); var useVisible = require('../utils/use-visible.cjs'); var useWindowFocus = require('../utils/use-window-focus.cjs'); var Composer = require('./Composer.cjs'); var Attachment = require('./internal/Attachment.cjs'); var Avatar = require('./internal/Avatar.cjs'); var Button = require('./internal/Button.cjs'); var Dropdown = require('./internal/Dropdown.cjs'); var Emoji = require('./internal/Emoji.cjs'); var EmojiPicker = require('./internal/EmojiPicker.cjs'); var List = require('./internal/List.cjs'); var Tooltip = require('./internal/Tooltip.cjs'); var User = require('./internal/User.cjs'); var TooltipPrimitive = require('@radix-ui/react-tooltip'); var PopoverPrimitive = require('@radix-ui/react-popover'); var DropdownMenuPrimitive = require('@radix-ui/react-dropdown-menu'); 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 REACTIONS_TRUNCATE = 5; function CommentMention({ userId, className, ...props }) { const currentId = shared.useCurrentUserId(); return /* @__PURE__ */ jsxRuntime.jsxs(index.Mention, { className: classNames.classNames("lb-comment-mention", className), "data-self": userId === currentId ? "" : void 0, ...props, children: [ mentions.MENTION_CHARACTER, /* @__PURE__ */ jsxRuntime.jsx(User.User, { userId }) ] }); } function CommentLink({ href, children, className, ...props }) { return /* @__PURE__ */ jsxRuntime.jsx(index.Link, { className: classNames.classNames("lb-comment-link", className), href, ...props, children }); } function CommentNonInteractiveLink({ href: _href, children, className, ...props }) { return /* @__PURE__ */ jsxRuntime.jsx("span", { className: classNames.classNames("lb-comment-link", className), ...props, children }); } const CommentReactionButton = react.forwardRef(({ reaction, overrides: overrides$1, className, ...props }, forwardedRef) => { const $ = overrides.useOverrides(overrides$1); return /* @__PURE__ */ jsxRuntime.jsxs(Button.CustomButton, { className: classNames.classNames("lb-comment-reaction", className), variant: "outline", "aria-label": $.COMMENT_REACTION_DESCRIPTION( reaction.emoji, reaction.users.length ), ...props, ref: forwardedRef, children: [ /* @__PURE__ */ jsxRuntime.jsx(Emoji.Emoji, { className: "lb-comment-reaction-emoji", emoji: reaction.emoji }), /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-comment-reaction-count", children: reaction.users.length }) ] }); }); const CommentReaction = react.forwardRef(({ comment, reaction, overrides: overrides$1, disabled, ...props }, forwardedRef) => { const addReaction = _private.useAddRoomCommentReaction(comment.roomId); const removeReaction = _private.useRemoveRoomCommentReaction(comment.roomId); const currentId = shared.useCurrentUserId(); const isActive = react.useMemo(() => { return reaction.users.some((users) => users.id === currentId); }, [currentId, reaction]); const $ = overrides.useOverrides(overrides$1); const tooltipContent = react.useMemo( () => /* @__PURE__ */ jsxRuntime.jsx("span", { children: $.COMMENT_REACTION_LIST( /* @__PURE__ */ jsxRuntime.jsx(List.List, { values: reaction.users.map((users) => /* @__PURE__ */ jsxRuntime.jsx(User.User, { userId: users.id, replaceSelf: true }, users.id)), formatRemaining: $.LIST_REMAINING_USERS, truncate: REACTIONS_TRUNCATE, locale: $.locale }), reaction.emoji, reaction.users.length ) }), [$, reaction] ); const stopPropagation = react.useCallback((event) => { event.stopPropagation(); }, []); const handlePressedChange = react.useCallback( (isPressed) => { if (isPressed) { addReaction({ threadId: comment.threadId, commentId: comment.id, emoji: reaction.emoji }); } else { removeReaction({ threadId: comment.threadId, commentId: comment.id, emoji: reaction.emoji }); } }, [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction] ); return /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: tooltipContent, multiline: true, className: "lb-comment-reaction-tooltip", children: /* @__PURE__ */ jsxRuntime.jsx(TogglePrimitive__namespace.Root, { asChild: true, pressed: isActive, onPressedChange: handlePressedChange, onClick: stopPropagation, disabled, ref: forwardedRef, children: /* @__PURE__ */ jsxRuntime.jsx(CommentReactionButton, { "data-self": isActive ? "" : void 0, reaction, overrides: overrides$1, ...props }) }) }); }); const CommentNonInteractiveReaction = react.forwardRef(({ reaction, overrides, ...props }, forwardedRef) => { const currentId = shared.useCurrentUserId(); const isActive = react.useMemo(() => { return reaction.users.some((users) => users.id === currentId); }, [currentId, reaction]); return /* @__PURE__ */ jsxRuntime.jsx(CommentReactionButton, { disableable: false, "data-self": isActive ? "" : void 0, reaction, overrides, ...props, ref: forwardedRef }); }); function openAttachment({ attachment, url }) { if (attachment.mimeType === "application/pdf" || attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/") || attachment.mimeType.startsWith("audio/")) { window.open(url, "_blank"); } else { download.download(url, attachment.name); } } function CommentMediaAttachment({ attachment, onAttachmentClick, roomId, className, overrides, ...props }) { const { url } = _private.useRoomAttachmentUrl(attachment.id, roomId); const handleClick = react.useCallback( (event) => { if (!url) { return; } const args = { attachment, url }; onAttachmentClick?.(args, event); if (event.isDefaultPrevented()) { return; } openAttachment(args); }, [attachment, onAttachmentClick, url] ); return /* @__PURE__ */ jsxRuntime.jsx(Attachment.MediaAttachment, { className: classNames.classNames("lb-comment-attachment", className), ...props, attachment, overrides, onClick: url ? handleClick : void 0, roomId }); } function CommentFileAttachment({ attachment, onAttachmentClick, roomId, className, overrides, ...props }) { const { url } = _private.useRoomAttachmentUrl(attachment.id, roomId); const handleClick = react.useCallback( (event) => { if (!url) { return; } const args = { attachment, url }; onAttachmentClick?.(args, event); if (event.isDefaultPrevented()) { return; } openAttachment(args); }, [attachment, onAttachmentClick, url] ); return /* @__PURE__ */ jsxRuntime.jsx(Attachment.FileAttachment, { className: classNames.classNames("lb-comment-attachment", className), ...props, attachment, overrides, onClick: url ? handleClick : void 0, roomId }); } function CommentNonInteractiveFileAttachment({ className, ...props }) { return /* @__PURE__ */ jsxRuntime.jsx(Attachment.FileAttachment, { className: classNames.classNames("lb-comment-attachment", className), allowMediaPreview: false, ...props }); } function AutoMarkReadThreadIdHandler({ threadId, roomId, commentRef }) { const markThreadAsRead = _private.useMarkRoomThreadAsRead(roomId); const isWindowFocused = useWindowFocus.useWindowFocus(); useVisible.useVisibleCallback( commentRef, () => { markThreadAsRead(threadId); }, { enabled: isWindowFocused } ); return null; } const Comment = react.forwardRef( ({ comment, indentContent = true, showDeleted, showActions = "hover", showReactions = true, showAttachments = true, showComposerFormattingControls = true, onAuthorClick, onMentionClick, onAttachmentClick, onCommentEdit, onCommentDelete, overrides: overrides$1, className, additionalActions, additionalActionsClassName, autoMarkReadThreadId, ...props }, forwardedRef) => { const ref = react.useRef(null); const mergedRefs = useRefs.useRefs(forwardedRef, ref); const currentUserId = shared.useCurrentUserId(); const deleteComment = _private.useDeleteRoomComment(comment.roomId); const editComment = _private.useEditRoomComment(comment.roomId); const addReaction = _private.useAddRoomCommentReaction(comment.roomId); const removeReaction = _private.useRemoveRoomCommentReaction(comment.roomId); const $ = overrides.useOverrides(overrides$1); const [isEditing, setEditing] = react.useState(false); const [isTarget, setTarget] = react.useState(false); const [isMoreActionOpen, setMoreActionOpen] = react.useState(false); const [isReactionActionOpen, setReactionActionOpen] = react.useState(false); const { mediaAttachments, fileAttachments } = react.useMemo(() => { return Attachment.separateMediaAttachments(comment.attachments); }, [comment.attachments]); const stopPropagation = react.useCallback((event) => { event.stopPropagation(); }, []); const handleEdit = react.useCallback(() => { setEditing(true); }, []); const handleEditCancel = react.useCallback( (event) => { event.stopPropagation(); setEditing(false); }, [] ); const handleEditSubmit = react.useCallback( ({ body, attachments }, event) => { onCommentEdit?.(comment); if (event.isDefaultPrevented()) { return; } event.stopPropagation(); event.preventDefault(); setEditing(false); editComment({ commentId: comment.id, threadId: comment.threadId, body, attachments }); }, [comment, editComment, onCommentEdit] ); const handleDelete = react.useCallback(() => { onCommentDelete?.(comment); deleteComment({ commentId: comment.id, threadId: comment.threadId }); }, [comment, deleteComment, onCommentDelete]); const handleAuthorClick = react.useCallback( (event) => { onAuthorClick?.(comment.userId, event); }, [comment.userId, onAuthorClick] ); const handleReactionSelect = react.useCallback( (emoji) => { const reactionIndex = comment.reactions.findIndex( (reaction) => reaction.emoji === emoji ); if (reactionIndex >= 0 && currentUserId && comment.reactions[reactionIndex]?.users.some( (user) => user.id === currentUserId )) { removeReaction({ threadId: comment.threadId, commentId: comment.id, emoji }); } else { addReaction({ threadId: comment.threadId, commentId: comment.id, emoji }); } }, [ addReaction, comment.id, comment.reactions, comment.threadId, removeReaction, currentUserId ] ); react.useEffect(() => { const isWindowDefined = typeof window !== "undefined"; if (!isWindowDefined) return; const hash = window.location.hash; const commentId = hash.slice(1); if (commentId === comment.id) { setTarget(true); } }, []); if (!showDeleted && !comment.body) { return null; } return /* @__PURE__ */ jsxRuntime.jsxs(TooltipPrimitive.TooltipProvider, { children: [ autoMarkReadThreadId && /* @__PURE__ */ jsxRuntime.jsx(AutoMarkReadThreadIdHandler, { commentRef: ref, threadId: autoMarkReadThreadId, roomId: comment.roomId }), /* @__PURE__ */ jsxRuntime.jsxs("div", { id: comment.id, className: classNames.classNames( "lb-root lb-comment", indentContent && "lb-comment:indent-content", showActions === "hover" && "lb-comment:show-actions-hover", (isMoreActionOpen || isReactionActionOpen) && "lb-comment:action-open", className ), "data-deleted": !comment.body ? "" : void 0, "data-editing": isEditing ? "" : void 0, "data-target": isTarget ? "" : void 0, dir: $.dir, ...props, ref: mergedRefs, children: [ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-comment-header", children: [ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-comment-details", children: [ /* @__PURE__ */ jsxRuntime.jsx(Avatar.Avatar, { className: "lb-comment-avatar", userId: comment.userId, onClick: handleAuthorClick }), /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lb-comment-details-labels", children: [ /* @__PURE__ */ jsxRuntime.jsx(User.User, { className: "lb-comment-author", userId: comment.userId, onClick: handleAuthorClick }), /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lb-comment-date", children: [ /* @__PURE__ */ jsxRuntime.jsx(Timestamp.Timestamp, { locale: $.locale, date: comment.createdAt, className: "lb-date lb-comment-date-created" }), comment.editedAt && comment.body && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ " ", /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-comment-date-edited", children: $.COMMENT_EDITED }) ] }) ] }) ] }) ] }), showActions && !isEditing && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.classNames( "lb-comment-actions", additionalActionsClassName ), children: [ additionalActions ?? null, showReactions && /* @__PURE__ */ jsxRuntime.jsx(EmojiPicker.EmojiPicker, { onEmojiSelect: handleReactionSelect, onOpenChange: setReactionActionOpen, children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: $.COMMENT_ADD_REACTION, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { className: "lb-comment-action", onClick: stopPropagation, "aria-label": $.COMMENT_ADD_REACTION, icon: /* @__PURE__ */ jsxRuntime.jsx(EmojiPlus.EmojiPlusIcon, {}) }) }) }) }), comment.userId === currentUserId && /* @__PURE__ */ jsxRuntime.jsx(Dropdown.Dropdown, { open: isMoreActionOpen, onOpenChange: setMoreActionOpen, align: "end", content: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx(Dropdown.DropdownItem, { onSelect: handleEdit, onClick: stopPropagation, icon: /* @__PURE__ */ jsxRuntime.jsx(Edit.EditIcon, {}), children: $.COMMENT_EDIT }), /* @__PURE__ */ jsxRuntime.jsx(Dropdown.DropdownItem, { onSelect: handleDelete, onClick: stopPropagation, icon: /* @__PURE__ */ jsxRuntime.jsx(Delete.DeleteIcon, {}), children: $.COMMENT_DELETE }) ] }), children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: $.COMMENT_MORE, children: /* @__PURE__ */ jsxRuntime.jsx(DropdownMenuPrimitive.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { className: "lb-comment-action", disabled: !comment.body, onClick: stopPropagation, "aria-label": $.COMMENT_MORE, icon: /* @__PURE__ */ jsxRuntime.jsx(Ellipsis.EllipsisIcon, {}) }) }) }) }) ] }) ] }), /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-comment-content", children: isEditing ? /* @__PURE__ */ jsxRuntime.jsx(Composer.Composer, { className: "lb-comment-composer", onComposerSubmit: handleEditSubmit, defaultValue: comment.body, defaultAttachments: comment.attachments, autoFocus: true, showAttribution: false, showAttachments, showFormattingControls: showComposerFormattingControls, actions: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: $.COMMENT_EDIT_COMPOSER_CANCEL, "aria-label": $.COMMENT_EDIT_COMPOSER_CANCEL, children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { className: "lb-composer-action", onClick: handleEditCancel, icon: /* @__PURE__ */ jsxRuntime.jsx(Cross.CrossIcon, {}) }) }), /* @__PURE__ */ jsxRuntime.jsx(Tooltip.ShortcutTooltip, { content: $.COMMENT_EDIT_COMPOSER_SAVE, shortcut: "Enter", children: /* @__PURE__ */ jsxRuntime.jsx(index$1.Submit, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "primary", className: "lb-composer-action", onClick: stopPropagation, "aria-label": $.COMMENT_EDIT_COMPOSER_SAVE, icon: /* @__PURE__ */ jsxRuntime.jsx(Check.CheckIcon, {}) }) }) }) ] }), overrides: { COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER }, roomId: comment.roomId }) : comment.body ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx(index.Body, { className: "lb-comment-body", body: comment.body, components: { Mention: ({ userId }) => /* @__PURE__ */ jsxRuntime.jsx(CommentMention, { userId, onClick: (event) => onMentionClick?.(userId, event) }), Link: CommentLink } }), showAttachments && (mediaAttachments.length > 0 || fileAttachments.length > 0) ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-comment-attachments", children: [ mediaAttachments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-attachments", children: mediaAttachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(CommentMediaAttachment, { attachment, overrides: overrides$1, onAttachmentClick, roomId: comment.roomId }, attachment.id)) }) : null, fileAttachments.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-attachments", children: fileAttachments.map((attachment) => /* @__PURE__ */ jsxRuntime.jsx(CommentFileAttachment, { attachment, overrides: overrides$1, onAttachmentClick, roomId: comment.roomId }, attachment.id)) }) : null ] }) : null, showReactions && comment.reactions.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-comment-reactions", children: [ comment.reactions.map((reaction) => /* @__PURE__ */ jsxRuntime.jsx(CommentReaction, { comment, reaction, overrides: overrides$1 }, reaction.emoji)), /* @__PURE__ */ jsxRuntime.jsx(EmojiPicker.EmojiPicker, { onEmojiSelect: handleReactionSelect, children: /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: $.COMMENT_ADD_REACTION, children: /* @__PURE__ */ jsxRuntime.jsx(PopoverPrimitive.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { className: "lb-comment-reaction lb-comment-reaction-add", variant: "outline", onClick: stopPropagation, "aria-label": $.COMMENT_ADD_REACTION, icon: /* @__PURE__ */ jsxRuntime.jsx(EmojiPlus.EmojiPlusIcon, {}) }) }) }) }) ] }) ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-comment-body", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "lb-comment-deleted", children: $.COMMENT_DELETED }) }) }) ] }) ] }); } ); exports.Comment = Comment; exports.CommentLink = CommentLink; exports.CommentMention = CommentMention; exports.CommentNonInteractiveFileAttachment = CommentNonInteractiveFileAttachment; exports.CommentNonInteractiveLink = CommentNonInteractiveLink; exports.CommentNonInteractiveReaction = CommentNonInteractiveReaction; exports.CommentReaction = CommentReaction; //# sourceMappingURL=Comment.cjs.map