@droppii-org/chat-sdk 0.1.49 → 0.1.51

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 (101) hide show
  1. package/dist/assets/svg/expand.d.ts +7 -0
  2. package/dist/assets/svg/expand.d.ts.map +1 -0
  3. package/dist/assets/svg/expand.js +2 -0
  4. package/dist/assets/svg/expand.tsx +44 -0
  5. package/dist/assets/svg/index.ts +1 -1
  6. package/dist/components/biz-inbox/BizThreadCard.d.ts.map +1 -1
  7. package/dist/components/biz-inbox/BizThreadCard.js +3 -11
  8. package/dist/components/biz-thread-detail/BizAttachmentPreview.d.ts.map +1 -1
  9. package/dist/components/biz-thread-detail/BizAttachmentPreview.js +1 -5
  10. package/dist/components/biz-thread-detail/BizLinkMessageCard.d.ts +3 -2
  11. package/dist/components/biz-thread-detail/BizLinkMessageCard.d.ts.map +1 -1
  12. package/dist/components/biz-thread-detail/BizLinkMessageCard.js +4 -8
  13. package/dist/components/biz-thread-detail/BizMessageContent.d.ts.map +1 -1
  14. package/dist/components/biz-thread-detail/BizMessageContent.js +59 -5
  15. package/dist/components/biz-thread-detail/BizMessageList.js +2 -2
  16. package/dist/components/biz-thread-detail/BizThreadDetailInput.d.ts.map +1 -1
  17. package/dist/components/biz-thread-detail/BizThreadDetailInput.js +20 -17
  18. package/dist/components/biz-thread-detail/BizVideoViewer.d.ts +8 -0
  19. package/dist/components/biz-thread-detail/BizVideoViewer.d.ts.map +1 -0
  20. package/dist/components/biz-thread-detail/BizVideoViewer.js +103 -0
  21. package/dist/components/biz-thread-detail/index.d.ts +0 -4
  22. package/dist/components/biz-thread-detail/index.d.ts.map +1 -1
  23. package/dist/components/biz-thread-detail/index.js +0 -2
  24. package/dist/components/conversation/ConversationBySessionItem.d.ts.map +1 -1
  25. package/dist/components/conversation/ConversationBySessionItem.js +6 -5
  26. package/dist/components/conversation/ThreadCard.d.ts +14 -0
  27. package/dist/components/conversation/ThreadCard.d.ts.map +1 -0
  28. package/dist/components/conversation/ThreadCard.js +16 -0
  29. package/dist/components/media-collection/ImageCollection.d.ts.map +1 -1
  30. package/dist/components/media-collection/ImageCollection.js +3 -17
  31. package/dist/components/message/MessageList.d.ts.map +1 -1
  32. package/dist/components/message/MessageList.js +1 -7
  33. package/dist/components/message/footer/ComposerTextSyncPlugin.d.ts +6 -0
  34. package/dist/components/message/footer/ComposerTextSyncPlugin.d.ts.map +1 -0
  35. package/dist/components/message/footer/ComposerTextSyncPlugin.js +16 -0
  36. package/dist/components/message/footer/FilePreview.d.ts +3 -2
  37. package/dist/components/message/footer/FilePreview.d.ts.map +1 -1
  38. package/dist/components/message/footer/FilePreview.js +61 -21
  39. package/dist/components/message/footer/LinkInputPreview.d.ts +7 -0
  40. package/dist/components/message/footer/LinkInputPreview.d.ts.map +1 -0
  41. package/dist/components/message/footer/LinkInputPreview.js +16 -0
  42. package/dist/components/message/footer/MediaActions.d.ts.map +1 -1
  43. package/dist/components/message/footer/MediaActions.js +8 -5
  44. package/dist/components/message/footer/index.d.ts.map +1 -1
  45. package/dist/components/message/footer/index.js +48 -35
  46. package/dist/components/message/item/FileMessage.d.ts +2 -2
  47. package/dist/components/message/item/FileMessage.d.ts.map +1 -1
  48. package/dist/components/message/item/FileMessage.js +20 -12
  49. package/dist/components/message/item/ImageMessage.d.ts.map +1 -1
  50. package/dist/components/message/item/ImageMessage.js +11 -15
  51. package/dist/components/message/item/LinkCard.d.ts +7 -0
  52. package/dist/components/message/item/LinkCard.d.ts.map +1 -0
  53. package/dist/components/message/item/LinkCard.js +17 -0
  54. package/dist/components/message/item/LinkText.d.ts +6 -0
  55. package/dist/components/message/item/LinkText.d.ts.map +1 -0
  56. package/dist/components/message/item/LinkText.js +13 -0
  57. package/dist/components/message/item/MergeImageMessage.d.ts +7 -0
  58. package/dist/components/message/item/MergeImageMessage.d.ts.map +1 -0
  59. package/dist/components/message/item/MergeImageMessage.js +30 -0
  60. package/dist/components/message/item/MessageBubble.d.ts +13 -0
  61. package/dist/components/message/item/MessageBubble.d.ts.map +1 -0
  62. package/dist/components/message/item/MessageBubble.js +19 -0
  63. package/dist/components/message/item/RevokeMessage.js +1 -1
  64. package/dist/components/message/item/TextMessage.d.ts.map +1 -1
  65. package/dist/components/message/item/TextMessage.js +20 -9
  66. package/dist/components/message/item/UrlTextMessage.d.ts.map +1 -1
  67. package/dist/components/message/item/UrlTextMessage.js +19 -17
  68. package/dist/components/message/item/VideoMessage.d.ts.map +1 -1
  69. package/dist/components/message/item/VideoMessage.js +4 -12
  70. package/dist/components/message/item/imagePreviewToolbar.d.ts +5 -0
  71. package/dist/components/message/item/imagePreviewToolbar.d.ts.map +1 -0
  72. package/dist/components/message/item/imagePreviewToolbar.js +14 -0
  73. package/dist/components/message/item/index.d.ts +2 -1
  74. package/dist/components/message/item/index.d.ts.map +1 -1
  75. package/dist/components/message/item/index.js +38 -41
  76. package/dist/components/message/item/textStyle.d.ts +2 -0
  77. package/dist/components/message/item/textStyle.d.ts.map +1 -0
  78. package/dist/components/message/item/textStyle.js +1 -0
  79. package/dist/hooks/message/useSendMessage.d.ts +7 -13
  80. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  81. package/dist/hooks/message/useSendMessage.js +87 -113
  82. package/dist/styles/global.css +1 -1
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/dist/types/chat.d.ts +8 -1
  85. package/dist/types/chat.d.ts.map +1 -1
  86. package/dist/utils/bizMessage.d.ts +1 -14
  87. package/dist/utils/bizMessage.d.ts.map +1 -1
  88. package/dist/utils/bizMessage.js +1 -26
  89. package/dist/utils/common.d.ts +3 -1
  90. package/dist/utils/common.d.ts.map +1 -1
  91. package/dist/utils/common.js +16 -3
  92. package/dist/utils/fileValidation.d.ts +1 -0
  93. package/dist/utils/fileValidation.d.ts.map +1 -1
  94. package/dist/utils/fileValidation.js +10 -17
  95. package/dist/utils/link.d.ts +13 -0
  96. package/dist/utils/link.d.ts.map +1 -0
  97. package/dist/utils/link.js +77 -0
  98. package/dist/utils/openLink.d.ts +3 -0
  99. package/dist/utils/openLink.d.ts.map +1 -0
  100. package/dist/utils/openLink.js +19 -0
  101. package/package.json +126 -127
