@liveblocks/react-ui 2.7.0-beta1 → 2.7.0-beta3

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.
Files changed (52) hide show
  1. package/dist/components/Comment.js +53 -8
  2. package/dist/components/Comment.js.map +1 -1
  3. package/dist/components/Comment.mjs +54 -9
  4. package/dist/components/Comment.mjs.map +1 -1
  5. package/dist/components/Composer.js +13 -16
  6. package/dist/components/Composer.js.map +1 -1
  7. package/dist/components/Composer.mjs +14 -17
  8. package/dist/components/Composer.mjs.map +1 -1
  9. package/dist/components/InboxNotification.js +4 -1
  10. package/dist/components/InboxNotification.js.map +1 -1
  11. package/dist/components/InboxNotification.mjs +4 -1
  12. package/dist/components/InboxNotification.mjs.map +1 -1
  13. package/dist/components/internal/Attachment.js +161 -74
  14. package/dist/components/internal/Attachment.js.map +1 -1
  15. package/dist/components/internal/Attachment.mjs +161 -76
  16. package/dist/components/internal/Attachment.mjs.map +1 -1
  17. package/dist/components/internal/InboxNotificationThread.js +5 -3
  18. package/dist/components/internal/InboxNotificationThread.js.map +1 -1
  19. package/dist/components/internal/InboxNotificationThread.mjs +5 -3
  20. package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
  21. package/dist/index.d.mts +4 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/primitives/Composer/contexts.js.map +1 -1
  24. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  25. package/dist/primitives/Composer/index.js +33 -26
  26. package/dist/primitives/Composer/index.js.map +1 -1
  27. package/dist/primitives/Composer/index.mjs +33 -26
  28. package/dist/primitives/Composer/index.mjs.map +1 -1
  29. package/dist/primitives/Composer/utils.js +73 -66
  30. package/dist/primitives/Composer/utils.js.map +1 -1
  31. package/dist/primitives/Composer/utils.mjs +73 -66
  32. package/dist/primitives/Composer/utils.mjs.map +1 -1
  33. package/dist/primitives/index.d.mts +4 -0
  34. package/dist/primitives/index.d.ts +4 -0
  35. package/dist/slate/plugins/{paste-html.js → paste.js} +11 -3
  36. package/dist/slate/plugins/paste.js.map +1 -0
  37. package/dist/slate/plugins/{paste-html.mjs → paste.mjs} +11 -3
  38. package/dist/slate/plugins/paste.mjs.map +1 -0
  39. package/dist/utils/data-transfer.js +14 -0
  40. package/dist/utils/data-transfer.js.map +1 -0
  41. package/dist/utils/data-transfer.mjs +12 -0
  42. package/dist/utils/data-transfer.mjs.map +1 -0
  43. package/dist/utils/use-latest.js.map +1 -1
  44. package/dist/utils/use-latest.mjs.map +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist/version.mjs +1 -1
  47. package/package.json +4 -4
  48. package/src/styles/index.css +91 -44
  49. package/styles.css +1 -1
  50. package/styles.css.map +1 -1
  51. package/dist/slate/plugins/paste-html.js.map +0 -1
  52. package/dist/slate/plugins/paste-html.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"InboxNotification.mjs","sources":["../../src/components/InboxNotification.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n InboxNotificationCustomData,\n InboxNotificationData,\n InboxNotificationTextMentionData,\n InboxNotificationThreadData,\n KDAD,\n} from \"@liveblocks/core\";\nimport { assertNever, console } from \"@liveblocks/core\";\nimport {\n useDeleteInboxNotification,\n useInboxNotificationThread,\n useMarkInboxNotificationAsRead,\n useRoomInfo,\n} from \"@liveblocks/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { TooltipProvider } from \"@radix-ui/react-tooltip\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n ComponentType,\n MouseEvent as ReactMouseEvent,\n ReactNode,\n SyntheticEvent,\n} from \"react\";\nimport React, { forwardRef, useCallback, useMemo, useState } from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { useComponents } from \"../components\";\nimport { CheckIcon } from \"../icons/Check\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { WarningIcon } from \"../icons/Warning\";\nimport type {\n CommentOverrides,\n GlobalOverrides,\n InboxNotificationOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { SlotProp } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { generateURL } from \"../utils/url\";\nimport { Avatar, type AvatarProps } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport {\n generateInboxNotificationThreadContents,\n INBOX_NOTIFICATION_THREAD_MAX_COMMENTS,\n InboxNotificationComment,\n} from \"./internal/InboxNotificationThread\";\nimport { List } from \"./internal/List\";\nimport { Room } from \"./internal/Room\";\nimport { Tooltip } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\ntype ComponentTypeWithRef<\n T extends keyof JSX.IntrinsicElements,\n P,\n> = ComponentType<P & Pick<ComponentProps<T>, \"ref\">>;\n\ntype InboxNotificationKinds<KS extends KDAD = KDAD> = {\n // For some reason, we cannot directly use KDAD in the mapped type line\n // below, because it will result in '{}' rather than picking up the\n // definition from the user-provided 'ActivitiesData'. Might be an internal\n // TS optimization, so we're making it a param to defer the resolution.\n [K in KS]: ComponentTypeWithRef<\"a\", InboxNotificationCustomKindProps<K>>;\n} & {\n thread: ComponentTypeWithRef<\"a\", InboxNotificationThreadKindProps>;\n textMention: ComponentTypeWithRef<\"a\", InboxNotificationTextMentionKindProps>;\n};\n\ninterface InboxNotificationSharedProps {\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n}\n\nexport interface InboxNotificationProps\n extends Omit<ComponentPropsWithoutRef<\"a\">, \"title\">,\n InboxNotificationSharedProps {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationData;\n\n /**\n * Override specific kinds of inbox notifications.\n */\n kinds?: Partial<InboxNotificationKinds>;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & InboxNotificationOverrides & CommentOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\nexport interface InboxNotificationThreadProps\n extends Omit<InboxNotificationProps, \"kinds\" | \"children\">,\n InboxNotificationSharedProps {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationThreadData;\n\n /**\n * Whether to show the room name in the title.\n */\n showRoomName?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n}\n\nexport interface InboxNotificationTextMentionProps\n extends Omit<InboxNotificationProps, \"kinds\">,\n InboxNotificationSharedProps {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationTextMentionData;\n\n /**\n * Whether to show the room name in the title.\n */\n showRoomName?: boolean;\n}\n\nexport interface InboxNotificationCustomProps\n extends Omit<InboxNotificationProps, \"kinds\">,\n InboxNotificationSharedProps,\n SlotProp {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationCustomData;\n\n /**\n * The inbox notification's content.\n */\n children: ReactNode;\n\n /**\n * The inbox notification's title.\n */\n title: ReactNode;\n\n /**\n * The inbox notification's aside content.\n * Can be combined with `InboxNotification.Icon` or `InboxNotification.Avatar` to easily follow default styles.\n */\n aside?: ReactNode;\n\n /**\n * Whether to mark the inbox notification as read when clicked.\n */\n markAsReadOnClick?: boolean;\n}\n\nexport type InboxNotificationThreadKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationThreadData;\n};\n\nexport type InboxNotificationTextMentionKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationTextMentionData;\n};\n\nexport type InboxNotificationCustomKindProps<K extends KDAD = KDAD> = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationCustomData<K>;\n};\n\ninterface InboxNotificationLayoutProps\n extends Omit<ComponentPropsWithoutRef<\"a\">, \"title\">,\n InboxNotificationSharedProps,\n SlotProp {\n inboxNotification: InboxNotificationData;\n aside: ReactNode;\n title: ReactNode;\n date: Date | string | number;\n unread?: boolean;\n overrides?: Partial<GlobalOverrides & InboxNotificationOverrides>;\n components?: Partial<GlobalComponents>;\n markAsReadOnClick?: boolean;\n}\n\nexport type InboxNotificationIconProps = ComponentProps<\"div\">;\n\nexport type InboxNotificationAvatarProps = AvatarProps;\n\nconst InboxNotificationLayout = forwardRef<\n HTMLAnchorElement,\n InboxNotificationLayoutProps\n>(\n (\n {\n inboxNotification,\n children,\n aside,\n title,\n date,\n unread,\n markAsReadOnClick,\n onClick,\n href,\n showActions,\n overrides,\n components,\n className,\n asChild,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const { Anchor } = useComponents(components);\n const Component = asChild ? Slot : Anchor;\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const markInboxNotificationAsRead = useMarkInboxNotificationAsRead();\n const deleteInboxNotification = useDeleteInboxNotification();\n\n const handleClick = useCallback(\n (event: ReactMouseEvent<HTMLAnchorElement, MouseEvent>) => {\n onClick?.(event);\n\n const shouldMarkAsReadOnClick = markAsReadOnClick ?? Boolean(href);\n\n if (unread && shouldMarkAsReadOnClick) {\n markInboxNotificationAsRead(inboxNotification.id);\n }\n },\n [\n href,\n inboxNotification.id,\n markAsReadOnClick,\n markInboxNotificationAsRead,\n onClick,\n unread,\n ]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const preventDefaultAndStopPropagation = useCallback(\n (event: SyntheticEvent) => {\n event.preventDefault();\n event.stopPropagation();\n },\n []\n );\n\n const handleMoreClick = useCallback((event: ReactMouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n setMoreActionOpen((open) => !open);\n }, []);\n\n const handleMarkAsRead = useCallback(() => {\n markInboxNotificationAsRead(inboxNotification.id);\n }, [inboxNotification.id, markInboxNotificationAsRead]);\n\n const handleDelete = useCallback(() => {\n deleteInboxNotification(inboxNotification.id);\n }, [inboxNotification.id, deleteInboxNotification]);\n\n return (\n <TooltipProvider>\n <Component\n className={classNames(\n \"lb-root lb-inbox-notification\",\n showActions === \"hover\" &&\n \"lb-inbox-notification:show-actions-hover\",\n isMoreActionOpen && \"lb-inbox-notification:action-open\",\n className\n )}\n dir={$.dir}\n data-unread={unread ? \"\" : undefined}\n data-kind={inboxNotification.kind}\n onClick={handleClick}\n href={href}\n {...props}\n ref={forwardedRef}\n >\n {aside && <div className=\"lb-inbox-notification-aside\">{aside}</div>}\n <div className=\"lb-inbox-notification-content\">\n <div className=\"lb-inbox-notification-header\">\n <span className=\"lb-inbox-notification-title\">{title}</span>\n <div className=\"lb-inbox-notification-details\">\n <span className=\"lb-inbox-notification-details-labels\">\n <Timestamp\n locale={$.locale}\n date={date}\n className=\"lb-inbox-notification-date\"\n />\n {unread && (\n <span\n className=\"lb-inbox-notification-unread-indicator\"\n role=\"presentation\"\n />\n )}\n </span>\n </div>\n {showActions && (\n <div className=\"lb-inbox-notification-actions\">\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n {unread ? (\n <DropdownItem\n onSelect={handleMarkAsRead}\n onClick={stopPropagation}\n >\n <CheckIcon className=\"lb-dropdown-item-icon\" />\n {$.INBOX_NOTIFICATION_MARK_AS_READ}\n </DropdownItem>\n ) : null}\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n >\n <DeleteIcon className=\"lb-dropdown-item-icon\" />\n {$.INBOX_NOTIFICATION_DELETE}\n </DropdownItem>\n </>\n }\n >\n <Tooltip content={$.INBOX_NOTIFICATION_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-inbox-notification-action\"\n onClick={handleMoreClick}\n onPointerDown={preventDefaultAndStopPropagation}\n onPointerUp={preventDefaultAndStopPropagation}\n aria-label={$.INBOX_NOTIFICATION_MORE}\n >\n <EllipsisIcon className=\"lb-button-icon\" />\n </Button>\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n </div>\n )}\n </div>\n <div className=\"lb-inbox-notification-body\">{children}</div>\n </div>\n </Component>\n </TooltipProvider>\n );\n }\n);\n\nfunction InboxNotificationIcon({\n className,\n ...props\n}: InboxNotificationIconProps) {\n return (\n <div\n className={classNames(\"lb-inbox-notification-icon\", className)}\n {...props}\n />\n );\n}\n\nfunction InboxNotificationAvatar({\n className,\n ...props\n}: InboxNotificationAvatarProps) {\n return (\n <Avatar\n className={classNames(\"lb-inbox-notification-avatar\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Displays a thread inbox notification.\n */\nconst InboxNotificationThread = forwardRef<\n HTMLAnchorElement,\n InboxNotificationThreadProps\n>(\n (\n {\n inboxNotification,\n href,\n showRoomName = true,\n showReactions = true,\n showAttachments = true,\n showActions = \"hover\",\n overrides,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const thread = useInboxNotificationThread(inboxNotification.id);\n const currentUserId = useCurrentUserId();\n // TODO: If you provide `href` (or plan to), we shouldn't run this hook. We should find a way to conditionally run it.\n // Because of batching and the fact that the same hook will be called within <Room /> in the notification's title,\n // it's not a big deal, the only scenario where it would be superfluous would be if the user provides their own\n // `href` AND disables room names in the title via `showRoomName={false}`.\n const { info } = useRoomInfo(inboxNotification.roomId);\n const { unread, date, aside, title, content, commentId } = useMemo(() => {\n const contents = generateInboxNotificationThreadContents(\n inboxNotification,\n thread,\n currentUserId ?? \"\"\n );\n\n switch (contents.type) {\n case \"comments\": {\n const reversedUserIds = [...contents.userIds].reverse();\n const firstUserId = reversedUserIds[0];\n\n const aside = <InboxNotificationAvatar userId={firstUserId} />;\n const title = $.INBOX_NOTIFICATION_THREAD_COMMENTS_LIST(\n <List\n values={reversedUserIds.map((userId, index) => (\n <User\n key={userId}\n userId={userId}\n capitalize={index === 0}\n replaceSelf\n />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={INBOX_NOTIFICATION_THREAD_MAX_COMMENTS - 1}\n />,\n showRoomName ? <Room roomId={thread.roomId} /> : undefined,\n reversedUserIds.length\n );\n const content = (\n <div className=\"lb-inbox-notification-comments\">\n {contents.comments.map((comment) => (\n <InboxNotificationComment\n key={comment.id}\n comment={comment}\n showHeader={contents.comments.length > 1}\n overrides={overrides}\n />\n ))}\n </div>\n );\n\n return {\n unread: contents.unread,\n date: contents.date,\n aside,\n title,\n content,\n threadId: thread.id,\n commentId: contents.comments[contents.comments.length - 1].id,\n };\n }\n\n case \"mention\": {\n const mentionUserId = contents.userIds[0];\n const mentionComment = contents.comments[0];\n\n const aside = <InboxNotificationAvatar userId={mentionUserId} />;\n const title = $.INBOX_NOTIFICATION_THREAD_MENTION(\n <User key={mentionUserId} userId={mentionUserId} capitalize />,\n showRoomName ? <Room roomId={thread.roomId} /> : undefined\n );\n const content = (\n <div className=\"lb-inbox-notification-comments\">\n <InboxNotificationComment\n key={mentionComment.id}\n comment={mentionComment}\n showHeader={false}\n showAttachments={showAttachments}\n showReactions={showReactions}\n />\n </div>\n );\n\n return {\n unread: contents.unread,\n date: contents.date,\n aside,\n title,\n content,\n threadId: thread.id,\n commentId: mentionComment.id,\n };\n }\n\n default:\n return assertNever(\n contents,\n \"Unexpected thread inbox notification type\"\n );\n }\n }, [\n $,\n currentUserId,\n inboxNotification,\n overrides,\n showRoomName,\n showAttachments,\n showReactions,\n thread,\n ]);\n // Add the thread ID and comment ID to the `href`.\n // And use URL from `resolveRoomsInfo` if `href` isn't set.\n const resolvedHref = useMemo(() => {\n const resolvedHref = href ?? info?.url;\n\n return resolvedHref\n ? generateURL(resolvedHref, undefined, commentId)\n : undefined;\n }, [commentId, href, info?.url]);\n\n return (\n <InboxNotificationLayout\n inboxNotification={inboxNotification}\n aside={aside}\n title={title}\n date={date}\n unread={unread}\n overrides={overrides}\n href={resolvedHref}\n showActions={showActions}\n markAsReadOnClick={false}\n {...props}\n ref={forwardedRef}\n >\n {content}\n </InboxNotificationLayout>\n );\n }\n);\n\n/**\n * Displays a text mention notification kind.\n */\nconst InboxNotificationTextMention = forwardRef<\n HTMLAnchorElement,\n InboxNotificationTextMentionProps\n>(\n (\n {\n inboxNotification,\n showActions = \"hover\",\n showRoomName = true,\n overrides,\n ...props\n },\n ref\n ) => {\n const $ = useOverrides(overrides);\n\n const unread = useMemo(() => {\n return (\n !inboxNotification.readAt ||\n inboxNotification.notifiedAt > inboxNotification.readAt\n );\n }, [inboxNotification.notifiedAt, inboxNotification.readAt]);\n\n return (\n <InboxNotificationLayout\n inboxNotification={inboxNotification}\n aside={<InboxNotificationAvatar userId={inboxNotification.createdBy} />}\n title={$.INBOX_NOTIFICATION_TEXT_MENTION(\n <User\n key={inboxNotification.createdBy}\n userId={inboxNotification.createdBy}\n capitalize\n />,\n showRoomName ? <Room roomId={inboxNotification.roomId} /> : undefined\n )}\n date={inboxNotification.notifiedAt}\n unread={unread}\n overrides={overrides}\n showActions={showActions}\n {...props}\n ref={ref}\n />\n );\n }\n);\n\n/**\n * Displays a custom notification kind.\n */\nconst InboxNotificationCustom = forwardRef<\n HTMLAnchorElement,\n InboxNotificationCustomProps\n>(\n (\n {\n inboxNotification,\n showActions = \"hover\",\n title,\n aside,\n children,\n overrides,\n ...props\n },\n forwardedRef\n ) => {\n const unread = useMemo(() => {\n return (\n !inboxNotification.readAt ||\n inboxNotification.notifiedAt > inboxNotification.readAt\n );\n }, [inboxNotification.notifiedAt, inboxNotification.readAt]);\n\n return (\n <InboxNotificationLayout\n inboxNotification={inboxNotification}\n aside={aside}\n title={title}\n date={inboxNotification.notifiedAt}\n unread={unread}\n overrides={overrides}\n showActions={showActions}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </InboxNotificationLayout>\n );\n }\n);\n\nconst InboxNotificationCustomMissing = forwardRef<\n HTMLAnchorElement,\n Omit<InboxNotificationCustomProps, \"children\" | \"title\" | \"aside\">\n>(({ inboxNotification, ...props }, forwardedRef) => {\n return (\n <InboxNotificationCustom\n inboxNotification={inboxNotification}\n {...props}\n title={\n <>\n Custom notification kind <code>{inboxNotification.kind}</code> is not\n handled\n </>\n }\n aside={\n <InboxNotificationIcon>\n <WarningIcon />\n </InboxNotificationIcon>\n }\n ref={forwardedRef}\n data-missing=\"\"\n >\n {/* TODO: Add link to the docs */}\n Notifications of this kind won’t be displayed in production. Use the{\" \"}\n <code>kinds</code> prop to define how they should be rendered.\n </InboxNotificationCustom>\n );\n});\n\n// Keeps track of which inbox notification kinds it has warned about already.\nconst inboxNotificationKindsWarnings: Set<string> = new Set();\n\n/**\n * Displays a single inbox notification.\n *\n * @example\n * <>\n * {inboxNotifications.map((inboxNotification) => (\n * <InboxNotification\n * key={inboxNotification.id}\n * inboxNotification={inboxNotification}\n * href={`/rooms/${inboxNotification.roomId}`\n * />\n * ))}\n * </>\n */\nexport const InboxNotification = Object.assign(\n forwardRef<HTMLAnchorElement, InboxNotificationProps>(\n ({ inboxNotification, kinds, ...props }, forwardedRef) => {\n switch (inboxNotification.kind) {\n case \"thread\": {\n const ResolvedInboxNotificationThread =\n kinds?.thread ?? InboxNotificationThread;\n\n return (\n <ResolvedInboxNotificationThread\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n\n case \"textMention\": {\n const ResolvedInboxNotificationTextMention =\n kinds?.textMention ?? InboxNotificationTextMention;\n\n return (\n <ResolvedInboxNotificationTextMention\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n\n default: {\n const ResolvedInboxNotificationCustom =\n kinds?.[inboxNotification.kind];\n\n if (!ResolvedInboxNotificationCustom) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!inboxNotificationKindsWarnings.has(inboxNotification.kind)) {\n inboxNotificationKindsWarnings.add(inboxNotification.kind);\n // TODO: Add link to the docs\n console.warn(\n `Custom notification kind \"${inboxNotification.kind}\" is not handled so notifications of this kind will not be displayed in production. Use the kinds prop to define how they should be rendered.`\n );\n }\n\n return (\n <InboxNotificationCustomMissing\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n } else {\n // Don't render anything in production if this inbox notification kind is not defined.\n return null;\n }\n }\n\n return (\n <ResolvedInboxNotificationCustom\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n }\n }\n ),\n {\n Thread: InboxNotificationThread,\n TextMention: InboxNotificationTextMention,\n Custom: InboxNotificationCustom,\n Icon: InboxNotificationIcon,\n Avatar: InboxNotificationAvatar,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuNA;AAAgC;AAK5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AAEA;AAAoB;AAEhB;AAEA;AAEA;AACE;AAAgD;AAClD;AACF;AACA;AACE;AACkB;AAClB;AACA;AACA;AACA;AACF;AAGF;AACE;AAAsB;AAGxB;AAAyC;AAErC;AACA;AAAsB;AACxB;AACC;AAGH;AACE;AACA;AACA;AAAiC;AAGnC;AACE;AAAgD;AAGlD;AACE;AAA4C;AAG9C;AAEK;AACY;AACT;AAEE;AACkB;AACpB;AACF;AACO;AACoB;AACE;AACpB;AACT;AACI;AACC;AAEM;AAAc;AACxB;AAAc;AACZ;AAAc;AACZ;AAAe;AACf;AAAc;AACZ;AAAe;AACb;AACW;AACV;AACU;AAGT;AACW;AACL;AAMV;AAAc;AACZ;AACO;AACQ;AACR;AAIC;AACW;AACD;AAER;AAAoB;AAIxB;AACW;AACD;AAER;AAAqB;AAG1B;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACD;AACM;AACF;AACC;AAEb;AAAuB;AAQrC;AAAc;AAGrB;AAGN;AAEA;AAA+B;AAC7B;AAEF;AACE;AACG;AAC8D;AACzD;AAGV;AAEA;AAAiC;AAC/B;AAEF;AACE;AACG;AACgE;AAC3D;AAGV;AAKA;AAAgC;AAK5B;AACE;AACA;AACe;AACC;AACE;AACJ;AACd;AACG;AAIL;AACA;AACA;AAKA;AACA;AACE;AAAiB;AACf;AACA;AACiB;AAGnB;AAAuB;AAEnB;AACA;AAEA;AAAe;AAAgC;AAC/C;AAAgB;AACb;AAEI;AACM;AACL;AACsB;AACX;AAEd;AACkB;AACgC;AACrD;AACgB;AAAoB;AAAa;AACjC;AAElB;AACG;AAAc;AAEV;AACc;AACb;AACuC;AACvC;AAMR;AAAO;AACY;AACF;AACf;AACA;AACA;AACiB;AAC0C;AAC7D;AACF;AAGE;AACA;AAEA;AAAe;AAAgC;AAC/C;AAAgB;AACb;AAAU;AAAuB;AAAyB;AAAC;AAC5C;AAAoB;AAAa;AAEnD;AACG;AAAc;AACZ;AACqB;AACX;AACG;AACZ;AACA;AAKN;AAAO;AACY;AACF;AACf;AACA;AACA;AACiB;AACS;AAC5B;AACF;AAGE;AAAO;AACL;AACA;AACF;AACJ;AACC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIF;AACE;AAEA;AAEI;AAGN;AACG;AACC;AACA;AACA;AACA;AACA;AACA;AACM;AACN;AACmB;AACf;AACC;AAGP;AAGN;AAKA;AAAqC;AAKjC;AACE;AACc;AACC;AACf;AACG;AAIL;AAEA;AACE;AAEmD;AAIrD;AACG;AACC;AACQ;AAAkD;AAAW;AAC5D;AACN;AACwB;AACG;AAChB;AACZ;AACgB;AAA+B;AAAa;AAC9D;AACwB;AACxB;AACA;AACA;AACI;AACJ;AACF;AAGN;AAKA;AAAgC;AAK5B;AACE;AACc;AACd;AACA;AACA;AACA;AACG;AAIL;AACE;AAEmD;AAIrD;AACG;AACC;AACA;AACA;AACwB;AACxB;AACA;AACA;AACI;AACC;AAGP;AAGN;AAEA;AAIE;AACG;AACC;AACI;AAKF;AAKA;AAEG;AACQ;AAOnB;AAGA;AAgBO;AAAiC;AACtC;AAEI;AAAgC;AAE5B;AAGA;AACG;AACC;AACI;AACC;AACP;AAEJ;AAGE;AAGA;AACG;AACC;AACI;AACC;AACP;AAEJ;AAGE;AAGA;AACE;AACE;AACE;AAEA;AAAQ;AACyC;AACjD;AAGF;AACG;AACC;AACI;AACC;AACP;AAIF;AAAO;AACT;AAGF;AACG;AACC;AACI;AACC;AACP;AAEJ;AACF;AACF;AACF;AACA;AACU;AACK;AACL;AACF;AACE;AAEZ;;"}
