@liveblocks/react-ui 2.7.2 → 2.8.0-beta2
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.d.mts +5 -3
- package/dist/_private/index.d.ts +5 -3
- package/dist/_private/index.js +4 -2
- package/dist/_private/index.js.map +1 -1
- package/dist/_private/index.mjs +2 -1
- package/dist/_private/index.mjs.map +1 -1
- package/dist/components/Comment.js +106 -3
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/Comment.mjs +107 -5
- package/dist/components/Comment.mjs.map +1 -1
- package/dist/components/Composer.js +216 -103
- package/dist/components/Composer.js.map +1 -1
- package/dist/components/Composer.mjs +220 -107
- package/dist/components/Composer.mjs.map +1 -1
- package/dist/components/HistoryVersionSummary.js +5 -0
- package/dist/components/HistoryVersionSummary.js.map +1 -1
- package/dist/components/HistoryVersionSummary.mjs +5 -0
- package/dist/components/HistoryVersionSummary.mjs.map +1 -1
- package/dist/components/InboxNotification.js +20 -4
- package/dist/components/InboxNotification.js.map +1 -1
- package/dist/components/InboxNotification.mjs +20 -4
- package/dist/components/InboxNotification.mjs.map +1 -1
- package/dist/components/Thread.js +5 -0
- package/dist/components/Thread.js.map +1 -1
- package/dist/components/Thread.mjs +5 -0
- package/dist/components/Thread.mjs.map +1 -1
- package/dist/components/internal/Attachment.js +314 -0
- package/dist/components/internal/Attachment.js.map +1 -0
- package/dist/components/internal/Attachment.mjs +310 -0
- package/dist/components/internal/Attachment.mjs.map +1 -0
- package/dist/components/internal/InboxNotificationThread.js +12 -2
- package/dist/components/internal/InboxNotificationThread.js.map +1 -1
- package/dist/components/internal/InboxNotificationThread.mjs +13 -3
- package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
- package/dist/icons/Attachment.js +15 -0
- package/dist/icons/Attachment.js.map +1 -0
- package/dist/icons/Attachment.mjs +13 -0
- package/dist/icons/Attachment.mjs.map +1 -0
- package/dist/icons/Spinner.js +3 -9
- package/dist/icons/Spinner.js.map +1 -1
- package/dist/icons/Spinner.mjs +4 -10
- package/dist/icons/Spinner.mjs.map +1 -1
- package/dist/icons/{Missing.js → Warning.js} +3 -3
- package/dist/icons/{Missing.js.map → Warning.js.map} +1 -1
- package/dist/icons/{Missing.mjs → Warning.mjs} +3 -3
- package/dist/icons/{Missing.mjs.map → Warning.mjs.map} +1 -1
- package/dist/index.d.mts +73 -4
- package/dist/index.d.ts +73 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/overrides.js +5 -0
- package/dist/overrides.js.map +1 -1
- package/dist/overrides.mjs +5 -0
- package/dist/overrides.mjs.map +1 -1
- package/dist/primitives/Composer/contexts.js +17 -3
- package/dist/primitives/Composer/contexts.js.map +1 -1
- package/dist/primitives/Composer/contexts.mjs +15 -4
- package/dist/primitives/Composer/contexts.mjs.map +1 -1
- package/dist/primitives/Composer/index.js +207 -30
- package/dist/primitives/Composer/index.js.map +1 -1
- package/dist/primitives/Composer/index.mjs +210 -35
- package/dist/primitives/Composer/index.mjs.map +1 -1
- package/dist/primitives/Composer/utils.js +231 -0
- package/dist/primitives/Composer/utils.js.map +1 -1
- package/dist/primitives/Composer/utils.mjs +229 -1
- package/dist/primitives/Composer/utils.mjs.map +1 -1
- package/dist/primitives/EmojiPicker/utils.js +2 -2
- package/dist/primitives/EmojiPicker/utils.js.map +1 -1
- package/dist/primitives/EmojiPicker/utils.mjs +1 -1
- package/dist/primitives/EmojiPicker/utils.mjs.map +1 -1
- package/dist/primitives/FileSize.js +33 -0
- package/dist/primitives/FileSize.js.map +1 -0
- package/dist/primitives/FileSize.mjs +31 -0
- package/dist/primitives/FileSize.mjs.map +1 -0
- package/dist/primitives/index.d.mts +87 -3
- package/dist/primitives/index.d.ts +87 -3
- package/dist/primitives/index.js +4 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/primitives/index.mjs +2 -0
- package/dist/primitives/index.mjs.map +1 -1
- package/dist/slate/plugins/{paste-html.js → paste.js} +16 -5
- package/dist/slate/plugins/paste.js.map +1 -0
- package/dist/slate/plugins/{paste-html.mjs → paste.mjs} +16 -5
- package/dist/slate/plugins/paste.mjs.map +1 -0
- package/dist/utils/data-transfer.js +20 -0
- package/dist/utils/data-transfer.js.map +1 -0
- package/dist/utils/data-transfer.mjs +18 -0
- package/dist/utils/data-transfer.mjs.map +1 -0
- package/dist/utils/download.js +14 -0
- package/dist/utils/download.js.map +1 -0
- package/dist/utils/download.mjs +12 -0
- package/dist/utils/download.mjs.map +1 -0
- package/dist/utils/format-file-size.js +45 -0
- package/dist/utils/format-file-size.js.map +1 -0
- package/dist/utils/format-file-size.mjs +43 -0
- package/dist/utils/format-file-size.mjs.map +1 -0
- package/dist/utils/intl.js +6 -0
- package/dist/utils/intl.js.map +1 -1
- package/dist/utils/intl.mjs +6 -1
- package/dist/utils/intl.mjs.map +1 -1
- package/dist/utils/use-initial.js +2 -1
- package/dist/utils/use-initial.js.map +1 -1
- package/dist/utils/use-initial.mjs +3 -2
- package/dist/utils/use-initial.mjs.map +1 -1
- package/dist/utils/use-latest.js.map +1 -1
- package/dist/utils/use-latest.mjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/version.mjs.map +1 -1
- package/package.json +4 -4
- package/src/styles/dark/index.css +1 -0
- package/src/styles/index.css +343 -62
- package/src/styles/utils.css +44 -0
- 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
- package/dist/slate/plugins/paste-html.js.map +0 -1
- package/dist/slate/plugins/paste-html.mjs.map +0 -1
- package/dist/utils/chunk.js +0 -12
- package/dist/utils/chunk.js.map +0 -1
- package/dist/utils/chunk.mjs +0 -10
- package/dist/utils/chunk.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Thread.mjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentData,\n DM,\n ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkThreadAsResolved,\n useMarkThreadAsUnresolved,\n useThreadSubscription,\n} 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 { 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<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 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<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 * 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 onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n overrides,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkThreadAsResolved();\n const markThreadAsUnresolved = useMarkThreadAsUnresolved();\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 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 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 >\n {thread.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 <M extends BaseMetadata = DM>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAsIO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACf;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;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;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;AACiB;AACC;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;;"}
|
|
1
|
+
{"version":3,"file":"Thread.mjs","sources":["../../src/components/Thread.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n BaseMetadata,\n CommentData,\n DM,\n ThreadData,\n} from \"@liveblocks/core\";\nimport {\n useMarkThreadAsResolved,\n useMarkThreadAsUnresolved,\n useThreadSubscription,\n} 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 { 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<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 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 * 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 onResolvedChange,\n onCommentEdit,\n onCommentDelete,\n onThreadDelete,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n overrides,\n className,\n ...props\n }: ThreadProps<M>,\n forwardedRef: ForwardedRef<HTMLDivElement>\n ) => {\n const markThreadAsResolved = useMarkThreadAsResolved();\n const markThreadAsUnresolved = useMarkThreadAsUnresolved();\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 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 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 >\n {thread.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 showAttachments={showAttachments}\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 <M extends BaseMetadata = DM>(\n props: ThreadProps<M> & RefAttributes<HTMLDivElement>\n) => JSX.Element;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAgJO;AAAe;AAElB;AACE;AACuB;AACT;AACd;AACoB;AACJ;AACD;AACG;AAClB;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;AAEK;AACY;AACT;AAC2B;AAC3B;AACF;AACsC;AACQ;AACvC;AACH;AACC;AAEJ;AAAc;AAEX;AACA;AAGA;AACG;AACc;AACH;AACmB;AAC7B;AACe;AACF;AACb;AACA;AACA;AACA;AACiB;AACjB;AACA;AACA;AAIM;AAGmC;AAIpC;AAIS;AAGP;AACiB;AACC;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;AACxD;AACW;AACe;AACP;AACnB;AAIR;AAGN;;"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('@liveblocks/react');
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var Cross = require('../../icons/Cross.js');
|
|
7
|
+
var Spinner = require('../../icons/Spinner.js');
|
|
8
|
+
var Warning = require('../../icons/Warning.js');
|
|
9
|
+
var overrides = require('../../overrides.js');
|
|
10
|
+
require('../../primitives/Comment/index.js');
|
|
11
|
+
require('../../primitives/Composer/index.js');
|
|
12
|
+
var contexts = require('../../primitives/Composer/contexts.js');
|
|
13
|
+
var utils = require('../../primitives/Composer/utils.js');
|
|
14
|
+
require('../../primitives/EmojiPicker/index.js');
|
|
15
|
+
require('../../primitives/FileSize.js');
|
|
16
|
+
require('../../primitives/Timestamp.js');
|
|
17
|
+
var classNames = require('../../utils/class-names.js');
|
|
18
|
+
var formatFileSize = require('../../utils/format-file-size.js');
|
|
19
|
+
var Tooltip = require('./Tooltip.js');
|
|
20
|
+
|
|
21
|
+
const MAX_DISPLAYED_MEDIA_SIZE = 60 * 1024 * 1024;
|
|
22
|
+
const fileExtensionRegex = /^(.+?)(\.[^.]+)?$/;
|
|
23
|
+
function splitFileName(name) {
|
|
24
|
+
const match = name.match(fileExtensionRegex);
|
|
25
|
+
return { base: match?.[1] ?? name, extension: match?.[2] };
|
|
26
|
+
}
|
|
27
|
+
function getAttachmentIconGlyph(mimeType) {
|
|
28
|
+
if (mimeType === "application/zip" || mimeType === "application/gzip" || mimeType === "application/vnd.rar" || mimeType === "application/x-rar-compressed" || mimeType === "application/x-7z-compressed" || mimeType === "application/x-zip-compressed" || mimeType === "application/x-tar" || mimeType === "application/x-bzip" || mimeType === "application/x-bzip2") {
|
|
29
|
+
return /* @__PURE__ */ React.createElement("path", {
|
|
30
|
+
d: "M13 15h2v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 .5-.5V20h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 .5-.5V15a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2Z"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (mimeType.startsWith("text/") || mimeType.startsWith("font/") || mimeType.startsWith("application/")) {
|
|
34
|
+
return /* @__PURE__ */ React.createElement("path", {
|
|
35
|
+
d: "M10 16a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Z"
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (mimeType.startsWith("image/")) {
|
|
39
|
+
return /* @__PURE__ */ React.createElement("path", {
|
|
40
|
+
d: "M12 16h6a1 1 0 0 1 1 1v3l-1.293-1.293a1 1 0 0 0-1.414 0L14.09 20.91l-.464-.386a1 1 0 0 0-1.265-.013l-1.231.985A.995.995 0 0 1 11 21v-4a1 1 0 0 1 1-1Zm-2 1a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-4Zm3 2a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (mimeType.startsWith("video/")) {
|
|
44
|
+
return /* @__PURE__ */ React.createElement("path", {
|
|
45
|
+
d: "M12 15.71a1 1 0 0 1 1.49-.872l4.96 2.79a1 1 0 0 1 0 1.744l-4.96 2.79A1 1 0 0 1 12 21.29v-5.58Z"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (mimeType.startsWith("audio/")) {
|
|
49
|
+
return /* @__PURE__ */ React.createElement("path", {
|
|
50
|
+
d: "M15 15a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 1 0v-7a.5.5 0 0 0-.5-.5Zm-2.5 2.5a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3Zm-2 1a.5.5 0 0 1 1 0v1a.5.5 0 0 1-1 0v-1Zm6-1a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3ZM19 16a.5.5 0 0 0-.5.5v5a.5.5 0 0 0 1 0v-5a.5.5 0 0 0-.5-.5Z"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const AttachmentFileIcon = React.memo(({ mimeType }) => {
|
|
56
|
+
const iconGlyph = React.useMemo(() => getAttachmentIconGlyph(mimeType), [mimeType]);
|
|
57
|
+
return /* @__PURE__ */ React.createElement("svg", {
|
|
58
|
+
className: "lb-attachment-icon",
|
|
59
|
+
width: 30,
|
|
60
|
+
height: 30,
|
|
61
|
+
viewBox: "0 0 30 30",
|
|
62
|
+
fill: "currentColor",
|
|
63
|
+
fillRule: "evenodd",
|
|
64
|
+
clipRule: "evenodd",
|
|
65
|
+
xmlns: "http://www.w3.org/2000/svg"
|
|
66
|
+
}, /* @__PURE__ */ React.createElement("path", {
|
|
67
|
+
d: "M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z",
|
|
68
|
+
className: "lb-attachment-icon-shadow"
|
|
69
|
+
}), /* @__PURE__ */ React.createElement("path", {
|
|
70
|
+
d: "M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z",
|
|
71
|
+
className: "lb-attachment-icon-background"
|
|
72
|
+
}), /* @__PURE__ */ React.createElement("path", {
|
|
73
|
+
d: "M14.382 3.037a4 4 0 0 1 2.29 1.135l6.156 6.157a4 4 0 0 1 1.136 2.289A2 2 0 0 0 22 11h-4a2 2 0 0 1-2-2V5a2 2 0 0 0-1.618-1.963Z",
|
|
74
|
+
className: "lb-attachment-icon-fold"
|
|
75
|
+
}), iconGlyph && /* @__PURE__ */ React.createElement("g", {
|
|
76
|
+
className: "lb-attachment-icon-glyph"
|
|
77
|
+
}, iconGlyph));
|
|
78
|
+
});
|
|
79
|
+
function AttachmentImagePreview({
|
|
80
|
+
attachment
|
|
81
|
+
}) {
|
|
82
|
+
const { url } = react.useAttachmentUrl(attachment.id);
|
|
83
|
+
const [isLoaded, setLoaded] = React.useState(false);
|
|
84
|
+
const handleLoad = React.useCallback(() => {
|
|
85
|
+
setLoaded(true);
|
|
86
|
+
}, []);
|
|
87
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, !isLoaded ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : null, url ? /* @__PURE__ */ React.createElement("div", {
|
|
88
|
+
className: "lb-attachment-preview-media",
|
|
89
|
+
"data-hidden": !isLoaded ? "" : void 0
|
|
90
|
+
}, /* @__PURE__ */ React.createElement("img", {
|
|
91
|
+
src: url,
|
|
92
|
+
loading: "lazy",
|
|
93
|
+
onLoad: handleLoad
|
|
94
|
+
})) : null);
|
|
95
|
+
}
|
|
96
|
+
function AttachmentVideoPreview({
|
|
97
|
+
attachment
|
|
98
|
+
}) {
|
|
99
|
+
const { url } = react.useAttachmentUrl(attachment.id);
|
|
100
|
+
const [isLoaded, setLoaded] = React.useState(false);
|
|
101
|
+
const handleLoad = React.useCallback(() => {
|
|
102
|
+
setLoaded(true);
|
|
103
|
+
}, []);
|
|
104
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, !isLoaded ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : null, url ? /* @__PURE__ */ React.createElement("div", {
|
|
105
|
+
className: "lb-attachment-preview-media",
|
|
106
|
+
"data-hidden": !isLoaded ? "" : void 0
|
|
107
|
+
}, /* @__PURE__ */ React.createElement("video", {
|
|
108
|
+
src: url,
|
|
109
|
+
onLoadedData: handleLoad
|
|
110
|
+
})) : null);
|
|
111
|
+
}
|
|
112
|
+
function AttachmentPreview({
|
|
113
|
+
attachment
|
|
114
|
+
}) {
|
|
115
|
+
const isInsideRoom = react.useIsInsideRoom();
|
|
116
|
+
const isUploaded = attachment.type === "attachment" || attachment.status === "uploaded";
|
|
117
|
+
if (isUploaded && isInsideRoom && attachment.size <= MAX_DISPLAYED_MEDIA_SIZE) {
|
|
118
|
+
if (attachment.mimeType.startsWith("image/")) {
|
|
119
|
+
return /* @__PURE__ */ React.createElement(AttachmentImagePreview, {
|
|
120
|
+
attachment
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (attachment.mimeType.startsWith("video/")) {
|
|
124
|
+
return /* @__PURE__ */ React.createElement(AttachmentVideoPreview, {
|
|
125
|
+
attachment
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return /* @__PURE__ */ React.createElement(AttachmentFileIcon, {
|
|
130
|
+
mimeType: attachment.mimeType
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function AttachmentName({
|
|
134
|
+
attachment
|
|
135
|
+
}) {
|
|
136
|
+
const { base: fileBaseName, extension: fileExtension } = React.useMemo(() => {
|
|
137
|
+
return splitFileName(attachment.name);
|
|
138
|
+
}, [attachment.name]);
|
|
139
|
+
return /* @__PURE__ */ React.createElement("span", {
|
|
140
|
+
className: "lb-attachment-name",
|
|
141
|
+
title: attachment.name
|
|
142
|
+
}, /* @__PURE__ */ React.createElement("span", {
|
|
143
|
+
className: "lb-attachment-name-base"
|
|
144
|
+
}, fileBaseName), fileExtension && /* @__PURE__ */ React.createElement("span", {
|
|
145
|
+
className: "lb-attachment-name-extension"
|
|
146
|
+
}, fileExtension));
|
|
147
|
+
}
|
|
148
|
+
function useClickOnKeyDown(onKeyDown) {
|
|
149
|
+
const handleKeyDown = React.useCallback(
|
|
150
|
+
(event) => {
|
|
151
|
+
onKeyDown?.(event);
|
|
152
|
+
if (event.isDefaultPrevented()) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
const clickEvent = new MouseEvent("click", {
|
|
158
|
+
bubbles: true,
|
|
159
|
+
cancelable: true,
|
|
160
|
+
view: window
|
|
161
|
+
});
|
|
162
|
+
event.target.dispatchEvent(clickEvent);
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
[onKeyDown]
|
|
166
|
+
);
|
|
167
|
+
return handleKeyDown;
|
|
168
|
+
}
|
|
169
|
+
function useAttachmentContent(attachment, overrides$1) {
|
|
170
|
+
const $ = overrides.useOverrides(overrides$1);
|
|
171
|
+
const composerAttachmentsContext = contexts.useComposerAttachmentsContextOrNull();
|
|
172
|
+
const isInComposer = Boolean(composerAttachmentsContext);
|
|
173
|
+
const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;
|
|
174
|
+
const status = attachment.type === "localAttachment" ? attachment.status : void 0;
|
|
175
|
+
const isUploading = status === "uploading";
|
|
176
|
+
const isError = status === "error";
|
|
177
|
+
let description;
|
|
178
|
+
if (attachment.type === "localAttachment" && attachment.status === "error") {
|
|
179
|
+
if (attachment.error instanceof utils.AttachmentTooLargeError) {
|
|
180
|
+
description = $.ATTACHMENT_TOO_LARGE(
|
|
181
|
+
maxAttachmentSize ? formatFileSize.formatFileSize(maxAttachmentSize, $.locale) : void 0
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
description = $.ATTACHMENT_ERROR(attachment.error);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
description = formatFileSize.formatFileSize(attachment.size, $.locale);
|
|
188
|
+
}
|
|
189
|
+
const deleteLabel = isInComposer ? $.COMPOSER_REMOVE_ATTACHMENT : $.COMMENT_DELETE_ATTACHMENT;
|
|
190
|
+
return {
|
|
191
|
+
isUploading,
|
|
192
|
+
isError,
|
|
193
|
+
description,
|
|
194
|
+
deleteLabel
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function MediaAttachment({
|
|
198
|
+
attachment,
|
|
199
|
+
overrides,
|
|
200
|
+
onClick,
|
|
201
|
+
onDeleteClick,
|
|
202
|
+
preventFocusOnDelete,
|
|
203
|
+
className,
|
|
204
|
+
onKeyDown,
|
|
205
|
+
...props
|
|
206
|
+
}) {
|
|
207
|
+
const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides);
|
|
208
|
+
const handleDeletePointerDown = React.useCallback(
|
|
209
|
+
(event) => {
|
|
210
|
+
if (preventFocusOnDelete) {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
[preventFocusOnDelete]
|
|
215
|
+
);
|
|
216
|
+
const handleKeyDown = useClickOnKeyDown(onKeyDown);
|
|
217
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
218
|
+
className: classNames.classNames("lb-attachment lb-media-attachment", className),
|
|
219
|
+
"data-error": isError ? "" : void 0,
|
|
220
|
+
...props,
|
|
221
|
+
role: onClick ? "button" : void 0,
|
|
222
|
+
onClick,
|
|
223
|
+
tabIndex: onClick ? 0 : -1,
|
|
224
|
+
onKeyDown: onClick ? handleKeyDown : void 0
|
|
225
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
226
|
+
className: "lb-attachment-preview"
|
|
227
|
+
}, isUploading ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : isError ? /* @__PURE__ */ React.createElement(Warning.WarningIcon, null) : /* @__PURE__ */ React.createElement(AttachmentPreview, {
|
|
228
|
+
attachment
|
|
229
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
230
|
+
className: "lb-attachment-details"
|
|
231
|
+
}, /* @__PURE__ */ React.createElement(AttachmentName, {
|
|
232
|
+
attachment
|
|
233
|
+
}), /* @__PURE__ */ React.createElement("span", {
|
|
234
|
+
className: "lb-attachment-description",
|
|
235
|
+
title: description
|
|
236
|
+
}, description)), onDeleteClick && /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
|
|
237
|
+
content: deleteLabel
|
|
238
|
+
}, /* @__PURE__ */ React.createElement("button", {
|
|
239
|
+
type: "button",
|
|
240
|
+
className: "lb-attachment-delete",
|
|
241
|
+
onClick: onDeleteClick,
|
|
242
|
+
onPointerDown: handleDeletePointerDown,
|
|
243
|
+
"aria-label": deleteLabel
|
|
244
|
+
}, /* @__PURE__ */ React.createElement(Cross.CrossIcon, null))));
|
|
245
|
+
}
|
|
246
|
+
function FileAttachment({
|
|
247
|
+
attachment,
|
|
248
|
+
overrides,
|
|
249
|
+
onClick,
|
|
250
|
+
onDeleteClick,
|
|
251
|
+
preventFocusOnDelete,
|
|
252
|
+
className,
|
|
253
|
+
onKeyDown,
|
|
254
|
+
...props
|
|
255
|
+
}) {
|
|
256
|
+
const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides);
|
|
257
|
+
const handleDeletePointerDown = React.useCallback(
|
|
258
|
+
(event) => {
|
|
259
|
+
if (preventFocusOnDelete) {
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
[preventFocusOnDelete]
|
|
264
|
+
);
|
|
265
|
+
const handleKeyDown = useClickOnKeyDown(onKeyDown);
|
|
266
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
267
|
+
className: classNames.classNames("lb-attachment lb-file-attachment", className),
|
|
268
|
+
"data-error": isError ? "" : void 0,
|
|
269
|
+
...props,
|
|
270
|
+
role: onClick ? "button" : void 0,
|
|
271
|
+
onClick,
|
|
272
|
+
tabIndex: onClick ? 0 : -1,
|
|
273
|
+
onKeyDown: onClick ? handleKeyDown : void 0
|
|
274
|
+
}, /* @__PURE__ */ React.createElement("div", {
|
|
275
|
+
className: "lb-attachment-preview"
|
|
276
|
+
}, isUploading ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : isError ? /* @__PURE__ */ React.createElement(Warning.WarningIcon, null) : /* @__PURE__ */ React.createElement(AttachmentPreview, {
|
|
277
|
+
attachment
|
|
278
|
+
})), /* @__PURE__ */ React.createElement("div", {
|
|
279
|
+
className: "lb-attachment-details"
|
|
280
|
+
}, /* @__PURE__ */ React.createElement(AttachmentName, {
|
|
281
|
+
attachment
|
|
282
|
+
}), /* @__PURE__ */ React.createElement("span", {
|
|
283
|
+
className: "lb-attachment-description",
|
|
284
|
+
title: description
|
|
285
|
+
}, description)), onDeleteClick && /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
|
|
286
|
+
content: deleteLabel
|
|
287
|
+
}, /* @__PURE__ */ React.createElement("button", {
|
|
288
|
+
type: "button",
|
|
289
|
+
className: "lb-attachment-delete",
|
|
290
|
+
onClick: onDeleteClick,
|
|
291
|
+
onPointerDown: handleDeletePointerDown,
|
|
292
|
+
"aria-label": deleteLabel
|
|
293
|
+
}, /* @__PURE__ */ React.createElement(Cross.CrossIcon, null))));
|
|
294
|
+
}
|
|
295
|
+
function separateMediaAttachments(attachments) {
|
|
296
|
+
const mediaAttachments = [];
|
|
297
|
+
const fileAttachments = [];
|
|
298
|
+
for (const attachment of attachments) {
|
|
299
|
+
if ((attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/")) && attachment.size <= MAX_DISPLAYED_MEDIA_SIZE) {
|
|
300
|
+
mediaAttachments.push(attachment);
|
|
301
|
+
} else {
|
|
302
|
+
fileAttachments.push(attachment);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
mediaAttachments,
|
|
307
|
+
fileAttachments
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
exports.FileAttachment = FileAttachment;
|
|
312
|
+
exports.MediaAttachment = MediaAttachment;
|
|
313
|
+
exports.separateMediaAttachments = separateMediaAttachments;
|
|
314
|
+
//# sourceMappingURL=Attachment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Attachment.js","sources":["../../../src/components/internal/Attachment.tsx"],"sourcesContent":["\"use client\";\n\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { useAttachmentUrl, useIsInsideRoom } from \"@liveblocks/react\";\nimport type {\n ComponentPropsWithoutRef,\n KeyboardEvent,\n MouseEventHandler,\n PointerEvent,\n} from \"react\";\nimport React, { memo, useCallback, useMemo, useState } from \"react\";\n\nimport { CrossIcon } from \"../../icons/Cross\";\nimport { SpinnerIcon } from \"../../icons/Spinner\";\nimport { WarningIcon } from \"../../icons/Warning\";\nimport type { Overrides } from \"../../overrides\";\nimport { useOverrides } from \"../../overrides\";\nimport { AttachmentTooLargeError } from \"../../primitives\";\nimport { useComposerAttachmentsContextOrNull } from \"../../primitives/Composer/contexts\";\nimport { classNames } from \"../../utils/class-names\";\nimport { formatFileSize } from \"../../utils/format-file-size\";\nimport { Tooltip } from \"./Tooltip\";\n\nconst MAX_DISPLAYED_MEDIA_SIZE = 60 * 1024 * 1024; // 60 MB\n\ninterface AttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n onDeleteClick?: MouseEventHandler<HTMLButtonElement>;\n preventFocusOnDelete?: boolean;\n overrides?: Partial<Overrides>;\n}\n\nconst fileExtensionRegex = /^(.+?)(\\.[^.]+)?$/;\n\nfunction splitFileName(name: string) {\n const match = name.match(fileExtensionRegex);\n\n return { base: match?.[1] ?? name, extension: match?.[2] };\n}\n\nfunction getAttachmentIconGlyph(mimeType: string) {\n if (\n mimeType === \"application/zip\" ||\n mimeType === \"application/gzip\" ||\n mimeType === \"application/vnd.rar\" ||\n mimeType === \"application/x-rar-compressed\" ||\n mimeType === \"application/x-7z-compressed\" ||\n mimeType === \"application/x-zip-compressed\" ||\n mimeType === \"application/x-tar\" ||\n mimeType === \"application/x-bzip\" ||\n mimeType === \"application/x-bzip2\"\n ) {\n return (\n <path d=\"M13 15h2v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 .5-.5V20h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 .5-.5V15a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2Z\" />\n );\n }\n\n if (\n mimeType.startsWith(\"text/\") ||\n mimeType.startsWith(\"font/\") ||\n mimeType.startsWith(\"application/\")\n ) {\n return (\n <path d=\"M10 16a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Z\" />\n );\n }\n\n if (mimeType.startsWith(\"image/\")) {\n return (\n <path d=\"M12 16h6a1 1 0 0 1 1 1v3l-1.293-1.293a1 1 0 0 0-1.414 0L14.09 20.91l-.464-.386a1 1 0 0 0-1.265-.013l-1.231.985A.995.995 0 0 1 11 21v-4a1 1 0 0 1 1-1Zm-2 1a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-4Zm3 2a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z\" />\n );\n }\n\n if (mimeType.startsWith(\"video/\")) {\n return (\n <path d=\"M12 15.71a1 1 0 0 1 1.49-.872l4.96 2.79a1 1 0 0 1 0 1.744l-4.96 2.79A1 1 0 0 1 12 21.29v-5.58Z\" />\n );\n }\n\n if (mimeType.startsWith(\"audio/\")) {\n return (\n <path d=\"M15 15a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 1 0v-7a.5.5 0 0 0-.5-.5Zm-2.5 2.5a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3Zm-2 1a.5.5 0 0 1 1 0v1a.5.5 0 0 1-1 0v-1Zm6-1a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3ZM19 16a.5.5 0 0 0-.5.5v5a.5.5 0 0 0 1 0v-5a.5.5 0 0 0-.5-.5Z\" />\n );\n }\n\n return null;\n}\n\nconst AttachmentFileIcon = memo(({ mimeType }: { mimeType: string }) => {\n const iconGlyph = useMemo(() => getAttachmentIconGlyph(mimeType), [mimeType]);\n\n return (\n <svg\n className=\"lb-attachment-icon\"\n width={30}\n height={30}\n viewBox=\"0 0 30 30\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z\"\n className=\"lb-attachment-icon-shadow\"\n />\n <path\n d=\"M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z\"\n className=\"lb-attachment-icon-background\"\n />\n <path\n d=\"M14.382 3.037a4 4 0 0 1 2.29 1.135l6.156 6.157a4 4 0 0 1 1.136 2.289A2 2 0 0 0 22 11h-4a2 2 0 0 1-2-2V5a2 2 0 0 0-1.618-1.963Z\"\n className=\"lb-attachment-icon-fold\"\n />\n\n {iconGlyph && <g className=\"lb-attachment-icon-glyph\">{iconGlyph}</g>}\n </svg>\n );\n});\n\nfunction AttachmentImagePreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const { url } = useAttachmentUrl(attachment.id);\n const [isLoaded, setLoaded] = useState(false);\n\n const handleLoad = useCallback(() => {\n setLoaded(true);\n }, []);\n\n return (\n <>\n {!isLoaded ? <SpinnerIcon /> : null}\n {url ? (\n <div\n className=\"lb-attachment-preview-media\"\n data-hidden={!isLoaded ? \"\" : undefined}\n >\n <img src={url} loading=\"lazy\" onLoad={handleLoad} />\n </div>\n ) : null}\n </>\n );\n}\n\nfunction AttachmentVideoPreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const { url } = useAttachmentUrl(attachment.id);\n const [isLoaded, setLoaded] = useState(false);\n\n const handleLoad = useCallback(() => {\n setLoaded(true);\n }, []);\n\n return (\n <>\n {!isLoaded ? <SpinnerIcon /> : null}\n {url ? (\n <div\n className=\"lb-attachment-preview-media\"\n data-hidden={!isLoaded ? \"\" : undefined}\n >\n <video src={url} onLoadedData={handleLoad} />\n </div>\n ) : null}\n </>\n );\n}\n\nfunction AttachmentPreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const isInsideRoom = useIsInsideRoom();\n const isUploaded =\n attachment.type === \"attachment\" || attachment.status === \"uploaded\";\n\n if (\n isUploaded &&\n isInsideRoom &&\n attachment.size <= MAX_DISPLAYED_MEDIA_SIZE\n ) {\n if (attachment.mimeType.startsWith(\"image/\")) {\n return <AttachmentImagePreview attachment={attachment} />;\n }\n\n if (attachment.mimeType.startsWith(\"video/\")) {\n return <AttachmentVideoPreview attachment={attachment} />;\n }\n }\n\n return <AttachmentFileIcon mimeType={attachment.mimeType} />;\n}\n\nfunction AttachmentName({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const { base: fileBaseName, extension: fileExtension } = useMemo(() => {\n return splitFileName(attachment.name);\n }, [attachment.name]);\n\n return (\n <span className=\"lb-attachment-name\" title={attachment.name}>\n <span className=\"lb-attachment-name-base\">{fileBaseName}</span>\n {fileExtension && (\n <span className=\"lb-attachment-name-extension\">{fileExtension}</span>\n )}\n </span>\n );\n}\n\nfunction useClickOnKeyDown(\n onKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void\n) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Simulate a click event on Enter or Space because it's a div\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n\n const clickEvent = new MouseEvent(\"click\", {\n bubbles: true,\n cancelable: true,\n view: window,\n });\n event.target.dispatchEvent(clickEvent);\n }\n },\n [onKeyDown]\n );\n\n return handleKeyDown;\n}\n\nfunction useAttachmentContent(\n attachment: CommentMixedAttachment,\n overrides?: Partial<Overrides>\n) {\n const $ = useOverrides(overrides);\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n const isInComposer = Boolean(composerAttachmentsContext);\n const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;\n\n const status =\n attachment.type === \"localAttachment\" ? attachment.status : undefined;\n const isUploading = status === \"uploading\";\n const isError = status === \"error\";\n\n let description: string;\n\n if (attachment.type === \"localAttachment\" && attachment.status === \"error\") {\n if (attachment.error instanceof AttachmentTooLargeError) {\n description = $.ATTACHMENT_TOO_LARGE(\n maxAttachmentSize\n ? formatFileSize(maxAttachmentSize, $.locale)\n : undefined\n );\n } else {\n description = $.ATTACHMENT_ERROR(attachment.error);\n }\n } else {\n description = formatFileSize(attachment.size, $.locale);\n }\n\n const deleteLabel = isInComposer\n ? $.COMPOSER_REMOVE_ATTACHMENT\n : $.COMMENT_DELETE_ATTACHMENT;\n\n return {\n isUploading,\n isError,\n description,\n deleteLabel,\n };\n}\n\nexport function MediaAttachment({\n attachment,\n overrides,\n onClick,\n onDeleteClick,\n preventFocusOnDelete,\n className,\n onKeyDown,\n ...props\n}: AttachmentProps) {\n const { isUploading, isError, description, deleteLabel } =\n useAttachmentContent(attachment, overrides);\n\n const handleDeletePointerDown = useCallback(\n (event: PointerEvent<HTMLButtonElement>) => {\n if (preventFocusOnDelete) {\n event.preventDefault();\n }\n },\n [preventFocusOnDelete]\n );\n\n const handleKeyDown = useClickOnKeyDown(onKeyDown);\n\n return (\n <div\n className={classNames(\"lb-attachment lb-media-attachment\", className)}\n data-error={isError ? \"\" : undefined}\n {...props}\n role={onClick ? \"button\" : undefined}\n onClick={onClick}\n tabIndex={onClick ? 0 : -1}\n onKeyDown={onClick ? handleKeyDown : undefined}\n >\n <div className=\"lb-attachment-preview\">\n {isUploading ? (\n <SpinnerIcon />\n ) : isError ? (\n <WarningIcon />\n ) : (\n <AttachmentPreview attachment={attachment} />\n )}\n </div>\n <div className=\"lb-attachment-details\">\n <AttachmentName attachment={attachment} />\n <span className=\"lb-attachment-description\" title={description}>\n {description}\n </span>\n </div>\n {onDeleteClick && (\n <Tooltip content={deleteLabel}>\n <button\n type=\"button\"\n className=\"lb-attachment-delete\"\n onClick={onDeleteClick}\n onPointerDown={handleDeletePointerDown}\n aria-label={deleteLabel}\n >\n <CrossIcon />\n </button>\n </Tooltip>\n )}\n </div>\n );\n}\n\nexport function FileAttachment({\n attachment,\n overrides,\n onClick,\n onDeleteClick,\n preventFocusOnDelete,\n className,\n onKeyDown,\n ...props\n}: AttachmentProps) {\n const { isUploading, isError, description, deleteLabel } =\n useAttachmentContent(attachment, overrides);\n\n const handleDeletePointerDown = useCallback(\n (event: PointerEvent<HTMLButtonElement>) => {\n if (preventFocusOnDelete) {\n event.preventDefault();\n }\n },\n [preventFocusOnDelete]\n );\n\n const handleKeyDown = useClickOnKeyDown(onKeyDown);\n\n return (\n <div\n className={classNames(\"lb-attachment lb-file-attachment\", className)}\n data-error={isError ? \"\" : undefined}\n {...props}\n role={onClick ? \"button\" : undefined}\n onClick={onClick}\n tabIndex={onClick ? 0 : -1}\n onKeyDown={onClick ? handleKeyDown : undefined}\n >\n <div className=\"lb-attachment-preview\">\n {isUploading ? (\n <SpinnerIcon />\n ) : isError ? (\n <WarningIcon />\n ) : (\n <AttachmentPreview attachment={attachment} />\n )}\n </div>\n <div className=\"lb-attachment-details\">\n <AttachmentName attachment={attachment} />\n <span className=\"lb-attachment-description\" title={description}>\n {description}\n </span>\n </div>\n {onDeleteClick && (\n <Tooltip content={deleteLabel}>\n <button\n type=\"button\"\n className=\"lb-attachment-delete\"\n onClick={onDeleteClick}\n onPointerDown={handleDeletePointerDown}\n aria-label={deleteLabel}\n >\n <CrossIcon />\n </button>\n </Tooltip>\n )}\n </div>\n );\n}\n\nexport function separateMediaAttachments<T extends CommentMixedAttachment>(\n attachments: T[]\n) {\n const mediaAttachments: T[] = [];\n const fileAttachments: T[] = [];\n\n for (const attachment of attachments) {\n if (\n (attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\")) &&\n attachment.size <= MAX_DISPLAYED_MEDIA_SIZE\n ) {\n mediaAttachments.push(attachment);\n } else {\n fileAttachments.push(attachment);\n }\n }\n\n return {\n mediaAttachments,\n fileAttachments,\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA;AASA;AAEA;AACE;AAEA;AACF;AAEA;AACE;AAWE;AACG;AAAO;AAAoP;AAIhQ;AAKE;AACG;AAAO;AAAiP;AAI7P;AACE;AACG;AAAO;AAAiQ;AAI7Q;AACE;AACG;AAAO;AAAiG;AAI7G;AACE;AACG;AAAO;AAAyP;AAIrQ;AACF;AAEA;AACE;AAEA;AACG;AACW;AACH;AACC;AACA;AACH;AACI;AACA;AACH;AAEL;AACG;AACQ;AAEX;AACG;AACQ;AAEX;AACG;AACQ;AAGG;AAAY;AAGjC;AAEA;AAAgC;AAEhC;AAGE;AACA;AAEA;AACE;AAAc;AAGhB;AAIO;AACW;AACoB;AAE7B;AAAS;AAAa;AAAe;AAKhD;AAEA;AAAgC;AAEhC;AAGE;AACA;AAEA;AACE;AAAc;AAGhB;AAIO;AACW;AACoB;AAE7B;AAAW;AAAmB;AAKzC;AAEA;AAA2B;AAE3B;AAGE;AACA;AAGA;AAKE;AACE;AAAQ;AAAuB;AAAwB;AAGzD;AACE;AAAQ;AAAuB;AAAwB;AACzD;AAGF;AAAQ;AAAwC;AAClD;AAEA;AAAwB;AAExB;AAGE;AACE;AAAoC;AAGtC;AACG;AAAe;AAAuC;AACpD;AAAe;AAEb;AAAe;AAIxB;AAEA;AAGE;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAEA;AAA2C;AAChC;AACG;AACN;AAER;AAAqC;AACvC;AACF;AACU;AAGZ;AACF;AAEA;AAIE;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACE;AACE;AAAgB;AAGV;AACN;AAEA;AAAiD;AACnD;AAEA;AAAsD;AAGxD;AAIA;AAAO;AACL;AACA;AACA;AACA;AAEJ;AAEO;AAAyB;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AAGA;AAAgC;AAE5B;AACE;AAAqB;AACvB;AACF;AACqB;AAGvB;AAEA;AACG;AACqE;AACzC;AACvB;AACuB;AAC3B;AACwB;AACa;AAEpC;AAAc;AAMV;AAAkB;AAGtB;AAAc;AACZ;AAAe;AACf;AAAe;AAAmC;AAKlD;AAAiB;AACf;AACM;AACK;AACD;AACM;AACH;AAQxB;AAEO;AAAwB;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AAGA;AAAgC;AAE5B;AACE;AAAqB;AACvB;AACF;AACqB;AAGvB;AAEA;AACG;AACoE;AACxC;AACvB;AACuB;AAC3B;AACwB;AACa;AAEpC;AAAc;AAMV;AAAkB;AAGtB;AAAc;AACZ;AAAe;AACf;AAAe;AAAmC;AAKlD;AAAiB;AACf;AACM;AACK;AACD;AACM;AACH;AAQxB;AAEO;AAGL;AACA;AAEA;AACE;AAKE;AAAgC;AAEhC;AAA+B;AACjC;AAGF;AAAO;AACL;AACA;AAEJ;;;;"}
|