@@ -0,0 +1,7 @@
1
+ interface ISvgProps {
2
+ size?: number;
3
+ className?: string;
4
+ }
5
+ export declare const ExpandIcon: ({ size, className }: ISvgProps) => import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=expand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../../../src/assets/svg/expand.tsx"],"names":[],"mappings":"AAAA,UAAU,SAAS;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,UAAU,GAAI,qBAA0B,SAAS,4CAsC7D,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const ExpandIcon = ({ size = 20, className }) => (_jsxs("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className, children: [_jsx("path", { d: "M2 7V2H7", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M18 7V2H13", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M2 13V18H7", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M18 13V18H13", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })] }));
@@ -0,0 +1,44 @@
1
+ interface ISvgProps {
2
+ size?: number;
3
+ className?: string;
4
+ }
5
+
6
+ export const ExpandIcon = ({ size = 20, className }: ISvgProps) => (
7
+ <svg
8
+ width={size}
9
+ height={size}
10
+ viewBox="0 0 20 20"
11
+ fill="none"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ className={className}
14
+ >
15
+ <path
16
+ d="M2 7V2H7"
17
+ stroke="currentColor"
18
+ strokeWidth="1.5"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ />
22
+ <path
23
+ d="M18 7V2H13"
24
+ stroke="currentColor"
25
+ strokeWidth="1.5"
26
+ strokeLinecap="round"
27
+ strokeLinejoin="round"
28
+ />
29
+ <path
30
+ d="M2 13V18H7"
31
+ stroke="currentColor"
32
+ strokeWidth="1.5"
33
+ strokeLinecap="round"
34
+ strokeLinejoin="round"
35
+ />
36
+ <path
37
+ d="M18 13V18H13"
38
+ stroke="currentColor"
39
+ strokeWidth="1.5"
40
+ strokeLinecap="round"
41
+ strokeLinejoin="round"
42
+ />
43
+ </svg>
44
+ );
@@ -1,2 +1,2 @@
1
1
  export * from "./document";
2
- export * from "./canned-response";
2
+ export * from "./canned-response";
@@ -1 +1 @@
1
- {"version":3,"file":"BizThreadCard.d.ts","sourceRoot":"","sources":["../../../src/components/biz-inbox/BizThreadCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAYvF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,IAAI,CAAC;CAClD;AAwBD,QAAA,MAAM,aAAa,GAAI,mBAAmB,kBAAkB,4CAiE3D,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"BizThreadCard.d.ts","sourceRoot":"","sources":["../../../src/components/biz-inbox/BizThreadCard.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAavF,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,OAAO,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,IAAI,CAAC;CAClD;AAQD,QAAA,MAAM,aAAa,GAAI,mBAAmB,kBAAkB,4CA6C3D,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -1,12 +1,12 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import clsx from "clsx";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
4
3
  import dayjs from "dayjs";
5
4
  import { useTranslation } from "react-i18next";
6
5
  import useAuthStore from "../../store/auth";
7
6
  import { formatTimestamp } from "../../utils/common";
8
7
  import { parseBizLatestMessage } from "../../utils/bizMessage";
9
8
  import { resolveBizAvatarUrl, resolveBizBadgeLabel, resolveBizDisplayName, } from "../../utils/bizConversation";
9
+ import ThreadCard from "../../components/conversation/ThreadCard";
10
10
  import BizThreadAvatar from "./BizThreadAvatar";
11
11
  import BizThreadTitle from "./BizThreadTitle";
12
12
  const formatLatestMessageTime = (sendTime) => {
@@ -15,12 +15,6 @@ const formatLatestMessageTime = (sendTime) => {
15
15
  const isToday = dayjs(sendTime).isSame(dayjs(), "day");
16
16
  return formatTimestamp(sendTime, { hasTime: isToday });
17
17
  };
18
- const formatUnreadCount = (count) => (count > 99 ? "99+" : count);
19
- const UnreadBadge = ({ count }) => {
20
- const label = String(formatUnreadCount(count));
21
- const isWide = label.length > 1;
22
- return (_jsx("span", { className: `inline-flex h-5 shrink-0 items-center justify-center rounded-[32px] border-2 border-white bg-[linear-gradient(93deg,#FF7D74_0%,#F04438_100%)] text-center text-[12px] font-semibold leading-[160%] text-white px-[3px] ${isWide ? "min-w-8" : "min-w-5"}`, children: label }));
23
- };
24
18
  const BizThreadCard = ({ item, onClick }) => {
25
19
  var _a;
26
20
  const { t } = useTranslation("biz-inbox");
@@ -32,8 +26,6 @@ const BizThreadCard = ({ item, onClick }) => {
32
26
  const latestMessagePreview = parseBizLatestMessage(item.latestMsg, currentUserId, tCommon, item.peer.fullName);
33
27
  const latestMessageTime = formatLatestMessageTime(item.latestMsgSendTime || null);
34
28
  const unreadCount = (_a = item.unreadCount) !== null && _a !== void 0 ? _a : 0;
35
- return (_jsxs("button", { type: "button", onClick: () => onClick(item), "data-testid": "biz-inbox-thread-card", className: "flex w-full shrink-0 items-start gap-2 border-b border-[#EFEFEF] bg-white p-3 text-left transition-colors hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#1677ff]", children: [_jsx(BizThreadAvatar, { peerType: item.peer.type, chatCategory: item.chatCategory, displayName: displayName, avatarUrl: avatarUrl, badgeLabel: badgeLabel }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsxs("div", { className: "flex min-h-[25.6px] items-center justify-between gap-1", children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsx(BizThreadTitle, { peerType: item.peer.type, chatCategory: item.chatCategory, displayName: displayName, hasUnread: unreadCount > 0 }) }), latestMessageTime && (_jsx("span", { className: "shrink-0 text-[12px] font-normal leading-[160%] text-[#747B7E] [font-feature-settings:'liga'_off,'clig'_off]", children: latestMessageTime }))] }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("p", { className: clsx("min-w-0 flex-1 truncate text-[14px] leading-[160%] [font-feature-settings:'liga'_off,'clig'_off]", unreadCount > 0
36
- ? "font-[510] text-black"
37
- : "font-normal tracking-[0.14px] text-[#5C6366]"), children: latestMessagePreview }), unreadCount > 0 && _jsx(UnreadBadge, { count: unreadCount })] })] })] }));
29
+ return (_jsx(ThreadCard, { testId: "biz-inbox-thread-card", onClick: () => onClick(item), unreadCount: unreadCount, time: latestMessageTime, preview: latestMessagePreview, avatar: _jsx(BizThreadAvatar, { peerType: item.peer.type, chatCategory: item.chatCategory, displayName: displayName, avatarUrl: avatarUrl, badgeLabel: badgeLabel }), title: _jsx(BizThreadTitle, { peerType: item.peer.type, chatCategory: item.chatCategory, displayName: displayName, hasUnread: unreadCount > 0 }) }));
38
30
  };