1
+ {"version":3,"file":"InboxNotification.mjs","sources":["../../src/components/InboxNotification.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n InboxNotificationCustomData,\n InboxNotificationData,\n InboxNotificationTextMentionData,\n InboxNotificationThreadData,\n KDAD,\n} from \"@liveblocks/core\";\nimport { assertNever, console } from \"@liveblocks/core\";\nimport {\n useDeleteInboxNotification,\n useInboxNotificationThread,\n useMarkInboxNotificationAsRead,\n useRoomInfo,\n} from \"@liveblocks/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { TooltipProvider } from \"@radix-ui/react-tooltip\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n ComponentType,\n MouseEvent as ReactMouseEvent,\n ReactNode,\n SyntheticEvent,\n} from \"react\";\nimport React, { forwardRef, useCallback, useMemo, useState } from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { useComponents } from \"../components\";\nimport { CheckIcon } from \"../icons/Check\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { WarningIcon } from \"../icons/Warning\";\nimport type {\n CommentOverrides,\n GlobalOverrides,\n InboxNotificationOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { SlotProp } from \"../types\";\nimport { classNames } from \"../utils/class-names\";\nimport { generateURL } from \"../utils/url\";\nimport { Avatar, type AvatarProps } from \"./internal/Avatar\";\nimport { Button } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport {\n generateInboxNotificationThreadContents,\n INBOX_NOTIFICATION_THREAD_MAX_COMMENTS,\n InboxNotificationComment,\n} from \"./internal/InboxNotificationThread\";\nimport { List } from \"./internal/List\";\nimport { Room } from \"./internal/Room\";\nimport { Tooltip } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\ntype ComponentTypeWithRef<\n T extends keyof JSX.IntrinsicElements,\n P,\n> = ComponentType<P & Pick<ComponentProps<T>, \"ref\">>;\n\ntype InboxNotificationKinds<KS extends KDAD = KDAD> = {\n // For some reason, we cannot directly use KDAD in the mapped type line\n // below, because it will result in '{}' rather than picking up the\n // definition from the user-provided 'ActivitiesData'. Might be an internal\n // TS optimization, so we're making it a param to defer the resolution.\n [K in KS]: ComponentTypeWithRef<\"a\", InboxNotificationCustomKindProps<K>>;\n} & {\n thread: ComponentTypeWithRef<\"a\", InboxNotificationThreadKindProps>;\n textMention: ComponentTypeWithRef<\"a\", InboxNotificationTextMentionKindProps>;\n};\n\ninterface InboxNotificationSharedProps {\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n}\n\nexport interface InboxNotificationProps\n extends Omit<ComponentPropsWithoutRef<\"a\">, \"title\">,\n InboxNotificationSharedProps {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationData;\n\n /**\n * Override specific kinds of inbox notifications.\n */\n kinds?: Partial<InboxNotificationKinds>;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides & InboxNotificationOverrides & CommentOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n}\n\nexport interface InboxNotificationThreadProps\n extends Omit<InboxNotificationProps, \"kinds\" | \"children\">,\n InboxNotificationSharedProps {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationThreadData;\n\n /**\n * Whether to show the room name in the title.\n */\n showRoomName?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n}\n\nexport interface InboxNotificationTextMentionProps\n extends Omit<InboxNotificationProps, \"kinds\">,\n InboxNotificationSharedProps {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationTextMentionData;\n\n /**\n * Whether to show the room name in the title.\n */\n showRoomName?: boolean;\n}\n\nexport interface InboxNotificationCustomProps\n extends Omit<InboxNotificationProps, \"kinds\">,\n InboxNotificationSharedProps,\n SlotProp {\n /**\n * The inbox notification to display.\n */\n inboxNotification: InboxNotificationCustomData;\n\n /**\n * The inbox notification's content.\n */\n children: ReactNode;\n\n /**\n * The inbox notification's title.\n */\n title: ReactNode;\n\n /**\n * The inbox notification's aside content.\n * Can be combined with `InboxNotification.Icon` or `InboxNotification.Avatar` to easily follow default styles.\n */\n aside?: ReactNode;\n\n /**\n * Whether to mark the inbox notification as read when clicked.\n */\n markAsReadOnClick?: boolean;\n}\n\nexport type InboxNotificationThreadKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationThreadData;\n};\n\nexport type InboxNotificationTextMentionKindProps = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationTextMentionData;\n};\n\nexport type InboxNotificationCustomKindProps<K extends KDAD = KDAD> = Omit<\n InboxNotificationProps,\n \"kinds\"\n> & {\n inboxNotification: InboxNotificationCustomData<K>;\n};\n\ninterface InboxNotificationLayoutProps\n extends Omit<ComponentPropsWithoutRef<\"a\">, \"title\">,\n InboxNotificationSharedProps,\n SlotProp {\n inboxNotification: InboxNotificationData;\n aside: ReactNode;\n title: ReactNode;\n date: Date | string | number;\n unread?: boolean;\n overrides?: Partial<GlobalOverrides & InboxNotificationOverrides>;\n components?: Partial<GlobalComponents>;\n markAsReadOnClick?: boolean;\n}\n\nexport type InboxNotificationIconProps = ComponentProps<\"div\">;\n\nexport type InboxNotificationAvatarProps = AvatarProps;\n\nconst InboxNotificationLayout = forwardRef<\n HTMLAnchorElement,\n InboxNotificationLayoutProps\n>(\n (\n {\n inboxNotification,\n children,\n aside,\n title,\n date,\n unread,\n markAsReadOnClick,\n onClick,\n href,\n showActions,\n overrides,\n components,\n className,\n asChild,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const { Anchor } = useComponents(components);\n const Component = asChild ? Slot : Anchor;\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const markInboxNotificationAsRead = useMarkInboxNotificationAsRead();\n const deleteInboxNotification = useDeleteInboxNotification();\n\n const handleClick = useCallback(\n (event: ReactMouseEvent<HTMLAnchorElement, MouseEvent>) => {\n onClick?.(event);\n\n const shouldMarkAsReadOnClick = markAsReadOnClick ?? Boolean(href);\n\n if (unread && shouldMarkAsReadOnClick) {\n markInboxNotificationAsRead(inboxNotification.id);\n }\n },\n [\n href,\n inboxNotification.id,\n markAsReadOnClick,\n markInboxNotificationAsRead,\n onClick,\n unread,\n ]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const preventDefaultAndStopPropagation = useCallback(\n (event: SyntheticEvent) => {\n event.preventDefault();\n event.stopPropagation();\n },\n []\n );\n\n const handleMoreClick = useCallback((event: ReactMouseEvent) => {\n event.preventDefault();\n event.stopPropagation();\n setMoreActionOpen((open) => !open);\n }, []);\n\n const handleMarkAsRead = useCallback(() => {\n markInboxNotificationAsRead(inboxNotification.id);\n }, [inboxNotification.id, markInboxNotificationAsRead]);\n\n const handleDelete = useCallback(() => {\n deleteInboxNotification(inboxNotification.id);\n }, [inboxNotification.id, deleteInboxNotification]);\n\n return (\n <TooltipProvider>\n <Component\n className={classNames(\n \"lb-root lb-inbox-notification\",\n showActions === \"hover\" &&\n \"lb-inbox-notification:show-actions-hover\",\n isMoreActionOpen && \"lb-inbox-notification:action-open\",\n className\n )}\n dir={$.dir}\n data-unread={unread ? \"\" : undefined}\n data-kind={inboxNotification.kind}\n onClick={handleClick}\n href={href}\n {...props}\n ref={forwardedRef}\n >\n {aside && <div className=\"lb-inbox-notification-aside\">{aside}</div>}\n <div className=\"lb-inbox-notification-content\">\n <div className=\"lb-inbox-notification-header\">\n <span className=\"lb-inbox-notification-title\">{title}</span>\n <div className=\"lb-inbox-notification-details\">\n <span className=\"lb-inbox-notification-details-labels\">\n <Timestamp\n locale={$.locale}\n date={date}\n className=\"lb-inbox-notification-date\"\n />\n {unread && (\n <span\n className=\"lb-inbox-notification-unread-indicator\"\n role=\"presentation\"\n />\n )}\n </span>\n </div>\n {showActions && (\n <div className=\"lb-inbox-notification-actions\">\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n {unread ? (\n <DropdownItem\n onSelect={handleMarkAsRead}\n onClick={stopPropagation}\n >\n <CheckIcon className=\"lb-dropdown-item-icon\" />\n {$.INBOX_NOTIFICATION_MARK_AS_READ}\n </DropdownItem>\n ) : null}\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n >\n <DeleteIcon className=\"lb-dropdown-item-icon\" />\n {$.INBOX_NOTIFICATION_DELETE}\n </DropdownItem>\n </>\n }\n >\n <Tooltip content={$.INBOX_NOTIFICATION_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-inbox-notification-action\"\n onClick={handleMoreClick}\n onPointerDown={preventDefaultAndStopPropagation}\n onPointerUp={preventDefaultAndStopPropagation}\n aria-label={$.INBOX_NOTIFICATION_MORE}\n >\n <EllipsisIcon className=\"lb-button-icon\" />\n </Button>\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n </div>\n )}\n </div>\n <div className=\"lb-inbox-notification-body\">{children}</div>\n </div>\n </Component>\n </TooltipProvider>\n );\n }\n);\n\nfunction InboxNotificationIcon({\n className,\n ...props\n}: InboxNotificationIconProps) {\n return (\n <div\n className={classNames(\"lb-inbox-notification-icon\", className)}\n {...props}\n />\n );\n}\n\nfunction InboxNotificationAvatar({\n className,\n ...props\n}: InboxNotificationAvatarProps) {\n return (\n <Avatar\n className={classNames(\"lb-inbox-notification-avatar\", className)}\n {...props}\n />\n );\n}\n\n/**\n * Displays a thread inbox notification.\n */\nconst InboxNotificationThread = forwardRef<\n HTMLAnchorElement,\n InboxNotificationThreadProps\n>(\n (\n {\n inboxNotification,\n href,\n showRoomName = true,\n showReactions = true,\n showAttachments = true,\n showActions = \"hover\",\n overrides,\n ...props\n },\n forwardedRef\n ) => {\n const $ = useOverrides(overrides);\n const thread = useInboxNotificationThread(inboxNotification.id);\n const currentUserId = useCurrentUserId();\n // TODO: If you provide `href` (or plan to), we shouldn't run this hook. We should find a way to conditionally run it.\n // Because of batching and the fact that the same hook will be called within <Room /> in the notification's title,\n // it's not a big deal, the only scenario where it would be superfluous would be if the user provides their own\n // `href` AND disables room names in the title via `showRoomName={false}`.\n const { info } = useRoomInfo(inboxNotification.roomId);\n const { unread, date, aside, title, content, commentId } = useMemo(() => {\n const contents = generateInboxNotificationThreadContents(\n inboxNotification,\n thread,\n currentUserId ?? \"\"\n );\n\n switch (contents.type) {\n case \"comments\": {\n const reversedUserIds = [...contents.userIds].reverse();\n const firstUserId = reversedUserIds[0];\n\n const aside = <InboxNotificationAvatar userId={firstUserId} />;\n const title = $.INBOX_NOTIFICATION_THREAD_COMMENTS_LIST(\n <List\n values={reversedUserIds.map((userId, index) => (\n <User\n key={userId}\n userId={userId}\n capitalize={index === 0}\n replaceSelf\n />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={INBOX_NOTIFICATION_THREAD_MAX_COMMENTS - 1}\n />,\n showRoomName ? <Room roomId={thread.roomId} /> : undefined,\n reversedUserIds.length\n );\n const content = (\n <div className=\"lb-inbox-notification-comments\">\n {contents.comments.map((comment) => (\n <InboxNotificationComment\n key={comment.id}\n comment={comment}\n showHeader={contents.comments.length > 1}\n showAttachments={showAttachments}\n showReactions={showReactions}\n overrides={overrides}\n />\n ))}\n </div>\n );\n\n return {\n unread: contents.unread,\n date: contents.date,\n aside,\n title,\n content,\n threadId: thread.id,\n commentId: contents.comments[contents.comments.length - 1].id,\n };\n }\n\n case \"mention\": {\n const mentionUserId = contents.userIds[0];\n const mentionComment = contents.comments[0];\n\n const aside = <InboxNotificationAvatar userId={mentionUserId} />;\n const title = $.INBOX_NOTIFICATION_THREAD_MENTION(\n <User key={mentionUserId} userId={mentionUserId} capitalize />,\n showRoomName ? <Room roomId={thread.roomId} /> : undefined\n );\n const content = (\n <div className=\"lb-inbox-notification-comments\">\n <InboxNotificationComment\n key={mentionComment.id}\n comment={mentionComment}\n showHeader={false}\n showAttachments={showAttachments}\n showReactions={showReactions}\n overrides={overrides}\n />\n </div>\n );\n\n return {\n unread: contents.unread,\n date: contents.date,\n aside,\n title,\n content,\n threadId: thread.id,\n commentId: mentionComment.id,\n };\n }\n\n default:\n return assertNever(\n contents,\n \"Unexpected thread inbox notification type\"\n );\n }\n }, [\n $,\n currentUserId,\n inboxNotification,\n overrides,\n showRoomName,\n showAttachments,\n showReactions,\n thread,\n ]);\n // Add the thread ID and comment ID to the `href`.\n // And use URL from `resolveRoomsInfo` if `href` isn't set.\n const resolvedHref = useMemo(() => {\n const resolvedHref = href ?? info?.url;\n\n return resolvedHref\n ? generateURL(resolvedHref, undefined, commentId)\n : undefined;\n }, [commentId, href, info?.url]);\n\n return (\n <InboxNotificationLayout\n inboxNotification={inboxNotification}\n aside={aside}\n title={title}\n date={date}\n unread={unread}\n overrides={overrides}\n href={resolvedHref}\n showActions={showActions}\n markAsReadOnClick={false}\n {...props}\n ref={forwardedRef}\n >\n {content}\n </InboxNotificationLayout>\n );\n }\n);\n\n/**\n * Displays a text mention notification kind.\n */\nconst InboxNotificationTextMention = forwardRef<\n HTMLAnchorElement,\n InboxNotificationTextMentionProps\n>(\n (\n {\n inboxNotification,\n showActions = \"hover\",\n showRoomName = true,\n overrides,\n ...props\n },\n ref\n ) => {\n const $ = useOverrides(overrides);\n\n const unread = useMemo(() => {\n return (\n !inboxNotification.readAt ||\n inboxNotification.notifiedAt > inboxNotification.readAt\n );\n }, [inboxNotification.notifiedAt, inboxNotification.readAt]);\n\n return (\n <InboxNotificationLayout\n inboxNotification={inboxNotification}\n aside={<InboxNotificationAvatar userId={inboxNotification.createdBy} />}\n title={$.INBOX_NOTIFICATION_TEXT_MENTION(\n <User\n key={inboxNotification.createdBy}\n userId={inboxNotification.createdBy}\n capitalize\n />,\n showRoomName ? <Room roomId={inboxNotification.roomId} /> : undefined\n )}\n date={inboxNotification.notifiedAt}\n unread={unread}\n overrides={overrides}\n showActions={showActions}\n {...props}\n ref={ref}\n />\n );\n }\n);\n\n/**\n * Displays a custom notification kind.\n */\nconst InboxNotificationCustom = forwardRef<\n HTMLAnchorElement,\n InboxNotificationCustomProps\n>(\n (\n {\n inboxNotification,\n showActions = \"hover\",\n title,\n aside,\n children,\n overrides,\n ...props\n },\n forwardedRef\n ) => {\n const unread = useMemo(() => {\n return (\n !inboxNotification.readAt ||\n inboxNotification.notifiedAt > inboxNotification.readAt\n );\n }, [inboxNotification.notifiedAt, inboxNotification.readAt]);\n\n return (\n <InboxNotificationLayout\n inboxNotification={inboxNotification}\n aside={aside}\n title={title}\n date={inboxNotification.notifiedAt}\n unread={unread}\n overrides={overrides}\n showActions={showActions}\n {...props}\n ref={forwardedRef}\n >\n {children}\n </InboxNotificationLayout>\n );\n }\n);\n\nconst InboxNotificationCustomMissing = forwardRef<\n HTMLAnchorElement,\n Omit<InboxNotificationCustomProps, \"children\" | \"title\" | \"aside\">\n>(({ inboxNotification, ...props }, forwardedRef) => {\n return (\n <InboxNotificationCustom\n inboxNotification={inboxNotification}\n {...props}\n title={\n <>\n Custom notification kind <code>{inboxNotification.kind}</code> is not\n handled\n </>\n }\n aside={\n <InboxNotificationIcon>\n <WarningIcon />\n </InboxNotificationIcon>\n }\n ref={forwardedRef}\n data-missing=\"\"\n >\n {/* TODO: Add link to the docs */}\n Notifications of this kind won’t be displayed in production. Use the{\" \"}\n <code>kinds</code> prop to define how they should be rendered.\n </InboxNotificationCustom>\n );\n});\n\n// Keeps track of which inbox notification kinds it has warned about already.\nconst inboxNotificationKindsWarnings: Set<string> = new Set();\n\n/**\n * Displays a single inbox notification.\n *\n * @example\n * <>\n * {inboxNotifications.map((inboxNotification) => (\n * <InboxNotification\n * key={inboxNotification.id}\n * inboxNotification={inboxNotification}\n * href={`/rooms/${inboxNotification.roomId}`\n * />\n * ))}\n * </>\n */\nexport const InboxNotification = Object.assign(\n forwardRef<HTMLAnchorElement, InboxNotificationProps>(\n ({ inboxNotification, kinds, ...props }, forwardedRef) => {\n switch (inboxNotification.kind) {\n case \"thread\": {\n const ResolvedInboxNotificationThread =\n kinds?.thread ?? InboxNotificationThread;\n\n return (\n <ResolvedInboxNotificationThread\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n\n case \"textMention\": {\n const ResolvedInboxNotificationTextMention =\n kinds?.textMention ?? InboxNotificationTextMention;\n\n return (\n <ResolvedInboxNotificationTextMention\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n\n default: {\n const ResolvedInboxNotificationCustom =\n kinds?.[inboxNotification.kind];\n\n if (!ResolvedInboxNotificationCustom) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!inboxNotificationKindsWarnings.has(inboxNotification.kind)) {\n inboxNotificationKindsWarnings.add(inboxNotification.kind);\n // TODO: Add link to the docs\n console.warn(\n `Custom notification kind \"${inboxNotification.kind}\" is not handled so notifications of this kind will not be displayed in production. Use the kinds prop to define how they should be rendered.`\n );\n }\n\n return (\n <InboxNotificationCustomMissing\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n } else {\n // Don't render anything in production if this inbox notification kind is not defined.\n return null;\n }\n }\n\n return (\n <ResolvedInboxNotificationCustom\n inboxNotification={inboxNotification}\n {...props}\n ref={forwardedRef}\n />\n );\n }\n }\n }\n ),\n {\n Thread: InboxNotificationThread,\n TextMention: InboxNotificationTextMention,\n Custom: InboxNotificationCustom,\n Icon: InboxNotificationIcon,\n Avatar: InboxNotificationAvatar,\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAuNA;AAAgC;AAK5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AAEA;AAAoB;AAEhB;AAEA;AAEA;AACE;AAAgD;AAClD;AACF;AACA;AACE;AACkB;AAClB;AACA;AACA;AACA;AACF;AAGF;AACE;AAAsB;AAGxB;AAAyC;AAErC;AACA;AAAsB;AACxB;AACC;AAGH;AACE;AACA;AACA;AAAiC;AAGnC;AACE;AAAgD;AAGlD;AACE;AAA4C;AAG9C;AAEK;AACY;AACT;AAEE;AACkB;AACpB;AACF;AACO;AACoB;AACE;AACpB;AACT;AACI;AACC;AAEM;AAAc;AACxB;AAAc;AACZ;AAAc;AACZ;AAAe;AACf;AAAc;AACZ;AAAe;AACb;AACW;AACV;AACU;AAGT;AACW;AACL;AAMV;AAAc;AACZ;AACO;AACQ;AACR;AAIC;AACW;AACD;AAER;AAAoB;AAIxB;AACW;AACD;AAER;AAAqB;AAG1B;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACD;AACM;AACF;AACC;AAEb;AAAuB;AAQrC;AAAc;AAGrB;AAGN;AAEA;AAA+B;AAC7B;AAEF;AACE;AACG;AAC8D;AACzD;AAGV;AAEA;AAAiC;AAC/B;AAEF;AACE;AACG;AACgE;AAC3D;AAGV;AAKA;AAAgC;AAK5B;AACE;AACA;AACe;AACC;AACE;AACJ;AACd;AACG;AAIL;AACA;AACA;AAKA;AACA;AACE;AAAiB;AACf;AACA;AACiB;AAGnB;AAAuB;AAEnB;AACA;AAEA;AAAe;AAAgC;AAC/C;AAAgB;AACb;AAEI;AACM;AACL;AACsB;AACX;AAEd;AACkB;AACgC;AACrD;AACgB;AAAoB;AAAa;AACjC;AAElB;AACG;AAAc;AAEV;AACc;AACb;AACuC;AACvC;AACA;AACA;AAMR;AAAO;AACY;AACF;AACf;AACA;AACA;AACiB;AAC0C;AAC7D;AACF;AAGE;AACA;AAEA;AAAe;AAAgC;AAC/C;AAAgB;AACb;AAAU;AAAuB;AAAyB;AAAC;AAC5C;AAAoB;AAAa;AAEnD;AACG;AAAc;AACZ;AACqB;AACX;AACG;AACZ;AACA;AACA;AAKN;AAAO;AACY;AACF;AACf;AACA;AACA;AACiB;AACS;AAC5B;AACF;AAGE;AAAO;AACL;AACA;AACF;AACJ;AACC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAIF;AACE;AAEA;AAEI;AAGN;AACG;AACC;AACA;AACA;AACA;AACA;AACA;AACM;AACN;AACmB;AACf;AACC;AAGP;AAGN;AAKA;AAAqC;AAKjC;AACE;AACc;AACC;AACf;AACG;AAIL;AAEA;AACE;AAEmD;AAIrD;AACG;AACC;AACQ;AAAkD;AAAW;AAC5D;AACN;AACwB;AACG;AAChB;AACZ;AACgB;AAA+B;AAAa;AAC9D;AACwB;AACxB;AACA;AACA;AACI;AACJ;AACF;AAGN;AAKA;AAAgC;AAK5B;AACE;AACc;AACd;AACA;AACA;AACA;AACG;AAIL;AACE;AAEmD;AAIrD;AACG;AACC;AACA;AACA;AACwB;AACxB;AACA;AACA;AACI;AACC;AAGP;AAGN;AAEA;AAIE;AACG;AACC;AACI;AAKF;AAKA;AAEG;AACQ;AAOnB;AAGA;AAgBO;AAAiC;AACtC;AAEI;AAAgC;AAE5B;AAGA;AACG;AACC;AACI;AACC;AACP;AAEJ;AAGE;AAGA;AACG;AACC;AACI;AACC;AACP;AAEJ;AAGE;AAGA;AACE;AACE;AACE;AAEA;AAAQ;AACyC;AACjD;AAGF;AACG;AACC;AACI;AACC;AACP;AAIF;AAAO;AACT;AAGF;AACG;AACC;AACI;AACC;AACP;AAEJ;AACF;AACF;AACF;AACA;AACU;AACK;AACL;AACF;AACE;AAEZ;;"}
@@ -18,19 +18,18 @@ var classNames = require('../../utils/class-names.js');
18
18
  var formatFileSize = require('../../utils/format-file-size.js');
19
19
  var Tooltip = require('./Tooltip.js');
20
20
 
21
- const IMAGE_PREVIEW_MAX_SIZE = 50 * 1024 * 1024;
22
21
  const fileExtensionRegex = /^(.+?)(\.[^.]+)?$/;
23
22
  function splitFileName(name) {
24
23
  const match = name.match(fileExtensionRegex);
25
24
  return { base: match?.[1] ?? name, extension: match?.[2] };
26
25
  }
27
- function getFileAttachmentIconGlyph(mimeType) {
28
- if (mimeType === "application/zip" || mimeType === "application/x-rar-compressed" || mimeType === "application/x-7z-compressed" || mimeType === "application/x-zip-compressed" || mimeType === "application/x-tar" || mimeType === "application/gzip") {
26
+ function getAttachmentIconGlyph(mimeType) {
27
+ 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
28
  return /* @__PURE__ */ React.createElement("path", {
30
29
  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
30
  });
32
31
  }
33
- if (mimeType.startsWith("text/")) {
32
+ if (mimeType.startsWith("text/") || mimeType.startsWith("font/") || mimeType.startsWith("application/")) {
34
33
  return /* @__PURE__ */ React.createElement("path", {
35
34
  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
35
  });
@@ -52,11 +51,8 @@ function getFileAttachmentIconGlyph(mimeType) {
52
51
  }
53
52
  return null;
54
53
  }
55
- const FileAttachmentIcon = React.memo(({ mimeType }) => {
56
- const iconGlyph = React.useMemo(
57
- () => getFileAttachmentIconGlyph(mimeType),
58
- [mimeType]
59
- );
54
+ const AttachmentFileIcon = React.memo(({ mimeType }) => {
55
+ const iconGlyph = React.useMemo(() => getAttachmentIconGlyph(mimeType), [mimeType]);
60
56
  return /* @__PURE__ */ React.createElement("svg", {
61
57
  className: "lb-attachment-icon",
62
58
  width: 30,
@@ -79,77 +75,101 @@ const FileAttachmentIcon = React.memo(({ mimeType }) => {
79
75
  className: "lb-attachment-icon-glyph"
80
76
  }, iconGlyph));
81
77
  });
82
- function FileAttachmentImagePreview({ url }) {
78
+ function AttachmentImagePreview({
79
+ attachment
80
+ }) {
81
+ const { url } = react.useAttachmentUrl(attachment.id);
83
82
  const [isLoaded, setLoaded] = React.useState(false);
84
83
  const handleLoad = React.useCallback(() => {
85
84
  setLoaded(true);
86
85
  }, []);
87
- return url ? /* @__PURE__ */ React.createElement("div", {
88
- className: "lb-attachment-preview-image",
86
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, !isLoaded ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : null, url ? /* @__PURE__ */ React.createElement("div", {
87
+ className: "lb-attachment-preview-media",
89
88
  "data-hidden": !isLoaded ? "" : void 0
90
89
  }, /* @__PURE__ */ React.createElement("img", {
91
90
  src: url,
92
91
  loading: "lazy",
93
92
  onLoad: handleLoad
94
- })) : null;
95
- }
96
- function FileAttachmentLocalImagePreview({
97
- attachment
98
- }) {
99
- const [url, setUrl] = React.useState();
100
- React.useEffect(() => {
101
- const url2 = URL.createObjectURL(attachment.file);
102
- setUrl(url2);
103
- return () => {
104
- URL.revokeObjectURL(url2);
105
- };
106
- }, [attachment.file]);
107
- return /* @__PURE__ */ React.createElement(FileAttachmentImagePreview, {
108
- url
109
- });
93
+ })) : null);
110
94
  }
111
- function FileAttachmentRemoteImagePreview({
95
+ function AttachmentVideoPreview({
112
96
  attachment
113
97
  }) {
114
98
  const { url } = react.useAttachmentUrl(attachment.id);
115
- return /* @__PURE__ */ React.createElement(FileAttachmentImagePreview, {
116
- url
117
- });
99
+ const [isLoaded, setLoaded] = React.useState(false);
100
+ const handleLoad = React.useCallback(() => {
101
+ setLoaded(true);
102
+ }, []);
103
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, !isLoaded ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : null, url ? /* @__PURE__ */ React.createElement("div", {
104
+ className: "lb-attachment-preview-media",
105
+ "data-hidden": !isLoaded ? "" : void 0
106
+ }, /* @__PURE__ */ React.createElement("video", {
107
+ src: url,
108
+ onLoadedData: handleLoad
109
+ })) : null);
118
110
  }
119
- function FileAttachmentPreview({
111
+ function AttachmentPreview({
120
112
  attachment
121
113
  }) {
122
114
  const isInsideRoom = react.useIsInsideRoom();
123
- const isLocal = attachment.type === "localAttachment";
124
- if (attachment.mimeType.startsWith("image/") && attachment.size < IMAGE_PREVIEW_MAX_SIZE) {
125
- if (isLocal) {
126
- return /* @__PURE__ */ React.createElement(FileAttachmentLocalImagePreview, {
115
+ const isUploaded = attachment.type === "attachment" || attachment.status === "uploaded";
116
+ if (isUploaded && isInsideRoom) {
117
+ if (attachment.mimeType.startsWith("image/")) {
118
+ return /* @__PURE__ */ React.createElement(AttachmentImagePreview, {
127
119
  attachment
128
120
  });
129
- } else if (isInsideRoom) {
130
- return /* @__PURE__ */ React.createElement(FileAttachmentRemoteImagePreview, {
121
+ }
122
+ if (attachment.mimeType.startsWith("video/")) {
123
+ return /* @__PURE__ */ React.createElement(AttachmentVideoPreview, {
131
124
  attachment
132
125
  });
133
126
  }
134
127
  }
135
- return null;
128
+ return /* @__PURE__ */ React.createElement(AttachmentFileIcon, {
129
+ mimeType: attachment.mimeType
130
+ });
136
131
  }
137
- function FileAttachment({
138
- attachment,
139
- overrides: overrides$1,
140
- onClick,
141
- onDeleteClick,
142
- preventFocusOnDelete,
143
- className,
144
- ...props
132
+ function AttachmentName({
133
+ attachment
145
134
  }) {
135
+ const { base: fileBaseName, extension: fileExtension } = React.useMemo(() => {
136
+ return splitFileName(attachment.name);
137
+ }, [attachment.name]);
138
+ return /* @__PURE__ */ React.createElement("span", {
139
+ className: "lb-attachment-name",
140
+ title: attachment.name
141
+ }, /* @__PURE__ */ React.createElement("span", {
142
+ className: "lb-attachment-name-base"
143
+ }, fileBaseName), fileExtension && /* @__PURE__ */ React.createElement("span", {
144
+ className: "lb-attachment-name-extension"
145
+ }, fileExtension));
146
+ }
147
+ function useClickOnKeyDown(onKeyDown) {
148
+ const handleKeyDown = React.useCallback(
149
+ (event) => {
150
+ onKeyDown?.(event);
151
+ if (event.isDefaultPrevented()) {
152
+ return;
153
+ }
154
+ if (event.key === "Enter" || event.key === " ") {
155
+ event.preventDefault();
156
+ const clickEvent = new MouseEvent("click", {
157
+ bubbles: true,
158
+ cancelable: true,
159
+ view: window
160
+ });
161
+ event.target.dispatchEvent(clickEvent);
162
+ }
163
+ },
164
+ [onKeyDown]
165
+ );
166
+ return handleKeyDown;
167
+ }
168
+ function useAttachmentContent(attachment, overrides$1) {
146
169
  const $ = overrides.useOverrides(overrides$1);
147
170
  const composerAttachmentsContext = contexts.useComposerAttachmentsContextOrNull();
148
171
  const isInComposer = Boolean(composerAttachmentsContext);
149
172
  const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;
150
- const { base: fileBaseName, extension: fileExtension } = React.useMemo(() => {
151
- return splitFileName(attachment.name);
152
- }, [attachment.name]);
153
173
  const status = attachment.type === "localAttachment" ? attachment.status : void 0;
154
174
  const isUploading = status === "uploading";
155
175
  const isError = status === "error";
@@ -166,6 +186,24 @@ function FileAttachment({
166
186
  description = formatFileSize.formatFileSize(attachment.size, $.locale);
167
187
  }
168
188
  const deleteLabel = isInComposer ? $.COMPOSER_REMOVE_ATTACHMENT : $.COMMENT_DELETE_ATTACHMENT;
189
+ return {
190
+ isUploading,
191
+ isError,
192
+ description,
193
+ deleteLabel
194
+ };
195
+ }
196
+ function MediaAttachment({
197
+ attachment,
198
+ overrides,
199
+ onClick,
200
+ onDeleteClick,
201
+ preventFocusOnDelete,
202
+ className,
203
+ onKeyDown,
204
+ ...props
205
+ }) {
206
+ const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides);
169
207
  const handleDeletePointerDown = React.useCallback(
170
208
  (event) => {
171
209
  if (preventFocusOnDelete) {
@@ -174,17 +212,56 @@ function FileAttachment({
174
212
  },
175
213
  [preventFocusOnDelete]
176
214
  );
177
- const handleKeyDown = React.useCallback((event) => {
178
- if (event.key === "Enter" || event.key === " ") {
179
- event.preventDefault();
180
- const clickEvent = new MouseEvent("click", {
181
- bubbles: true,
182
- cancelable: true,
183
- view: window
184
- });
185
- event.target.dispatchEvent(clickEvent);
186
- }
187
- }, []);
215
+ const handleKeyDown = useClickOnKeyDown(onKeyDown);
216
+ return /* @__PURE__ */ React.createElement("div", {
217
+ className: classNames.classNames("lb-attachment lb-media-attachment", className),
218
+ "data-error": isError ? "" : void 0,
219
+ ...props,
220
+ role: onClick ? "button" : void 0,
221
+ onClick,
222
+ tabIndex: onClick ? 0 : -1,
223
+ onKeyDown: onClick ? handleKeyDown : void 0
224
+ }, /* @__PURE__ */ React.createElement("div", {
225
+ className: "lb-attachment-preview"
226
+ }, isUploading ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : isError ? /* @__PURE__ */ React.createElement(Warning.WarningIcon, null) : /* @__PURE__ */ React.createElement(AttachmentPreview, {
227
+ attachment
228
+ })), /* @__PURE__ */ React.createElement("div", {
229
+ className: "lb-attachment-details"
230
+ }, /* @__PURE__ */ React.createElement(AttachmentName, {
231
+ attachment
232
+ }), /* @__PURE__ */ React.createElement("span", {
233
+ className: "lb-attachment-description",
234
+ title: description
235
+ }, description)), onDeleteClick && /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
236
+ content: deleteLabel
237
+ }, /* @__PURE__ */ React.createElement("button", {
238
+ type: "button",
239
+ className: "lb-attachment-delete",
240
+ onClick: onDeleteClick,
241
+ onPointerDown: handleDeletePointerDown,
242
+ "aria-label": deleteLabel
243
+ }, /* @__PURE__ */ React.createElement(Cross.CrossIcon, null))));
244
+ }
245
+ function FileAttachment({
246
+ attachment,
247
+ overrides,
248
+ onClick,
249
+ onDeleteClick,
250
+ preventFocusOnDelete,
251
+ className,
252
+ onKeyDown,
253
+ ...props
254
+ }) {
255
+ const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides);
256
+ const handleDeletePointerDown = React.useCallback(
257
+ (event) => {
258
+ if (preventFocusOnDelete) {
259
+ event.preventDefault();
260
+ }
261
+ },
262
+ [preventFocusOnDelete]
263
+ );
264
+ const handleKeyDown = useClickOnKeyDown(onKeyDown);
188
265
  return /* @__PURE__ */ React.createElement("div", {
189
266
  className: classNames.classNames("lb-attachment lb-file-attachment", className),
190
267
  "data-error": isError ? "" : void 0,
@@ -195,20 +272,13 @@ function FileAttachment({
195
272
  onKeyDown: onClick ? handleKeyDown : void 0
196
273
  }, /* @__PURE__ */ React.createElement("div", {
197
274
  className: "lb-attachment-preview"
198
- }, isUploading ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : isError ? /* @__PURE__ */ React.createElement(Warning.WarningIcon, null) : /* @__PURE__ */ React.createElement(React.Fragment, null, !isError ? /* @__PURE__ */ React.createElement(FileAttachmentPreview, {
275
+ }, isUploading ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : isError ? /* @__PURE__ */ React.createElement(Warning.WarningIcon, null) : /* @__PURE__ */ React.createElement(AttachmentPreview, {
199
276
  attachment
200
- }) : null, /* @__PURE__ */ React.createElement(FileAttachmentIcon, {
201
- mimeType: attachment.mimeType
202
- }))), /* @__PURE__ */ React.createElement("div", {
277
+ })), /* @__PURE__ */ React.createElement("div", {
203
278
  className: "lb-attachment-details"
204
- }, /* @__PURE__ */ React.createElement("span", {
205
- className: "lb-attachment-name",
206
- title: attachment.name
207
- }, /* @__PURE__ */ React.createElement("span", {
208
- className: "lb-attachment-name-base"
209
- }, fileBaseName), fileExtension && /* @__PURE__ */ React.createElement("span", {
210
- className: "lb-attachment-name-extension"
211
- }, fileExtension)), /* @__PURE__ */ React.createElement("span", {
279
+ }, /* @__PURE__ */ React.createElement(AttachmentName, {
280
+ attachment
281
+ }), /* @__PURE__ */ React.createElement("span", {
212
282
  className: "lb-attachment-description",
213
283
  title: description
214
284
  }, description)), onDeleteClick && /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
@@ -221,6 +291,23 @@ function FileAttachment({
221
291
  "aria-label": deleteLabel
222
292
  }, /* @__PURE__ */ React.createElement(Cross.CrossIcon, null))));
223
293
  }
294
+ function separateMediaAttachments(attachments) {
295
+ const mediaAttachments = [];
296
+ const fileAttachments = [];
297
+ for (const attachment of attachments) {
298
+ if (attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/")) {
299
+ mediaAttachments.push(attachment);
300
+ } else {
301
+ fileAttachments.push(attachment);
302
+ }
303
+ }
304
+ return {
305
+ mediaAttachments,
306
+ fileAttachments
307
+ };
308
+ }
224
309
 
225
310
  exports.FileAttachment = FileAttachment;
311
+ exports.MediaAttachment = MediaAttachment;
312
+ exports.separateMediaAttachments = separateMediaAttachments;
226
313
  //# sourceMappingURL=Attachment.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Attachment.js","sources":["../../../src/components/internal/Attachment.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n CommentAttachment,\n CommentLocalAttachment,\n CommentMixedAttachment,\n} 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, useEffect, useMemo, useState } from \"react\";\n\nimport { CrossIcon } from \"../../icons/Cross\";\nimport { SpinnerIcon } from \"../../icons/Spinner\";\nimport { WarningIcon } from \"../../icons/Warning\";\nimport type { GlobalOverrides } 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\ninterface FileAttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n onDeleteClick?: MouseEventHandler<HTMLButtonElement>;\n preventFocusOnDelete?: boolean;\n overrides?: Partial<GlobalOverrides>;\n}\n\nconst IMAGE_PREVIEW_MAX_SIZE = 50 * 1024 * 1024; // 50 MB\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 getFileAttachmentIconGlyph(mimeType: string) {\n if (\n mimeType === \"application/zip\" ||\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/gzip\"\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 (mimeType.startsWith(\"text/\")) {\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 FileAttachmentIcon = memo(({ mimeType }: { mimeType: string }) => {\n const iconGlyph = useMemo(\n () => getFileAttachmentIconGlyph(mimeType),\n [mimeType]\n );\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 FileAttachmentImagePreview({ url }: { url?: string }) {\n const [isLoaded, setLoaded] = useState(false);\n\n const handleLoad = useCallback(() => {\n setLoaded(true);\n }, []);\n\n return url ? (\n <div\n className=\"lb-attachment-preview-image\"\n data-hidden={!isLoaded ? \"\" : undefined}\n >\n <img src={url} loading=\"lazy\" onLoad={handleLoad} />\n </div>\n ) : null;\n}\n\nfunction FileAttachmentLocalImagePreview({\n attachment,\n}: {\n attachment: CommentLocalAttachment;\n}) {\n const [url, setUrl] = useState<string>();\n\n useEffect(() => {\n const url = URL.createObjectURL(attachment.file);\n\n setUrl(url);\n\n return () => {\n URL.revokeObjectURL(url);\n };\n }, [attachment.file]);\n\n return <FileAttachmentImagePreview url={url} />;\n}\n\nfunction FileAttachmentRemoteImagePreview({\n attachment,\n}: {\n attachment: CommentAttachment;\n}) {\n const { url } = useAttachmentUrl(attachment.id);\n\n return <FileAttachmentImagePreview url={url} />;\n}\n\nfunction FileAttachmentPreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const isInsideRoom = useIsInsideRoom();\n const isLocal = attachment.type === \"localAttachment\";\n\n if (\n attachment.mimeType.startsWith(\"image/\") &&\n attachment.size < IMAGE_PREVIEW_MAX_SIZE\n ) {\n if (isLocal) {\n return <FileAttachmentLocalImagePreview attachment={attachment} />;\n } else if (isInsideRoom) {\n return <FileAttachmentRemoteImagePreview attachment={attachment} />;\n }\n }\n\n return null;\n}\n\nexport function FileAttachment({\n attachment,\n overrides,\n onClick,\n onDeleteClick,\n preventFocusOnDelete,\n className,\n ...props\n}: FileAttachmentProps) {\n const $ = useOverrides(overrides);\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n const isInComposer = Boolean(composerAttachmentsContext);\n const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;\n const { base: fileBaseName, extension: fileExtension } = useMemo(() => {\n return splitFileName(attachment.name);\n }, [attachment.name]);\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 const handleDeletePointerDown = useCallback(\n (event: PointerEvent<HTMLButtonElement>) => {\n if (preventFocusOnDelete) {\n event.preventDefault();\n }\n },\n [preventFocusOnDelete]\n );\n\n const handleKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {\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\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 <>\n {!isError ? (\n <FileAttachmentPreview attachment={attachment} />\n ) : null}\n <FileAttachmentIcon mimeType={attachment.mimeType} />\n </>\n )}\n </div>\n <div className=\"lb-attachment-details\">\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\">\n {fileExtension}\n </span>\n )}\n </span>\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"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAkCA;AAEA;AAEA;AACE;AAEA;AACF;AAEA;AACE;AAQE;AACG;AAAO;AAAoP;AAIhQ;AACE;AACG;AAAO;AAAiP;AAI7P;AACE;AACG;AAAO;AAAiQ;AAI7Q;AACE;AACG;AAAO;AAAiG;AAI7G;AACE;AACG;AAAO;AAAyP;AAIrQ;AACF;AAEA;AACE;AAAkB;AACyB;AAChC;AAGX;AACG;AACW;AACH;AACC;AACA;AACH;AACI;AACA;AACH;AAEL;AACG;AACQ;AAEX;AACG;AACQ;AAEX;AACG;AACQ;AAGG;AAAY;AAGjC;AAEA;AACE;AAEA;AACE;AAAc;AAGhB;AACG;AACW;AACoB;AAE7B;AAAS;AAAa;AAAe;AAG5C;AAEA;AAAyC;AAEzC;AAGE;AAEA;AACE;AAEA;AAEA;AACE;AAAuB;AACzB;AAGF;AAAQ;AAA2B;AACrC;AAEA;AAA0C;AAE1C;AAGE;AAEA;AAAQ;AAA2B;AACrC;AAEA;AAA+B;AAE/B;AAGE;AACA;AAEA;AAIE;AACE;AAAQ;AAAgC;AAAwB;AAEhE;AAAQ;AAAiC;AAAwB;AACnE;AAGF;AACF;AAEO;AAAwB;AAC7B;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AACA;AACA;AACE;AAAoC;AAEtC;AAEA;AACA;AAEA;AAEA;AACE;AACE;AAAgB;AAGV;AACN;AAEA;AAAiD;AACnD;AAEA;AAAsD;AAGxD;AAIA;AAAgC;AAE5B;AACE;AAAqB;AACvB;AACF;AACqB;AAGvB;AAEE;AACE;AAEA;AAA2C;AAChC;AACG;AACN;AAER;AAAqC;AACvC;AAGF;AACG;AACoE;AACxC;AACvB;AACuB;AAC3B;AACwB;AACa;AAEpC;AAAc;AAQN;AAAsB;AAExB;AAAwC;AAI9C;AAAc;AACZ;AAAe;AAAuC;AACpD;AAAe;AAEb;AAAe;AAKnB;AAAe;AAAmC;AAKlD;AAAiB;AACf;AACM;AACK;AACD;AACM;AACH;AAQxB;;"}
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\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 (isUploaded && isInsideRoom) {\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 ) {\n mediaAttachments.push(attachment);\n } else {\n fileAttachments.push(attachment);\n }\n }\n\n return {\n mediaAttachments,\n fileAttachments,\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA8BA;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;AACE;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;AAIE;AAAgC;AAEhC;AAA+B;AACjC;AAGF;AAAO;AACL;AACA;AAEJ;;;;"}