39
31
  export default BizThreadCard;
@@ -1 +1 @@
1
- {"version":3,"file":"BizAttachmentPreview.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizAttachmentPreview.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAmJlC,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,QAAA,MAAM,oBAAoB,GAAI,oCAI3B,yBAAyB,mDAuB3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"BizAttachmentPreview.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizAttachmentPreview.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AA8IlC,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,QAAA,MAAM,oBAAoB,GAAI,oCAI3B,yBAAyB,mDAuB3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -5,11 +5,7 @@ import { Icon } from "../../components/icon";
5
5
  import { documentIcon } from "../../assets/svg";
6
6
  import { shortenFileName } from "../../components/message/footer/FilePreview";
7
7
  import { DOCUMENT_MIME_TYPES } from "../../utils/fileValidation";
8
- const formatDuration = (seconds) => {
9
- const minutes = Math.floor(seconds / 60);
10
- const secs = Math.floor(seconds % 60);
11
- return `${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
12
- };
8
+ import { formatDuration } from "../../utils/common";
13
9
  const formatFileSize = (bytes) => {
14
10
  if (bytes === 0)
15
11
  return "0 B";
@@ -1,6 +1,7 @@
1
+ import type { UrlMetadataInfo } from "../../types/chat";
1
2
  export interface BizLinkMessageCardProps {
2
- url: string;
3
+ metadata: UrlMetadataInfo;
3
4
  }
4
- declare const BizLinkMessageCard: ({ url }: BizLinkMessageCardProps) => import("react/jsx-runtime").JSX.Element | null;
5
+ declare const BizLinkMessageCard: ({ metadata }: BizLinkMessageCardProps) => import("react/jsx-runtime").JSX.Element | null;
5
6
  export default BizLinkMessageCard;
6
7
  //# sourceMappingURL=BizLinkMessageCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BizLinkMessageCard.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizLinkMessageCard.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,QAAA,MAAM,kBAAkB,GAAI,SAAS,uBAAuB,mDA+D3D,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"BizLinkMessageCard.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizLinkMessageCard.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,QAAA,MAAM,kBAAkB,GAAI,cAAc,uBAAuB,mDA8ChE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1,21 +1,17 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslation } from "react-i18next";
4
- import { useFetchUrlMetadata } from "../../hooks/common/useFetchUrlMetadata";
5
4
  import { getHostLabel } from "../../utils/bizLink";
6
5
  import { openBizLink } from "../../utils/openBizLink";
7
- const BizLinkMessageCard = ({ url }) => {
6
+ const BizLinkMessageCard = ({ metadata }) => {
8
7
  const { t } = useTranslation("biz-inbox");
9
- const { data, isLoading } = useFetchUrlMetadata(url);
10
- if (isLoading) {
11
- return (_jsxs("div", { "data-testid": "biz-link-message-card-loading", className: "flex w-full max-w-[292px] flex-col overflow-hidden rounded-[12px] border border-[rgba(57,62,64,0.1)] bg-white", children: [_jsx("div", { className: "flex aspect-[1920/1080] w-full items-center justify-center bg-[#EAF5FF]", children: _jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-[#1B3FE4] border-t-transparent" }) }), _jsxs("div", { className: "flex flex-col gap-1 px-3 py-2", children: [_jsx("div", { className: "h-4 w-2/3 animate-pulse rounded bg-[#EFEFEF]" }), _jsx("div", { className: "h-3 w-1/3 animate-pulse rounded bg-[#EFEFEF]" })] })] }));
12
- }
13
- if (!(data === null || data === void 0 ? void 0 : data.title) && !(data === null || data === void 0 ? void 0 : data.description) && !(data === null || data === void 0 ? void 0 : data.image)) {
8
+ const { url, title, description, image } = metadata;
9
+ if (!title && !description && !image) {
14
10
  return null;
15
11
  }
16
12
  const handleOpen = () => {
17
13
  openBizLink(url, t);
18
14
  };
19
- return (_jsxs("button", { type: "button", onClick: handleOpen, "data-testid": "biz-link-message-card", className: "flex w-full max-w-[292px] flex-col overflow-hidden rounded-[12px] border border-[rgba(57,62,64,0.1)] bg-white text-left", children: [(data === null || data === void 0 ? void 0 : data.image) && (_jsx("div", { className: "aspect-[16/9] w-full overflow-hidden", children: _jsx("img", { src: data.image, alt: "", className: "h-full w-full object-cover" }) })), _jsxs("div", { className: "flex flex-col px-3 py-2", children: [(data === null || data === void 0 ? void 0 : data.title) && (_jsx("p", { className: "truncate text-[14px] font-medium leading-[1.6] text-[#393E40]", children: data.title })), _jsx("p", { className: "truncate text-[12px] font-medium leading-[1.6] tracking-[0.12px] text-[#1B3FE4]", children: getHostLabel(url) }), (data === null || data === void 0 ? void 0 : data.description) && (_jsx("p", { className: "line-clamp-2 text-[12px] leading-[1.6] text-[#5C6366]", children: data.description }))] })] }));
15
+ return (_jsxs("button", { type: "button", onClick: handleOpen, "data-testid": "biz-link-message-card", className: "flex w-full max-w-[292px] flex-col overflow-hidden rounded-[12px] border border-[rgba(57,62,64,0.1)] bg-white text-left", children: [image && (_jsx("div", { className: "aspect-[16/9] w-full overflow-hidden", children: _jsx("img", { src: image, alt: "", className: "h-full w-full object-cover" }) })), _jsxs("div", { className: "flex flex-col px-3 py-2", children: [title && (_jsx("p", { className: "truncate text-[14px] font-medium leading-[1.6] text-[#393E40]", children: title })), _jsx("p", { className: "truncate text-[12px] font-medium leading-[1.6] tracking-[0.12px] text-[#1B3FE4]", children: getHostLabel(url) }), description && (_jsx("p", { className: "line-clamp-2 text-[12px] leading-[1.6] text-[#5C6366]", children: description }))] })] }));
20
16
  };
21
17
  export default BizLinkMessageCard;
@@ -1 +1 @@
1
- {"version":3,"file":"BizMessageContent.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizMessageContent.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAA8B,MAAM,yBAAyB,CAAC;AAOlF,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;CACtB;AAuGD,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,mDA4C7D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"BizMessageContent.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizMessageContent.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAA8B,MAAM,yBAAyB,CAAC;AAalF,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,WAAW,CAAC;CACtB;AAgND,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,mDAyD7D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -4,10 +4,15 @@ import { useState } from "react";
4
4
  import clsx from "clsx";
5
5
  import { MessageStatus, MessageType } from "@openim/wasm-client-sdk";
6
6
  import { Image, Spin } from "antd";
7
+ import { Icon } from "../../components/icon";
8
+ import { documentIcon } from "../../assets/svg";
9
+ import { shortenFileName } from "../../components/message/footer/FilePreview";
7
10
  import BizLinkMessageCard from "./BizLinkMessageCard";
8
11
  import BizLinkTextContent from "./BizLinkTextContent";
9
12
  import BizImageViewer from "./BizImageViewer";
13
+ import BizVideoViewer from "./BizVideoViewer";
10
14
  import { extractBizLinkUrls } from "../../utils/bizLink";
15
+ import { formatDuration, renderFileSize } from "../../utils/common";
11
16
  const MERGE_IMAGE_GRID_COLS_CLASS = {
12
17
  1: "grid-cols-[repeat(1,100px)]",
13
18
  2: "grid-cols-[repeat(2,100px)]",
@@ -43,21 +48,70 @@ const BizMergeImageMessage = ({ message }) => {
43
48
  return (_jsx("div", { className: "cursor-pointer", onClick: () => setViewerIndex(index), children: _jsx(Image, { src: sourceUrl, width: 100, height: 100, className: "rounded-xl object-cover", preview: false, placeholder: _jsx("div", { className: "flex h-[100px] w-[100px] items-center justify-center rounded-xl bg-black/10", children: _jsx(Spin, {}) }) }) }, item.clientMsgID));
44
49
  }) }) }), viewerIndex !== null && (_jsx(BizImageViewer, { images: imageUrls, initialIndex: viewerIndex, onClose: () => setViewerIndex(null) }))] }));
45
50
  };
51
+ const BizVideoMessage = ({ message }) => {
52
+ var _a;
53
+ const [viewerVisible, setViewerVisible] = useState(false);
54
+ const videoElem = message.videoElem;
55
+ const videoUrl = (videoElem === null || videoElem === void 0 ? void 0 : videoElem.videoUrl) || "";
56
+ const posterUrl = (videoElem === null || videoElem === void 0 ? void 0 : videoElem.snapshotUrl) || "";
57
+ const durationSeconds = ((_a = videoElem === null || videoElem === void 0 ? void 0 : videoElem.duration) !== null && _a !== void 0 ? _a : 0) / 1000;
58
+ const isSending = message.status === MessageStatus.Sending;
59
+ return (_jsxs("div", { className: "-mx-1", children: [_jsx(Spin, { spinning: isSending, children: _jsxs("div", { className: "relative h-[150px] w-[215px] cursor-pointer overflow-hidden rounded-xl", onClick: () => setViewerVisible(true), children: [_jsx("img", { src: posterUrl, alt: "", className: "h-full w-full object-cover" }), _jsx("div", { className: "absolute bottom-1 right-1 flex h-[18px] w-9 items-center justify-center rounded-xl bg-black/70", children: _jsx("span", { className: "text-[10px] leading-[1.6] text-white", children: formatDuration(durationSeconds) }) }), _jsx("div", { className: "absolute left-1/2 top-1/2 flex h-[50px] w-[50px] -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-2xl bg-black/60", children: _jsx(Icon, { icon: "play-b", size: 24, className: "text-white" }) })] }) }), viewerVisible && (_jsx(BizVideoViewer, { videoUrl: videoUrl, posterUrl: posterUrl, onClose: () => setViewerVisible(false) }))] }));
60
+ };
61
+ const BizFileMessage = ({ message }) => {
62
+ var _a;
63
+ const fileElem = message.fileElem;
64
+ const fileName = (fileElem === null || fileElem === void 0 ? void 0 : fileElem.fileName) || "";
65
+ const fileSize = (_a = fileElem === null || fileElem === void 0 ? void 0 : fileElem.fileSize) !== null && _a !== void 0 ? _a : 0;
66
+ const sourceUrl = (fileElem === null || fileElem === void 0 ? void 0 : fileElem.sourceUrl) || "";
67
+ const isSending = message.status === MessageStatus.Sending;
68
+ const hasUrl = !!sourceUrl;
69
+ const handleView = () => {
70
+ if (!hasUrl)
71
+ return;
72
+ window.open(sourceUrl, "_blank", "noopener,noreferrer");
73
+ };
74
+ const handleDownload = () => {
75
+ if (!hasUrl)
76
+ return;
77
+ const link = document.createElement("a");
78
+ link.href = sourceUrl;
79
+ link.setAttribute("download", fileName);
80
+ link.setAttribute("target", "_blank");
81
+ link.setAttribute("rel", "noopener noreferrer");
82
+ document.body.appendChild(link);
83
+ link.click();
84
+ link.remove();
85
+ };
86
+ return (_jsx(Spin, { spinning: isSending, children: _jsxs("div", { className: "flex h-[51px] w-[292px] items-center gap-3 rounded-xl border border-[rgba(168,173,175,0.2)] bg-white px-3 py-1", children: [_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden [&>svg]:h-6 [&>svg]:w-6", children: documentIcon }), _jsxs("div", { className: "flex min-w-0 w-[168px] flex-col justify-center", children: [_jsx("span", { className: "truncate text-[14px] font-medium leading-[160%] text-[#393E40]", children: shortenFileName(fileName) }), _jsx("span", { className: "truncate text-[13px] leading-[160%] text-[#5C6366]", children: renderFileSize(fileSize) })] }), _jsxs("div", { className: "flex w-[52px] shrink-0 items-center justify-end gap-3", children: [_jsx("button", { type: "button", onClick: handleView, disabled: !hasUrl, "aria-label": "Xem file", className: !hasUrl ? "opacity-30" : undefined, children: _jsx(Icon, { icon: "eye-o", size: 20, className: "text-[#393E40]" }) }), _jsx("button", { type: "button", onClick: handleDownload, disabled: !hasUrl, "aria-label": "T\u1EA3i xu\u1ED1ng", className: !hasUrl ? "opacity-30" : undefined, children: _jsx(Icon, { icon: "download-o", size: 20, className: "text-[#393E40]" }) })] })] }) }));
87
+ };
46
88
  const BizMessageContent = ({ message }) => {
47
- var _a, _b, _c, _d, _e, _f, _g;
89
+ var _a, _b, _c, _d, _e;
48
90
  if (message.contentType === MessageType.PictureMessage) {
49
91
  return _jsx(BizPictureMessage, { message: message });
50
92
  }
93
+ if (message.contentType === MessageType.FileMessage) {
94
+ return _jsx(BizFileMessage, { message: message });
95
+ }
96
+ if (message.contentType === MessageType.VideoMessage) {
97
+ return _jsx(BizVideoMessage, { message: message });
98
+ }
51
99
  if (message.contentType === MessageType.MergeMessage) {
52
100
  return _jsx(BizMergeImageMessage, { message: message });
53
101
  }
54
102
  const text = (_e = (_d = (_b = (_a = message.textElem) === null || _a === void 0 ? void 0 : _a.content) !== null && _b !== void 0 ? _b : (_c = message.urlTextElem) === null || _c === void 0 ? void 0 : _c.content) !== null && _d !== void 0 ? _d : message.content) !== null && _e !== void 0 ? _e : "";
55
103
  const hasLinks = text ? extractBizLinkUrls(text).length > 0 : false;
56
104
  if (message.contentType === MessageType.UrlTextMessage) {
57
- const urls = (_g = (_f = message.urlTextElem) === null || _f === void 0 ? void 0 : _f.urls) !== null && _g !== void 0 ? _g : [];
58
- const singleLink = urls.length === 1 ? urls[0] : null;
59
- if (singleLink) {
60
- return (_jsxs("div", { className: "flex max-w-[292px] flex-col gap-2", children: [text && _jsx(BizLinkTextContent, { text: text }), _jsx(BizLinkMessageCard, { url: singleLink })] }));
105
+ let urlMetadata;
106
+ try {
107
+ const ex = JSON.parse(message.ex || "{}");
108
+ urlMetadata = ex.urlMetadata;
109
+ }
110
+ catch (_f) {
111
+ urlMetadata = undefined;
112
+ }
113
+ if (urlMetadata === null || urlMetadata === void 0 ? void 0 : urlMetadata.url) {
114
+ return (_jsxs("div", { className: "flex max-w-[292px] flex-col gap-2", children: [text && _jsx(BizLinkTextContent, { text: text }), _jsx(BizLinkMessageCard, { metadata: urlMetadata })] }));
61
115
  }
62
116
  return text ? _jsx(BizLinkTextContent, { text: text }) : null;
63
117
  }
@@ -12,7 +12,7 @@ import useConversationStore from "../../store/conversation";
12
12
  import useAuthStore from "../../store/auth";
13
13
  import { isBizGroupChat } from "../../utils/bizConversation";
14
14
  import emitter from "../../utils/events";
15
- import BizMessageItem from "./item/BizMessageItem";
15
+ import MessageItem from "../../components/message/item";
16
16
  const BizMessageList = () => {
17
17
  const { t } = useTranslation("biz-inbox");
18
18
  const { user } = useChatContext();
@@ -31,6 +31,6 @@ const BizMessageList = () => {
31
31
  emitter.off("BIZ_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM", scrollToBottomIfAtBottom);
32
32
  };
33
33
  }, [scrollToBottomIfAtBottom]);
34
- return (_jsx("div", { "data-testid": "biz-message-list", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableBizMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: loadState.messageList.map((message, _, array) => (_jsx(BizMessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat }, message.clientMsgID))) })) }));
34
+ return (_jsx("div", { "data-testid": "biz-message-list", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableBizMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat }, message.clientMsgID))) })) }));
35
35
  };
36
36
  export default BizMessageList;
@@ -1 +1 @@
1
- {"version":3,"file":"BizThreadDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizThreadDetailInput.tsx"],"names":[],"mappings":"AA8BA,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,QAAA,MAAM,oBAAoB,GAAI,gDAI3B,yBAAyB,4CAkY3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"BizThreadDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizThreadDetailInput.tsx"],"names":[],"mappings":"AA+BA,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED,QAAA,MAAM,oBAAoB,GAAI,gDAI3B,yBAAyB,4CA0Y3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -12,10 +12,11 @@ import useConversationStore from "../../store/conversation";
12
12
  import useBizMessageDraftStore from "../../store/bizMessageDraft";
13
13
  import { resolveBizAvatarUrl, resolveBizDisplayName, } from "../../utils/bizConversation";
14
14
  import { emit } from "../../utils/events";
15
- import { extractBizLinkUrls } from "../../utils/bizLink";
15
+ import { extractLinkUrls } from "../../utils/link";
16
+ import { useFetchUrlMetadata } from "../../hooks/common/useFetchUrlMetadata";
16
17
  import { processAndValidateDocuments, processAndValidateFiles, } from "../../utils/fileValidation";
17
- import BizAttachmentPreview from "./BizAttachmentPreview";
18
- import BizLinkInputPreview from "./BizLinkInputPreview";
18
+ import { FilePreviewList } from "../../components/message/footer/FilePreview";
19
+ import LinkInputPreview from "../../components/message/footer/LinkInputPreview";
19
20
  import BizMoreOptionPanel from "./BizMoreOptionPanel";
20
21
  const MAX_LINES = 5;
21
22
  const CONTAINER_MIN_HEIGHT = 48;
@@ -28,7 +29,6 @@ const BizThreadDetailInput = ({ placeholder, onSendMessage, onInputChange, }) =>
28
29
  const conversationId = selectedConversationId || (bizConversation === null || bizConversation === void 0 ? void 0 : bizConversation.conversationId) || "";
29
30
  const isConversationReady = !!conversationData && (!!conversationData.userID || !!conversationData.groupID);
30
31
  const sendMessageOptions = useMemo(() => ({
31
- extractUrls: extractBizLinkUrls,
32
32
  scrollEvent: "BIZ_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM",
33
33
  resolveOfflinePushMeta: () => ({
34
34
  title: bizConversation
@@ -42,7 +42,7 @@ const BizThreadDetailInput = ({ placeholder, onSendMessage, onInputChange, }) =>
42
42
  "",
43
43
  }),
44
44
  }), [bizConversation, conversationData, t]);
45
- const { sendTextMessage, sendMergeMessage } = useSendMessage(sendMessageOptions);
45
+ const { sendMergeMessage } = useSendMessage(sendMessageOptions);
46
46
  const userID = useAuthStore((state) => state.userID);
47
47
  const setDraft = useBizMessageDraftStore((state) => state.setDraft);
48
48
  const clearDraft = useBizMessageDraftStore((state) => state.clearDraft);
@@ -58,10 +58,11 @@ const BizThreadDetailInput = ({ placeholder, onSendMessage, onInputChange, }) =>
58
58
  const [previewDismissed, setPreviewDismissed] = useState(false);
59
59
  const [isMoreOptionOpen, { setFalse: closeMoreOption, toggle: toggleMoreOption },] = useBoolean(false);
60
60
  const previewUrl = useMemo(() => {
61
- const urls = extractBizLinkUrls(value);
61
+ const urls = extractLinkUrls(value);
62
62
  return urls.length === 1 ? urls[0] : null;
63
63
  }, [value]);
64
64
  const showLinkPreview = !!previewUrl && !previewDismissed;
65
+ const { data: previewMetadata } = useFetchUrlMetadata(previewUrl !== null && previewUrl !== void 0 ? previewUrl : "", showLinkPreview);
65
66
  useEffect(() => {
66
67
  setPreviewDismissed(false);
67
68
  }, [previewUrl]);
@@ -182,23 +183,25 @@ const BizThreadDetailInput = ({ placeholder, onSendMessage, onInputChange, }) =>
182
183
  if (onSendMessage) {
183
184
  onSendMessage(text);
184
185
  }
185
- else if (hasFiles) {
186
- await sendMergeMessage({
187
- plainText: text,
188
- richText: "",
189
- files: listUploadFiles,
190
- isInternal: false,
191
- });
192
- setListUploadFiles([]);
193
- }
194
186
  else {
195
- const sent = await sendTextMessage({
187
+ const urlMetadata = showLinkPreview && previewUrl && previewMetadata
188
+ ? {
189
+ url: previewUrl,
190
+ title: previewMetadata.title,
191
+ description: previewMetadata.description,
192
+ image: previewMetadata.image,
193
+ }
194
+ : undefined;
195
+ const sent = await sendMergeMessage({
196
196
  plainText: text,
197
197
  richText: "",
198
+ files: listUploadFiles,
198
199
  isInternal: false,
200
+ urlMetadata,
199
201
  });
200
202
  if (!sent)
201
203
  return;
204
+ setListUploadFiles([]);
202
205
  }
203
206
  setValue("");
204
207
  if (conversationId && userID) {
@@ -246,7 +249,7 @@ const BizThreadDetailInput = ({ placeholder, onSendMessage, onInputChange, }) =>
246
249
  event.preventDefault();
247
250
  void handleSend();
248
251
  };
249
- return (_jsxs("div", { ref: containerRef, "data-testid": "biz-thread-detail-input", className: "flex w-full shrink-0 flex-col bg-[rgba(131,137,157,0.05)]", children: [_jsx("input", { ref: mediaInputRef, type: "file", accept: "image/jpeg,image/png,image/jpg,video/*", multiple: true, className: "hidden", onChange: handleMediaInputChange }), _jsx("input", { ref: documentInputRef, type: "file", accept: ".pdf,.doc,.docx", multiple: true, className: "hidden", onChange: handleDocumentInputChange }), listUploadFiles.length > 0 && (_jsx(BizAttachmentPreview, { files: listUploadFiles, onRemove: handleRemoveFile, onVideoReady: () => setIsVideoLoading(false) })), showLinkPreview && previewUrl && (_jsx(BizLinkInputPreview, { url: previewUrl, onRemove: () => setPreviewDismissed(true) })), _jsxs("div", { className: "flex items-end gap-3 px-3 py-2", children: [_jsx("div", { className: "flex shrink-0 items-center", children: _jsx("button", { type: "button", onClick: handleToggleMoreOption, "aria-label": t("thread_detail.more_option.toggle"), "aria-expanded": isMoreOptionOpen, "data-testid": "biz-thread-detail-more-option-toggle", className: "flex py-3 items-center justify-center rounded-full", children: isMoreOptionOpen ? (_jsx(Icon, { icon: "close-circle-o", size: 24, className: "text-[#1B3FE4]" })) : (_jsx(Icon, { icon: "plus-circle-o", size: 24, className: "text-[#393E40]" })) }) }), _jsx("div", { className: clsx("flex min-w-0 flex-1 overflow-hidden rounded-xl border bg-white transition-shadow", isFocused
252
+ return (_jsxs("div", { ref: containerRef, "data-testid": "biz-thread-detail-input", className: "flex w-full shrink-0 flex-col bg-[rgba(131,137,157,0.05)]", children: [_jsx("input", { ref: mediaInputRef, type: "file", accept: "image/jpeg,image/png,image/jpg,video/*", multiple: true, className: "hidden", onChange: handleMediaInputChange }), _jsx("input", { ref: documentInputRef, type: "file", accept: ".pdf,.doc,.docx", multiple: true, className: "hidden", onChange: handleDocumentInputChange }), listUploadFiles.length > 0 && (_jsx(FilePreviewList, { files: listUploadFiles, onRemove: (file) => handleRemoveFile(file.uid), onVideoReady: () => setIsVideoLoading(false) })), showLinkPreview && previewUrl && (_jsx(LinkInputPreview, { url: previewUrl, onRemove: () => setPreviewDismissed(true) })), _jsxs("div", { className: "flex items-end gap-3 px-3 py-2", children: [_jsx("div", { className: "flex shrink-0 items-center", children: _jsx("button", { type: "button", onClick: handleToggleMoreOption, "aria-label": t("thread_detail.more_option.toggle"), "aria-expanded": isMoreOptionOpen, "data-testid": "biz-thread-detail-more-option-toggle", className: "flex py-3 items-center justify-center rounded-full", children: isMoreOptionOpen ? (_jsx(Icon, { icon: "close-circle-o", size: 24, className: "text-[#1B3FE4]" })) : (_jsx(Icon, { icon: "plus-circle-o", size: 24, className: "text-[#393E40]" })) }) }), _jsx("div", { className: clsx("flex min-w-0 flex-1 overflow-hidden rounded-xl border bg-white transition-shadow", isFocused
250
253
  ? "border-[#607CFB] shadow-[0px_0px_2px_rgba(27,63,228,0.5)]"
251
254
  : "border-[#DDDEDF]"), style: { minHeight: CONTAINER_MIN_HEIGHT }, children: _jsx("div", { className: "flex min-w-0 flex-[1_0_0] items-center gap-2 self-stretch px-3 py-1", children: _jsx("textarea", { ref: textareaRef, value: value, rows: 1, onChange: (event) => handleChange(event.target.value), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t("thread_detail.input_placeholder"), "data-testid": "biz-thread-detail-message-input", className: "biz-message-input-scrollbar min-h-0 min-w-0 flex-1 resize-none appearance-none border-0 bg-transparent p-0 text-[16px] leading-[1.6] tracking-[0.16px] text-black outline-none ring-0 placeholder:text-[#747B7E] focus:border-0 focus:outline-none focus:ring-0" }) }) }), _jsx("div", { className: "flex shrink-0 items-center pb-1", children: _jsx("button", { type: "button", onClick: () => void handleSend(), disabled: !canSend, "data-testid": "biz-thread-detail-send", className: clsx("flex h-10 w-10 items-center justify-center rounded-full", canSend ? "bg-[#002BEB]" : "bg-[#002BEB] opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(BizMoreOptionPanel, { disabled: !isConversationReady, onTriggerMediaInput: () => { var _a; return (_a = mediaInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, onTriggerDocumentInput: () => { var _a; return (_a = documentInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } }))] }));
252
255
  };
@@ -0,0 +1,8 @@
1
+ interface BizVideoViewerProps {
2
+ videoUrl: string;
3
+ posterUrl?: string;
4
+ onClose: () => void;
5
+ }
6
+ declare const BizVideoViewer: ({ videoUrl, posterUrl, onClose }: BizVideoViewerProps) => import("react").ReactPortal;
7
+ export default BizVideoViewer;
8
+ //# sourceMappingURL=BizVideoViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BizVideoViewer.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/BizVideoViewer.tsx"],"names":[],"mappings":"AAQA,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID,QAAA,MAAM,cAAc,GAAI,kCAAkC,mBAAmB,gCAyM5E,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -0,0 +1,103 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { Icon } from "../../components/icon";
6
+ import { ExpandIcon } from "../../assets/svg";
7
+ import { formatDuration } from "../../utils/common";
8
+ const SKIP_SECONDS = 10;
9
+ const BizVideoViewer = ({ videoUrl, posterUrl, onClose }) => {
10
+ var _a;
11
+ const containerRef = useRef(null);
12
+ const videoRef = useRef(null);
13
+ const [isPlaying, setIsPlaying] = useState(false);
14
+ const [isMuted, setIsMuted] = useState(false);
15
+ const [isFullscreen, setIsFullscreen] = useState(false);
16
+ const [currentTime, setCurrentTime] = useState(0);
17
+ const [duration, setDuration] = useState(0);
18
+ const togglePlay = () => {
19
+ const video = videoRef.current;
20
+ if (!video)
21
+ return;
22
+ if (video.paused) {
23
+ void video.play();
24
+ }
25
+ else {
26
+ video.pause();
27
+ }
28
+ };
29
+ const skip = (seconds) => {
30
+ const video = videoRef.current;
31
+ if (!video)
32
+ return;
33
+ const max = video.duration || Infinity;
34
+ video.currentTime = Math.min(Math.max(video.currentTime + seconds, 0), max);
35
+ setCurrentTime(video.currentTime);
36
+ };
37
+ const toggleFullscreen = () => {
38
+ var _a, _b;
39
+ const el = containerRef.current;
40
+ if (!el)
41
+ return;
42
+ if (!document.fullscreenElement) {
43
+ void ((_a = el.requestFullscreen) === null || _a === void 0 ? void 0 : _a.call(el));
44
+ }
45
+ else {
46
+ void ((_b = document.exitFullscreen) === null || _b === void 0 ? void 0 : _b.call(document));
47
+ }
48
+ };
49
+ useEffect(() => {
50
+ const handleKeyDown = (event) => {
51
+ if (event.key === "Escape")
52
+ onClose();
53
+ if (event.key === " ") {
54
+ event.preventDefault();
55
+ togglePlay();
56
+ }
57
+ if (event.key === "ArrowLeft")
58
+ skip(-SKIP_SECONDS);
59
+ if (event.key === "ArrowRight")
60
+ skip(SKIP_SECONDS);
61
+ };
62
+ document.addEventListener("keydown", handleKeyDown);
63
+ return () => document.removeEventListener("keydown", handleKeyDown);
64
+ // eslint-disable-next-line react-hooks/exhaustive-deps
65
+ }, [onClose]);
66
+ useEffect(() => {
67
+ const handleFullscreenChange = () => setIsFullscreen(document.fullscreenElement === containerRef.current);
68
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
69
+ return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
70
+ }, []);
71
+ useEffect(() => {
72
+ var _a;
73
+ (_a = videoRef.current) === null || _a === void 0 ? void 0 : _a.play().catch(() => { });
74
+ }, []);
75
+ const toggleMute = () => {
76
+ const video = videoRef.current;
77
+ if (!video)
78
+ return;
79
+ video.muted = !video.muted;
80
+ setIsMuted(video.muted);
81
+ };
82
+ const handleSeek = (event) => {
83
+ const video = videoRef.current;
84
+ if (!video)
85
+ return;
86
+ const value = Number(event.target.value);
87
+ video.currentTime = value;
88
+ setCurrentTime(value);
89
+ };
90
+ const handleDownload = () => {
91
+ const filename = videoUrl.substring(videoUrl.lastIndexOf("/") + 1) || "video";
92
+ const link = document.createElement("a");
93
+ link.href = videoUrl;
94
+ link.download = filename;
95
+ document.body.appendChild(link);
96
+ link.click();
97
+ document.body.removeChild(link);
98
+ };
99
+ const remainingTime = Math.max(duration - currentTime, 0);
100
+ const container = (_a = document.querySelector('[data-testid="biz-thread-detail-root"]')) !== null && _a !== void 0 ? _a : document.body;
101
+ return createPortal(_jsxs("div", { ref: containerRef, className: "absolute inset-0 z-[999] flex flex-col bg-black", children: [_jsxs("div", { className: "flex items-center justify-between px-4 pt-12 pb-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("button", { type: "button", className: "flex h-10 w-10 items-center justify-center text-white", onClick: onClose, "aria-label": "\u0110\u00F3ng", children: _jsx(Icon, { icon: "close-b", size: 24 }) }), _jsx("button", { type: "button", className: "flex h-10 w-10 items-center justify-center text-white", onClick: toggleFullscreen, "aria-label": isFullscreen ? "Thu nhỏ" : "Toàn màn hình", children: _jsx(ExpandIcon, { size: 20 }) })] }), _jsx("button", { type: "button", className: "flex h-10 w-10 items-center justify-center text-white", onClick: handleDownload, "aria-label": "T\u1EA3i xu\u1ED1ng", children: _jsx(Icon, { icon: "download-o", size: 24 }) })] }), _jsxs("div", { className: "relative flex min-h-0 flex-1 items-center justify-center overflow-hidden px-4", children: [_jsx("video", { ref: videoRef, src: videoUrl, poster: posterUrl, className: "max-h-full max-w-full rounded-[24px] object-contain", onPlay: () => setIsPlaying(true), onPause: () => setIsPlaying(false), onTimeUpdate: (event) => setCurrentTime(event.currentTarget.currentTime), onLoadedMetadata: (event) => setDuration(event.currentTarget.duration), onClick: togglePlay }), _jsxs("div", { className: "pointer-events-none absolute inset-0 flex items-center justify-between px-8", children: [_jsx("button", { type: "button", className: "pointer-events-auto relative flex h-9 w-9 items-center justify-center text-white", onClick: () => skip(-SKIP_SECONDS), "aria-label": "L\u00F9i 10 gi\u00E2y", children: _jsx(Icon, { icon: "arrow-reload-o", size: 30 }) }), !isPlaying && (_jsx("button", { type: "button", className: "pointer-events-auto flex h-[50px] w-[50px] shrink-0 items-center justify-center rounded-2xl bg-black/60", onClick: togglePlay, "aria-label": "Ph\u00E1t", children: _jsx(Icon, { icon: "play-b", size: 24, className: "text-white" }) })), _jsx("button", { type: "button", className: "pointer-events-auto relative flex h-9 w-9 items-center justify-center text-white", onClick: () => skip(SKIP_SECONDS), "aria-label": "Ti\u1EBFn 10 gi\u00E2y", children: _jsx(Icon, { icon: "arrow-reload-o", size: 30, className: "-scale-x-100" }) })] })] }), _jsx("div", { className: "flex flex-col gap-2 px-4 pb-8 pt-4", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "w-9 shrink-0 text-xs text-white", children: formatDuration(currentTime) }), _jsx("input", { type: "range", min: 0, max: duration || 0, step: 0.1, value: currentTime, onChange: handleSeek, className: "h-1 flex-1 cursor-pointer appearance-none rounded-full bg-white/30 accent-white" }), _jsxs("span", { className: "w-9 shrink-0 text-right text-xs text-white", children: ["-", formatDuration(remainingTime)] }), _jsx("button", { type: "button", onClick: toggleMute, className: "flex h-6 w-6 shrink-0 items-center justify-center text-white", "aria-label": "\u00C2m l\u01B0\u1EE3ng", children: _jsx(Icon, { icon: isMuted ? "volume-off-o" : "volume-up-o", size: 20 }) })] }) })] }), container);
102
+ };
103
+ export default BizVideoViewer;
@@ -3,12 +3,8 @@ export type { BizThreadDetailHeaderProps } from "./BizThreadDetailHeader";
3
3
  export { default as BizThreadDetailInput } from "./BizThreadDetailInput";
4
4
  export type { BizThreadDetailInputProps } from "./BizThreadDetailInput";
5
5
  export { default as BizMessageList } from "./BizMessageList";
6
- export { default as BizMessageItem } from "./item/BizMessageItem";
7
- export type { BizMessageItemProps } from "./item/BizMessageItem";
8
6
  export { default as BizMoreOptionPanel } from "./BizMoreOptionPanel";
9
7
  export type { BizMoreOptionPanelProps } from "./BizMoreOptionPanel";
10
8
  export { default as BizMoreOptionItem } from "./BizMoreOptionItem";
11
9
  export type { BizMoreOptionItemProps } from "./BizMoreOptionItem";
12
- export { default as BizAttachmentPreview } from "./BizAttachmentPreview";
13
- export type { BizAttachmentPreviewProps } from "./BizAttachmentPreview";
14
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/biz-thread-detail/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACzE,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACnE,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1,7 +1,5 @@
1
1
  export { default as BizThreadDetailHeader } from "./BizThreadDetailHeader";
2
2
  export { default as BizThreadDetailInput } from "./BizThreadDetailInput";
3
3
  export { default as BizMessageList } from "./BizMessageList";
4
- export { default as BizMessageItem } from "./item/BizMessageItem";
5
4
  export { default as BizMoreOptionPanel } from "./BizMoreOptionPanel";
6
5
  export { default as BizMoreOptionItem } from "./BizMoreOptionItem";
7
- export { default as BizAttachmentPreview } from "./BizAttachmentPreview";
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationBySessionItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationBySessionItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAQhD,UAAU,8BAA8B;IACtC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,yBAAyB,GAAI,iCAGhC,8BAA8B,mDAyFhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
1
+ {"version":3,"file":"ConversationBySessionItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationBySessionItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAE7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAShD,UAAU,8BAA8B;IACtC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,yBAAyB,GAAI,iCAGhC,8BAA8B,mDA8DhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
@@ -1,12 +1,13 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import useConversationStore from "../../store/conversation";
5
5
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
6
- import { Avatar, Badge } from "antd";
6
+ import { Avatar } from "antd";
7
7
  import { useChatContext } from "../../context/ChatContext";
8
8
  import { useConversationDisplayData } from "../../hooks/conversation/useConversation";
9
9
  import { formatTimestamp, parseLatestMessage } from "../../utils/common";
10
+ import ThreadCard from "./ThreadCard";
10
11
  const ConversationBySessionItem = ({ sessionItem, latestPreview, }) => {
11
12
  var _a;
12
13
  const { t } = useTranslation();
@@ -34,8 +35,8 @@ const ConversationBySessionItem = ({ sessionItem, latestPreview, }) => {
34
35
  const latestMsgSendTime = (latestPreview === null || latestPreview === void 0 ? void 0 : latestPreview.latestMsgSendTime) || 0;
35
36
  if (!conversation)
36
37
  return null;
37
- return (_jsxs("div", { onClick: () => handleConversationClick(conversation), className: `relative p-3 border-b border-gray-100 hover:bg-gray-100 cursor-pointer transition-colors ${isSelected ? "bg-blue-50" : "bg-white"}`, children: [isSelected && (_jsx("div", { className: "absolute left-0 top-0 bottom-0 w-1 bg-blue-500" })), _jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "relative flex-shrink-0", children: _jsx(Avatar, { size: 48, src: avatar, children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" }) }), _jsx("div", { className: "flex-1 min-w-0", children: _jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h3", { className: "font-semibold text-gray-900 text-sm truncate", children: displayName }), _jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: parseLatestMessage(latestMsg, user === null || user === void 0 ? void 0 : user.userID, t) })] }), _jsxs("div", { className: "flex flex-col items-end gap-1 ml-2", children: [latestMsgSendTime > 0 && (_jsx("span", { className: "text-xs text-gray-400", children: formatTimestamp(latestMsgSendTime, {
38
- hasTime: true,
39
- }) })), _jsx("div", { className: "flex items-center gap-1", children: conversation.unreadCount > 0 && (_jsx(Badge, { count: conversation.unreadCount })) })] })] }) })] })] }, conversation.conversationID));
38
+ return (_jsx(ThreadCard, { onClick: () => handleConversationClick(conversation), isSelected: isSelected, unreadCount: conversation.unreadCount, preview: parseLatestMessage(latestMsg, user === null || user === void 0 ? void 0 : user.userID, t), time: latestMsgSendTime > 0
39
+ ? formatTimestamp(latestMsgSendTime, { hasTime: true })
40
+ : undefined, avatar: _jsx(Avatar, { size: 48, src: avatar, children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" }), title: _jsx("h3", { className: "truncate text-sm font-semibold text-gray-900", children: displayName }) }));
40
41
  };
41
42
  export default ConversationBySessionItem;