@droppii-org/chat-sdk 0.1.1 → 0.1.3

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 (107) hide show
  1. package/dist/assets/sdk/sql-wasm.wasm +0 -0
  2. package/dist/components/conversation/ConversationBySessionItem.d.ts.map +1 -1
  3. package/dist/components/conversation/ConversationBySessionItem.js +6 -2
  4. package/dist/components/mediaCollection/LinkCollection.js +1 -1
  5. package/dist/components/message/MediaPreviewIcon.d.ts +7 -0
  6. package/dist/components/message/MediaPreviewIcon.d.ts.map +1 -0
  7. package/dist/components/message/MediaPreviewIcon.js +24 -0
  8. package/dist/components/message/MessageHeader.js +1 -1
  9. package/dist/components/message/MessageList.d.ts.map +1 -1
  10. package/dist/components/message/MessageList.js +48 -5
  11. package/dist/components/message/footer/ActionBar.d.ts.map +1 -1
  12. package/dist/components/message/footer/ActionBar.js +15 -86
  13. package/dist/components/message/footer/EmojiPicker.d.ts.map +1 -1
  14. package/dist/components/message/footer/EmojiPicker.js +9 -5
  15. package/dist/components/message/footer/EnterHandler.d.ts.map +1 -1
  16. package/dist/components/message/footer/EnterHandler.js +16 -5
  17. package/dist/components/message/footer/FilePreview.d.ts +5 -0
  18. package/dist/components/message/footer/FilePreview.d.ts.map +1 -1
  19. package/dist/components/message/footer/FilePreview.js +15 -12
  20. package/dist/components/message/footer/MediaActions.d.ts +10 -0
  21. package/dist/components/message/footer/MediaActions.d.ts.map +1 -0
  22. package/dist/components/message/footer/MediaActions.js +78 -0
  23. package/dist/components/message/footer/QuotedMessage.d.ts +2 -0
  24. package/dist/components/message/footer/QuotedMessage.d.ts.map +1 -0
  25. package/dist/components/message/footer/QuotedMessage.js +24 -0
  26. package/dist/components/message/footer/editorConfig.d.ts +24 -0
  27. package/dist/components/message/footer/editorConfig.d.ts.map +1 -0
  28. package/dist/components/message/footer/editorConfig.js +33 -0
  29. package/dist/components/message/footer/index.d.ts.map +1 -1
  30. package/dist/components/message/footer/index.js +6 -27
  31. package/dist/components/message/item/MessageStatusIndicator.d.ts +8 -0
  32. package/dist/components/message/item/MessageStatusIndicator.d.ts.map +1 -0
  33. package/dist/components/message/item/MessageStatusIndicator.js +16 -0
  34. package/dist/components/message/item/QuoteMessage.d.ts +9 -0
  35. package/dist/components/message/item/QuoteMessage.d.ts.map +1 -0
  36. package/dist/components/message/item/QuoteMessage.js +22 -0
  37. package/dist/components/message/item/RevokeMessage.d.ts +5 -0
  38. package/dist/components/message/item/RevokeMessage.d.ts.map +1 -0
  39. package/dist/components/message/item/RevokeMessage.js +8 -0
  40. package/dist/components/message/item/TextMessage.js +1 -1
  41. package/dist/components/message/item/UrlTextMessage.d.ts.map +1 -1
  42. package/dist/components/message/item/UrlTextMessage.js +3 -3
  43. package/dist/components/message/item/index.d.ts +6 -1
  44. package/dist/components/message/item/index.d.ts.map +1 -1
  45. package/dist/components/message/item/index.js +88 -25
  46. package/dist/components/richTextEditor/RichTextEditor.d.ts +12 -0
  47. package/dist/components/richTextEditor/RichTextEditor.d.ts.map +1 -0
  48. package/dist/components/richTextEditor/RichTextEditor.js +62 -0
  49. package/dist/components/searchConversation/SearchDrawer.js +1 -1
  50. package/dist/components/searchConversation/item/SearchItemAsMessage.d.ts +3 -1
  51. package/dist/components/searchConversation/item/SearchItemAsMessage.d.ts.map +1 -1
  52. package/dist/components/searchConversation/item/SearchItemAsMessage.js +5 -2
  53. package/dist/components/thread/AssignConfirmModal.d.ts +12 -0
  54. package/dist/components/thread/AssignConfirmModal.d.ts.map +1 -0
  55. package/dist/components/thread/AssignConfirmModal.js +11 -0
  56. package/dist/components/thread/ManualAssignPopover.d.ts +14 -0
  57. package/dist/components/thread/ManualAssignPopover.d.ts.map +1 -0
  58. package/dist/components/thread/ManualAssignPopover.js +83 -0
  59. package/dist/components/thread/SessionSection.d.ts.map +1 -1
  60. package/dist/components/thread/SessionSection.js +11 -6
  61. package/dist/components/thread/UserSection.js +1 -1
  62. package/dist/hooks/message/useMessage.d.ts +1 -0
  63. package/dist/hooks/message/useMessage.d.ts.map +1 -1
  64. package/dist/hooks/message/useMessage.js +7 -0
  65. package/dist/hooks/message/useRevokeMessage.d.ts +5 -0
  66. package/dist/hooks/message/useRevokeMessage.d.ts.map +1 -0
  67. package/dist/hooks/message/useRevokeMessage.js +16 -0
  68. package/dist/hooks/message/useSendMessage.d.ts +6 -0
  69. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  70. package/dist/hooks/message/useSendMessage.js +170 -32
  71. package/dist/hooks/session/useAssignSession.d.ts +8 -0
  72. package/dist/hooks/session/useAssignSession.d.ts.map +1 -0
  73. package/dist/hooks/session/useAssignSession.js +15 -0
  74. package/dist/hooks/session/useCreateNote.d.ts.map +1 -1
  75. package/dist/hooks/session/useCreateNote.js +2 -1
  76. package/dist/hooks/session/useGetTeamSupporters.d.ts +8 -0
  77. package/dist/hooks/session/useGetTeamSupporters.d.ts.map +1 -0
  78. package/dist/hooks/session/useGetTeamSupporters.js +20 -0
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +1 -0
  82. package/dist/locales/vi/common.json +49 -37
  83. package/dist/services/query.d.ts +2 -0
  84. package/dist/services/query.d.ts.map +1 -1
  85. package/dist/services/query.js +2 -0
  86. package/dist/services/routes.d.ts +2 -0
  87. package/dist/services/routes.d.ts.map +1 -1
  88. package/dist/services/routes.js +2 -0
  89. package/dist/store/conversation.d.ts.map +1 -1
  90. package/dist/store/conversation.js +7 -1
  91. package/dist/styles/global.css +1 -1
  92. package/dist/tsconfig.tsbuildinfo +1 -1
  93. package/dist/types/chat.d.ts +1 -1
  94. package/dist/types/chat.d.ts.map +1 -1
  95. package/dist/types/dto.d.ts +26 -0
  96. package/dist/types/dto.d.ts.map +1 -1
  97. package/dist/utils/common.d.ts +3 -2
  98. package/dist/utils/common.d.ts.map +1 -1
  99. package/dist/utils/common.js +43 -19
  100. package/dist/utils/events.d.ts +1 -0
  101. package/dist/utils/events.d.ts.map +1 -1
  102. package/dist/utils/fileValidation.d.ts.map +1 -1
  103. package/dist/utils/fileValidation.js +2 -8
  104. package/dist/utils/queryHelpers.d.ts +3 -0
  105. package/dist/utils/queryHelpers.d.ts.map +1 -0
  106. package/dist/utils/queryHelpers.js +11 -0
  107. package/package.json +11 -10
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationBySessionItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationBySessionItem.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAQpD,UAAU,8BAA8B;IACtC,WAAW,EAAE,gBAAgB,CAAC;CAC/B;AAED,QAAA,MAAM,yBAAyB,GAAI,kBAEhC,8BAA8B,mDA0FhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
1
+ {"version":3,"file":"ConversationBySessionItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationBySessionItem.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAQpD,UAAU,8BAA8B;IACtC,WAAW,EAAE,gBAAgB,CAAC;CAC/B;AAED,QAAA,MAAM,yBAAyB,GAAI,kBAEhC,8BAA8B,mDA2FhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
@@ -10,8 +10,12 @@ import { formatTimestamp, parseLatestMessage } from "../../utils/common";
10
10
  const ConversationBySessionItem = ({ sessionItem, }) => {
11
11
  var _a;
12
12
  const { t } = useTranslation();
13
- const { user } = useChatContext();
14
- const isSelected = useConversationStore((state) => { var _a; return state.selectedConversationId === ((_a = sessionItem === null || sessionItem === void 0 ? void 0 : sessionItem.conversation) === null || _a === void 0 ? void 0 : _a.conversationID); });
13
+ const user = useChatContext().user;
14
+ const isSelected = useConversationStore((state) => {
15
+ var _a;
16
+ return state.selectedConversationId ===
17
+ ((_a = sessionItem === null || sessionItem === void 0 ? void 0 : sessionItem.conversation) === null || _a === void 0 ? void 0 : _a.conversationID);
18
+ });
15
19
  const conversation = sessionItem === null || sessionItem === void 0 ? void 0 : sessionItem.conversation;
16
20
  const router = useRouter();
17
21
  const pathname = usePathname();
@@ -73,7 +73,7 @@ const LinkCollection = ({ onClose }) => {
73
73
  const menuItems = [
74
74
  {
75
75
  key: "open",
76
- label: "Xem tin nhắn",
76
+ label: t("view_message"),
77
77
  onClick: () => onPressItem(item.chatLog),
78
78
  },
79
79
  ];
@@ -0,0 +1,7 @@
1
+ import { MessageItem } from "@openim/wasm-client-sdk";
2
+ interface MediaPreviewIconProps {
3
+ message?: MessageItem;
4
+ }
5
+ declare const MediaPreviewIcon: ({ message }: MediaPreviewIconProps) => import("react/jsx-runtime").JSX.Element | null;
6
+ export default MediaPreviewIcon;
7
+ //# sourceMappingURL=MediaPreviewIcon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MediaPreviewIcon.d.ts","sourceRoot":"","sources":["../../../src/components/message/MediaPreviewIcon.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,yBAAyB,CAAC;AAInE,UAAU,qBAAqB;IAC7B,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,QAAA,MAAM,gBAAgB,GAAI,aAAa,qBAAqB,mDA8B3D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { MessageType } from "@openim/wasm-client-sdk";
3
+ import { Image } from "antd";
4
+ import { documentIcon } from "../../assets/svg";
5
+ const MediaPreviewIcon = ({ message }) => {
6
+ var _a, _b;
7
+ if (!message) {
8
+ return null;
9
+ }
10
+ if (message.contentType === MessageType.FileMessage) {
11
+ return _jsx("div", { children: documentIcon });
12
+ }
13
+ if (message.contentType === MessageType.PictureMessage) {
14
+ const imageUrl = ((_a = message.pictureElem.sourcePicture) === null || _a === void 0 ? void 0 : _a.url) ||
15
+ ((_b = message.pictureElem.snapshotPicture) === null || _b === void 0 ? void 0 : _b.url);
16
+ return (_jsx("div", { className: "w-10 h-10", children: _jsx(Image, { rootClassName: "message-image cursor-pointer", className: "rounded-md", src: imageUrl }) }));
17
+ }
18
+ if (message.contentType === MessageType.VideoMessage) {
19
+ const videoUrl = message.videoElem.videoUrl;
20
+ return (_jsx("div", { className: "flex flex-col justify-center items-center w-10 h-10 bg-black rounded-md", children: _jsx("video", { src: videoUrl, controls: false, autoPlay: false, muted: true }) }));
21
+ }
22
+ return null;
23
+ };
24
+ export default MediaPreviewIcon;
@@ -122,6 +122,6 @@ const MessageHeader = ({ onClose, currentSession }) => {
122
122
  setCurrentSessionStatus(currentSession.status);
123
123
  }
124
124
  }, [currentSession]);
125
- return (_jsxs("div", { className: "px-4 py-3 flex items-center border-b gap-3 bg-white flex-wrap", children: [_jsx(Avatar, { src: avatar, size: "large", className: "min-w-10 min-h-10", children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" }), _jsxs("div", { className: "flex flex-col overflow-hidden flex-1", children: [_jsx("p", { className: "text-base truncate", children: displayName || "" }), _jsx("p", { className: "text-xs text-gray-500 truncate", children: "2 thành viên" })] }), _jsxs("div", { className: "flex items-center gap-2 justify-end overflow-hidden", children: [isCx && (_jsx(SelectSession, { placeholder: t("select_tag"), options: tagOptions, value: currentSessionTag, onChange: (value) => handleUpdateSession(value, "tag", value === currentSessionTag), excludeOptions: [SessionTag.SLOW_PROCESSING, SessionTag.NONE] })), isCx && (_jsx(SelectSession, { placeholder: t("select_status"), options: statusOptions, value: currentSessionStatus, onChange: (value) => handleUpdateSession(value, "status", value === currentSessionStatus) })), _jsx(SearchDrawer, {}), _jsx(MediaCollection, {}), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", children: _jsx(Icon, { icon: "align-justify-o", size: 22 }) }), !!onClose && (_jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: onClose, children: _jsx(Icon, { icon: "close-b", size: 22 }) }))] })] }));
125
+ return (_jsxs("div", { className: "px-4 py-3 flex items-center border-b gap-3 bg-white flex-wrap", children: [_jsx(Avatar, { src: avatar, size: "large", className: "min-w-10 min-h-10", children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" }), _jsxs("div", { className: "flex flex-col overflow-hidden flex-1", children: [_jsx("p", { className: "text-base truncate", children: displayName || "" }), _jsx("p", { className: "text-xs text-gray-500 truncate", children: t("member_count", { count: 2 }) })] }), _jsxs("div", { className: "flex items-center gap-2 justify-end overflow-hidden", children: [isCx && (_jsx(SelectSession, { placeholder: t("select_tag"), options: tagOptions, value: currentSessionTag, onChange: (value) => handleUpdateSession(value, "tag", value === currentSessionTag), excludeOptions: [SessionTag.SLOW_PROCESSING, SessionTag.NONE] })), isCx && (_jsx(SelectSession, { placeholder: t("select_status"), options: statusOptions, value: currentSessionStatus, onChange: (value) => handleUpdateSession(value, "status", value === currentSessionStatus) })), _jsx(SearchDrawer, {}), _jsx(MediaCollection, {}), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", children: _jsx(Icon, { icon: "align-justify-o", size: 22 }) }), !!onClose && (_jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: onClose, children: _jsx(Icon, { icon: "close-b", size: 22 }) }))] })] }));
126
126
  };
127
127
  export default MessageHeader;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAwBA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAGD,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAqN3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAyBA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAGD,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CA6S3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useMessage } from "../../hooks/message/useMessage";
4
- import { Empty, Spin } from "antd";
5
- import { useEffect, useMemo, useRef } from "react";
4
+ import { Empty, Modal, Spin } from "antd";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
6
  import dayjs from "dayjs";
7
7
  import isToday from "dayjs/plugin/isToday";
8
8
  import emitter from "../../utils/events";
@@ -14,11 +14,12 @@ import { images } from "../../constants/images";
14
14
  import { useTranslation } from "react-i18next";
15
15
  import useConversationStore from "../../store/conversation";
16
16
  import { isNumber } from "lodash";
17
- import { useDebounceFn } from "ahooks";
17
+ import { useBoolean, useDebounceFn } from "ahooks";
18
18
  import { MSG_ITEM_CONTENT_PREFIX, MSG_ITEM_PREFIX } from "../../constants";
19
19
  import { markConversationMessageAsRead } from "../../hooks/conversation/useConversation";
20
20
  import { useChatContext } from "../../context/ChatContext";
21
21
  import { useGetSession } from "../../hooks/session/useGetSession";
22
+ import { useRevokeMessage } from "../../hooks/message/useRevokeMessage";
22
23
  dayjs.extend(isToday);
23
24
  const BOTTOM_THRESHOLD = -5;
24
25
  const MessageList = (props) => {
@@ -29,11 +30,34 @@ const MessageList = (props) => {
29
30
  const scrollRef = useRef(null);
30
31
  const { getMoreOldMessages, moreOldLoading, loadState, getMoreNewMessages, moreNewLoading, latestLoadState, } = useMessage(conversationId, searchClientMsgID);
31
32
  const conversationData = useConversationStore((state) => state.conversationData);
33
+ const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
34
+ const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
32
35
  const { dataFlatten: sessions, refetch: refetchSession } = useGetSession({
33
36
  filter: {
34
37
  conversationIds: !!conversationId ? [conversationId] : [],
35
38
  },
36
39
  });
40
+ const [openMenuId, setOpenMenuId] = useState(null);
41
+ const [selectedItem, setSelectedItem] = useState(null);
42
+ const [showConfirmRevoke, { setTrue: openConfirmRevoke, setFalse: closeConfirmRevoke },] = useBoolean(false);
43
+ const { revokeMessage, loading: isRevoking } = useRevokeMessage();
44
+ const handleOpenRevoke = useCallback((clientMsgID) => {
45
+ setSelectedItem(clientMsgID);
46
+ openConfirmRevoke();
47
+ }, [openConfirmRevoke]);
48
+ const handleCloseRevoke = useCallback(() => {
49
+ setSelectedItem(null);
50
+ closeConfirmRevoke();
51
+ }, [closeConfirmRevoke]);
52
+ const onRevokeMessage = useCallback(async () => {
53
+ await revokeMessage((conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "", selectedItem || "");
54
+ handleCloseRevoke();
55
+ }, [
56
+ conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID,
57
+ selectedItem,
58
+ revokeMessage,
59
+ handleCloseRevoke,
60
+ ]);
37
61
  const currentSession = useMemo(() => {
38
62
  return sessions === null || sessions === void 0 ? void 0 : sessions.find((session) => session.conversationId === conversationId);
39
63
  }, [sessions, conversationId]);
@@ -84,6 +108,15 @@ const MessageList = (props) => {
84
108
  }, 500);
85
109
  }, 200);
86
110
  };
111
+ const onPressQuoteMessage = useCallback((clientMsgID) => {
112
+ const hasMessage = loadState.messageList.some((msg) => msg.clientMsgID === clientMsgID);
113
+ if (hasMessage) {
114
+ scrollToMessage(clientMsgID);
115
+ }
116
+ else {
117
+ setSearchClientMsgID(clientMsgID);
118
+ }
119
+ }, [scrollToMessage, loadState.messageList, setSearchClientMsgID]);
87
120
  const loadMoreOldMessage = () => {
88
121
  if (!loadState.hasMoreOld || moreOldLoading)
89
122
  return;
@@ -123,6 +156,16 @@ const MessageList = (props) => {
123
156
  loadState.initLoading,
124
157
  handleMarkConversationMessageAsRead,
125
158
  ]);
159
+ useEffect(() => {
160
+ if (!openMenuId)
161
+ return;
162
+ const scrollContainer = scrollRef.current;
163
+ if (!scrollContainer)
164
+ return;
165
+ const close = () => setOpenMenuId(null);
166
+ scrollContainer.addEventListener("scroll", close);
167
+ return () => scrollContainer.removeEventListener("scroll", close);
168
+ }, [openMenuId]);
126
169
  if (!conversationData) {
127
170
  return (_jsx("div", { className: "flex flex-1 items-center justify-center h-full", children: _jsx(Empty, { description: t("no_conversation_data") }) }));
128
171
  }
@@ -142,13 +185,13 @@ const MessageList = (props) => {
142
185
  display: "flex",
143
186
  flexDirection: "column-reverse",
144
187
  minWidth: 0,
145
- width: "100%"
188
+ width: "100%",
146
189
  }, inverse: true, hasMore: loadState.hasMoreOld, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableMessagesDiv", onScroll: (e) => {
147
190
  const target = e.target;
148
191
  if (target.scrollTop > BOTTOM_THRESHOLD) {
149
192
  handleMarkConversationMessageAsRead();
150
193
  loadMoreNewMessage();
151
194
  }
152
- }, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array }, message.clientMsgID))) }) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), _jsx(MessageFooter, { currentSession: currentSession })] }));
195
+ }, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage }, message.clientMsgID))) }) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), _jsx(MessageFooter, { currentSession: currentSession }), _jsx(Modal, { centered: true, open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, getContainer: false, forceRender: true, children: _jsx("p", { children: t("revoke_message_confirm_message") }) })] }));
153
196
  };
154
197
  export default MessageList;
@@ -1 +1 @@
1
- {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ActionBar.tsx"],"names":[],"mappings":"AAoFA,QAAA,MAAM,SAAS,+CA0Od,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ActionBar.tsx"],"names":[],"mappings":"AAsBA,QAAA,MAAM,SAAS,+CA0Gd,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -1,9 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useEffect, useRef, useState } from "react";
4
- import { Button, Upload, message, } from "antd";
5
- import clsx from "clsx";
6
- import EmojiPicker from "./EmojiPicker";
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+ import { Button } from "antd";
7
5
  import { Icon } from "../../icon";
8
6
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
9
7
  import { $createParagraphNode, $getRoot, $getSelection, $isRangeSelection, } from "lexical";
@@ -11,33 +9,27 @@ import { $generateHtmlFromNodes } from "@lexical/html";
11
9
  import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
12
10
  import { $isQuoteNode } from "@lexical/rich-text";
13
11
  import { useMessageFooterContext } from ".";
14
- import { useTranslation } from "react-i18next";
15
- import { validateFile, validateVideoLimit } from "../../../utils/fileValidation";
16
- const documentTypes = [
17
- "application/pdf",
18
- "application/msword",
19
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
20
- ];
21
- const EmojiIcon = (_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M14 8.39997C14 8.8418 13.6418 9.19997 13.2 9.19997C12.7581 9.19997 12.4 8.8418 12.4 8.39997C12.4 7.95814 12.7581 7.59997 13.2 7.59997C13.6418 7.59997 14 7.95814 14 8.39997Z", fill: "white" }), _jsx("path", { d: "M7.59997 8.39997C7.59997 8.8418 7.2418 9.19997 6.79997 9.19997C6.35814 9.19997 5.99997 8.8418 5.99997 8.39997C5.99997 7.95814 6.35814 7.59997 6.79997 7.59997C7.2418 7.59997 7.59997 7.95814 7.59997 8.39997Z", fill: "white" }), _jsx("path", { d: "M7.59997 12.4C7.59997 12.4 8.49997 13.2 9.99997 13.2C11.5 13.2 12.4 12.4 12.4 12.4M14 8.39997C14 8.8418 13.6418 9.19997 13.2 9.19997C12.7581 9.19997 12.4 8.8418 12.4 8.39997C12.4 7.95814 12.7581 7.59997 13.2 7.59997C13.6418 7.59997 14 7.95814 14 8.39997ZM18 9.99997C18 14.4182 14.4182 18 9.99997 18C5.58169 18 1.99997 14.4182 1.99997 9.99997C1.99997 5.58169 5.58169 1.99997 9.99997 1.99997C14.4182 1.99997 18 5.58169 18 9.99997ZM7.59997 8.39997C7.59997 8.8418 7.2418 9.19997 6.79997 9.19997C6.35814 9.19997 5.99997 8.8418 5.99997 8.39997C5.99997 7.95814 6.35814 7.59997 6.79997 7.59997C7.2418 7.59997 7.59997 7.95814 7.59997 8.39997Z", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })] }));
22
- const AttachIcon = (_jsx("svg", { width: "22", height: "22", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M15.6 10.0001V11.2001C15.6 14.5137 12.9137 17.2001 9.59998 17.2001C6.28626 17.2001 3.59998 14.5137 3.59998 11.2001V6.79999C3.59998 4.59085 5.39084 2.79999 7.59998 2.79999C9.8091 2.79999 11.6 4.59085 11.6 6.79999V11.2C11.6 12.3045 10.7045 13.2 9.59998 13.2C8.49542 13.2 7.59998 12.3045 7.59998 11.2V7.99999", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
12
+ import MediaActions from "./MediaActions";
13
+ import useConversationStore from "../../../store/conversation";
23
14
  const ActionBar = () => {
24
15
  const [editor] = useLexicalComposerContext();
25
- const { onSendMessage, setListUploadFiles } = useMessageFooterContext();
26
- const containerRef = useRef(null);
27
- const [showEmojiPicker, setShowEmojiPicker] = useState(false);
28
- const { listUploadFiles } = useMessageFooterContext();
16
+ const { onSendMessage, listUploadFiles, setListUploadFiles } = useMessageFooterContext();
17
+ const quotedMessage = useConversationStore((state) => state.quotedMessage);
29
18
  const [isEmptyInput, setIsEmptyInput] = useState(true);
30
- const { t } = useTranslation();
31
- const canSend = !isEmptyInput || listUploadFiles.length > 0;
19
+ const canSend = useMemo(() => {
20
+ const hasQuote = !!(quotedMessage === null || quotedMessage === void 0 ? void 0 : quotedMessage.clientMsgID);
21
+ return hasQuote
22
+ ? !isEmptyInput
23
+ : !isEmptyInput || listUploadFiles.length > 0;
24
+ }, [isEmptyInput, listUploadFiles === null || listUploadFiles === void 0 ? void 0 : listUploadFiles.length, quotedMessage === null || quotedMessage === void 0 ? void 0 : quotedMessage.clientMsgID]);
32
25
  const handleSend = useCallback(() => {
33
26
  let plainText = "";
34
27
  let richText = "";
35
- // lấy plain text & html
36
28
  editor.getEditorState().read(() => {
37
29
  plainText = $getRoot().getTextContent();
38
30
  richText = $generateHtmlFromNodes(editor);
39
31
  });
40
- if (plainText.trim().length > 0 || listUploadFiles.length > 0) {
32
+ if (canSend) {
41
33
  onSendMessage({
42
34
  plainText,
43
35
  richText,
@@ -67,9 +59,7 @@ const ActionBar = () => {
67
59
  selection.formatText("strikethrough");
68
60
  }
69
61
  if (isQuoteNode) {
70
- // Nếu đang là QuoteNode → chuyển về Paragraph (bình thường)
71
62
  const paragraph = $createParagraphNode();
72
- // copy con của quote sang paragraph
73
63
  const children = topLevelNode.getChildren();
74
64
  children.forEach((child) => paragraph.append(child));
75
65
  topLevelNode.replace(paragraph);
@@ -81,68 +71,7 @@ const ActionBar = () => {
81
71
  }
82
72
  }
83
73
  });
84
- }, [editor, onSendMessage]);
85
- const handleEmojiSelect = useCallback((emoji) => {
86
- editor.update(() => {
87
- const selection = $getSelection();
88
- if ($isRangeSelection(selection)) {
89
- selection.insertText(emoji); // chèn emoji như text bình thường
90
- }
91
- });
92
- setShowEmojiPicker(false);
93
- }, [editor]);
94
- const beforeUploadImagesAndVideo = (file, fileList) => {
95
- // Validate file type and size
96
- const validation = validateFile(file, t);
97
- if (!validation.isValid) {
98
- message.error(validation.error);
99
- return Upload.LIST_IGNORE;
100
- }
101
- // Validate video limit
102
- const videoValidation = validateVideoLimit([file], listUploadFiles, t);
103
- if (!videoValidation.isValid) {
104
- message.error(videoValidation.error);
105
- return Upload.LIST_IGNORE;
106
- }
107
- // Check if multiple videos in current upload batch
108
- const newVideos = fileList.filter((f) => { var _a; return (_a = f.type) === null || _a === void 0 ? void 0 : _a.startsWith("video/"); });
109
- if (newVideos.length > 1) {
110
- message.error(t("video_limit_exceeded", {
111
- defaultValue: "Chỉ được phép tải lên 1 video duy nhất",
112
- }));
113
- return Upload.LIST_IGNORE;
114
- }
115
- return false;
116
- };
117
- const beforeUploadFile = (file) => {
118
- const isAllowed = documentTypes.includes(file.type);
119
- if (!isAllowed) {
120
- message.error(`${file.name} không đúng định dạng (chỉ hỗ trợ PDF, DOC, DOCX)`);
121
- return Upload.LIST_IGNORE;
122
- }
123
- return false;
124
- };
125
- const handleChange = (info) => {
126
- var _a;
127
- let newList = [...info.fileList];
128
- const lastFile = info.file;
129
- if (documentTypes.includes(lastFile.type || "")) {
130
- newList = newList.filter((f) => !documentTypes.includes(f.type || ""));
131
- const originFile = ((_a = lastFile.originFileObj) !== null && _a !== void 0 ? _a : lastFile);
132
- newList.push(Object.assign(Object.assign({}, lastFile), { originFileObj: originFile }));
133
- }
134
- setListUploadFiles(newList);
135
- };
136
- useEffect(() => {
137
- const handleClickOutside = (event) => {
138
- if (containerRef.current &&
139
- !containerRef.current.contains(event.target)) {
140
- setShowEmojiPicker(false);
141
- }
142
- };
143
- document.addEventListener("mousedown", handleClickOutside);
144
- return () => document.removeEventListener("mousedown", handleClickOutside);
145
- }, []);
74
+ }, [editor, onSendMessage, canSend]);
146
75
  useEffect(() => {
147
76
  return editor.registerUpdateListener(({ editorState }) => {
148
77
  editorState.read(() => {
@@ -152,6 +81,6 @@ const ActionBar = () => {
152
81
  });
153
82
  });
154
83
  }, [editor]);
155
- return (_jsxs("div", { className: "flex items-center justify-between px-4", ref: containerRef, children: [_jsxs("div", { className: "flex items-center gap-3 relative", children: [_jsx(Button, { type: "text", shape: "default", className: clsx("text-gray-500 w-8 h-8 p-0", showEmojiPicker ? "bg-blue-100 text-blue-600" : "text-gray-500"), onClick: () => setShowEmojiPicker(!showEmojiPicker), children: EmojiIcon }), _jsx(Upload, { accept: "image/jpeg, image/png, image/jpg, video/*", beforeUpload: beforeUploadImagesAndVideo, multiple: true, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0 text-gray-500", children: _jsx(Icon, { icon: "image-02-o", size: 22 }) }) }), _jsx(Upload, { accept: ".doc,.docx,.pdf", beforeUpload: beforeUploadFile, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0 text-gray-500", children: AttachIcon }) }), showEmojiPicker && (_jsx(EmojiPicker, { onEmojiSelect: handleEmojiSelect, onClose: () => setShowEmojiPicker(false) }))] }), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleSend, disabled: !canSend, children: _jsx(Icon, { icon: "send-b", size: 28, className: `${canSend ? "text-blue-500" : "text-gray-400"}` }) })] }));
84
+ return (_jsxs("div", { className: "flex items-center justify-between px-4", children: [_jsx(MediaActions, { listUploadFiles: listUploadFiles, onFilesChange: setListUploadFiles, buttonClassName: "text-gray-500 w-8 h-8 p-0" }), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleSend, disabled: !canSend, children: _jsx(Icon, { icon: "send-b", size: 28, className: `${canSend ? "text-blue-500" : "text-gray-400"}` }) })] }));
156
85
  };
157
86
  export default ActionBar;
@@ -1 +1 @@
1
- {"version":3,"file":"EmojiPicker.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EmojiPicker.tsx"],"names":[],"mappings":"AAIA,UAAU,gBAAgB;IACxB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,QAAA,MAAM,WAAW,GAAI,4BAA4B,gBAAgB,4CAyLhE,CAAA;AAED,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"EmojiPicker.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EmojiPicker.tsx"],"names":[],"mappings":"AAKA,UAAU,gBAAgB;IACxB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,WAAW,GAAI,4BAA4B,gBAAgB,4CAiMhE,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,11 +1,13 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
+ import { useTranslation } from "react-i18next";
4
5
  const EmojiPicker = ({ onEmojiSelect, onClose }) => {
6
+ const { t } = useTranslation("common");
5
7
  const [activeCategory, setActiveCategory] = useState("smileys");
6
8
  const emojiCategories = {
7
9
  smileys: {
8
- name: "Smileys & People",
10
+ name: t("emoji_smileys"),
9
11
  emojis: [
10
12
  "😀",
11
13
  "😃",
@@ -41,7 +43,7 @@ const EmojiPicker = ({ onEmojiSelect, onClose }) => {
41
43
  ],
42
44
  },
43
45
  nature: {
44
- name: "Animals & Nature",
46
+ name: t("emoji_nature"),
45
47
  emojis: [
46
48
  "🐶",
47
49
  "🐱",
@@ -76,7 +78,7 @@ const EmojiPicker = ({ onEmojiSelect, onClose }) => {
76
78
  ],
77
79
  },
78
80
  food: {
79
- name: "Food & Drink",
81
+ name: t("emoji_food"),
80
82
  emojis: [
81
83
  "🍎",
82
84
  "🍐",
@@ -111,7 +113,7 @@ const EmojiPicker = ({ onEmojiSelect, onClose }) => {
111
113
  ],
112
114
  },
113
115
  activities: {
114
- name: "Activities",
116
+ name: t("emoji_activities"),
115
117
  emojis: [
116
118
  "⚽",
117
119
  "🏀",
@@ -146,6 +148,8 @@ const EmojiPicker = ({ onEmojiSelect, onClose }) => {
146
148
  ],
147
149
  },
148
150
  };
149
- return (_jsx("div", { className: "absolute bottom-full left-0 mb-2 bg-white border border-gray-200 rounded-xl shadow-lg w-80 z-50", children: _jsxs("div", { className: "p-3", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "font-medium text-gray-900", children: "Choose an emoji" }), _jsx("button", { onClick: onClose, className: "text-gray-400 hover:text-gray-600 text-xl", children: "\u00D7" })] }), _jsx("div", { className: "flex gap-1 mb-3", children: Object.entries(emojiCategories).map(([key, category]) => (_jsx("button", { onClick: () => setActiveCategory(key), className: `px-3 py-1 text-xs rounded-full transition-colors ${activeCategory === key ? "bg-blue-100 text-blue-600" : "text-gray-500 hover:text-gray-700"}`, children: category.name.split(" ")[0] }, key))) }), _jsx("div", { className: "grid grid-cols-8 gap-1 max-h-48 overflow-y-auto", children: emojiCategories[activeCategory].emojis.map((emoji, index) => (_jsx("button", { onClick: () => onEmojiSelect(emoji), className: "p-2 text-xl hover:bg-gray-100 rounded-lg transition-colors", children: emoji }, index))) })] }) }));
151
+ return (_jsx("div", { className: "absolute bottom-full left-0 mb-2 bg-white border border-gray-200 rounded-xl shadow-lg w-96 z-50", children: _jsxs("div", { className: "p-3", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "font-medium text-gray-900", children: t("choose_emoji") }), _jsx("button", { onClick: onClose, className: "text-gray-400 hover:text-gray-600 text-xl", children: "\u00D7" })] }), _jsx("div", { className: "flex gap-1 mb-3", children: Object.entries(emojiCategories).map(([key, category]) => (_jsx("button", { onClick: () => setActiveCategory(key), className: `px-3 py-1 text-xs rounded-full transition-colors ${activeCategory === key
152
+ ? "bg-blue-100 text-blue-600"
153
+ : "text-gray-500 hover:text-gray-700"}`, children: category.name }, key))) }), _jsx("div", { className: "grid grid-cols-8 gap-1 max-h-48 overflow-y-auto", children: emojiCategories[activeCategory].emojis.map((emoji, index) => (_jsx("button", { onClick: () => onEmojiSelect(emoji), className: "p-2 text-xl hover:bg-gray-100 rounded-lg transition-colors", children: emoji }, index))) })] }) }));
150
154
  };
151
155
  export default EmojiPicker;
@@ -1 +1 @@
1
- {"version":3,"file":"EnterHandler.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EnterHandler.tsx"],"names":[],"mappings":"AAsBA,MAAM,CAAC,OAAO,UAAU,YAAY,SAwFnC"}
1
+ {"version":3,"file":"EnterHandler.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EnterHandler.tsx"],"names":[],"mappings":"AAuBA,MAAM,CAAC,OAAO,UAAU,YAAY,SAmGnC"}
@@ -6,11 +6,12 @@ import { $generateHtmlFromNodes } from "@lexical/html";
6
6
  import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
7
7
  import { $isQuoteNode } from "@lexical/rich-text";
8
8
  import { useMessageFooterContext } from ".";
9
+ import useConversationStore from "../../../store/conversation";
9
10
  export default function EnterHandler() {
10
11
  const [editor] = useLexicalComposerContext();
11
- const { onSendMessage } = useMessageFooterContext();
12
+ const { onSendMessage, listUploadFiles } = useMessageFooterContext();
12
13
  const shiftEnterCount = useRef(0);
13
- const { listUploadFiles } = useMessageFooterContext();
14
+ const quotedMessage = useConversationStore((state) => state.quotedMessage);
14
15
  useEffect(() => {
15
16
  return editor.registerCommand(KEY_ENTER_COMMAND, (event) => {
16
17
  // Case 1: Enter (submit, chặn xuống dòng)
@@ -24,11 +25,15 @@ export default function EnterHandler() {
24
25
  plainText = $getRoot().getTextContent();
25
26
  richText = $generateHtmlFromNodes(editor);
26
27
  });
27
- if (plainText.trim().length > 0 || listUploadFiles.length > 0) {
28
+ const hasText = plainText.trim().length > 0;
29
+ const hasFiles = listUploadFiles.length > 0;
30
+ const hasQuote = !!(quotedMessage === null || quotedMessage === void 0 ? void 0 : quotedMessage.clientMsgID);
31
+ const canSend = hasQuote ? hasText : hasText || hasFiles;
32
+ if (canSend) {
28
33
  onSendMessage({
29
34
  plainText,
30
35
  richText,
31
- type: listUploadFiles.length > 0 ? "file" : "text",
36
+ type: hasFiles ? "file" : "text",
32
37
  });
33
38
  }
34
39
  editor.update(() => {
@@ -74,6 +79,12 @@ export default function EnterHandler() {
74
79
  }
75
80
  return false;
76
81
  }, COMMAND_PRIORITY_NORMAL);
77
- }, [editor, onSendMessage, listUploadFiles]);
82
+ }, [
83
+ editor,
84
+ onSendMessage,
85
+ listUploadFiles,
86
+ quotedMessage,
87
+ quotedMessage === null || quotedMessage === void 0 ? void 0 : quotedMessage.clientMsgID,
88
+ ]);
78
89
  return null;
79
90
  }
@@ -1,9 +1,14 @@
1
+ import { UploadFile } from "antd";
1
2
  interface ShortenOptions {
2
3
  maxLength?: number;
3
4
  keepStart?: number;
4
5
  keepEnd?: number;
5
6
  }
6
7
  export declare const shortenFileName: (name: string, options?: ShortenOptions) => string;
8
+ export declare function FilePreviewList({ files, onRemove, }: {
9
+ files: UploadFile[];
10
+ onRemove: (file: UploadFile) => void;
11
+ }): import("react/jsx-runtime").JSX.Element | null;
7
12
  declare const FilePreview: () => import("react/jsx-runtime").JSX.Element;
8
13
  export default FilePreview;
9
14
  //# sourceMappingURL=FilePreview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FilePreview.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/FilePreview.tsx"],"names":[],"mappings":"AAYA,UAAU,cAAc;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,EAAE,UAAS,cAAmB,WAkBzE,CAAC;AAEF,QAAA,MAAM,WAAW,+CAkFhB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"FilePreview.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/FilePreview.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;AAS1C,UAAU,cAAc;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,EAAE,UAAS,cAAmB,WAiBzE,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC,kDA8EA;AAED,QAAA,MAAM,WAAW,+CAQhB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -17,20 +17,14 @@ export const shortenFileName = (name, options = {}) => {
17
17
  const dotIndex = name.lastIndexOf(".");
18
18
  const ext = dotIndex !== -1 ? name.slice(dotIndex) : "";
19
19
  const base = dotIndex !== -1 ? name.slice(0, dotIndex) : name;
20
- // Tính toán độ dài phần đầu & cuối
21
- const available = maxLength - ext.length - 3; // trừ "...”
20
+ const available = maxLength - ext.length - 3;
22
21
  const startLen = keepStart !== null && keepStart !== void 0 ? keepStart : Math.floor(available / 2);
23
22
  const endLen = keepEnd !== null && keepEnd !== void 0 ? keepEnd : Math.floor(available / 2);
24
23
  const start = base.slice(0, startLen);
25
24
  const end = base.slice(-endLen);
26
25
  return `${start}...${end}${ext}`;
27
26
  };
28
- const FilePreview = () => {
29
- var _a;
30
- const { listUploadFiles, setListUploadFiles } = useMessageFooterContext();
31
- const onRemoveFile = (file) => {
32
- setListUploadFiles(listUploadFiles.filter((f) => f.uid !== file.uid));
33
- };
27
+ export function FilePreviewList({ files, onRemove, }) {
34
28
  const renderFilePreview = useCallback((file) => {
35
29
  var _a, _b, _c;
36
30
  const isDocument = documentTypes.includes(((_a = file === null || file === void 0 ? void 0 : file.originFileObj) === null || _a === void 0 ? void 0 : _a.type) || "");
@@ -40,10 +34,19 @@ const FilePreview = () => {
40
34
  src = URL.createObjectURL(file.originFileObj);
41
35
  }
42
36
  if (isDocument) {
43
- return (_jsxs("div", { className: "relative flex flex-row items-center gap-2 align-center bg-gray-100 rounded-md p-1 pr-2", children: [documentIcon, _jsx("span", { className: "text-xs text-gray-500", children: shortenFileName(((_c = file === null || file === void 0 ? void 0 : file.originFileObj) === null || _c === void 0 ? void 0 : _c.name) || "") }), _jsx(Button, { className: "absolute top-[-8px] right-[-8px] w-5 h-5 rounded-full p-0 bg-gray-500 hover:bg-gray-600", type: "primary", onClick: () => onRemoveFile(file), children: _jsx(Icon, { icon: "close-b", size: 12, color: "white" }) })] }, file.uid));
37
+ return (_jsxs("div", { className: "relative flex flex-row items-center gap-2 align-center bg-gray-100 rounded-md p-1 pr-2", children: [documentIcon, _jsx("span", { className: "text-xs text-gray-500", children: shortenFileName(((_c = file === null || file === void 0 ? void 0 : file.originFileObj) === null || _c === void 0 ? void 0 : _c.name) || "") }), _jsx(Button, { className: "absolute top-[-8px] right-[-8px] w-5 h-5 rounded-full p-0 bg-gray-500 hover:bg-gray-600", type: "primary", onClick: () => onRemove(file), children: _jsx(Icon, { icon: "close-b", size: 12, color: "white" }) })] }, file.uid));
44
38
  }
45
- return (_jsxs("div", { className: "relative rounded-md border", children: [isVideo ? (_jsx("video", { src: src, className: "w-[48px] h-[48px] object-cover rounded-lg", autoPlay: false })) : (_jsx("img", { src: src, alt: file.name, className: "w-[48px] h-[48px] object-cover rounded-lg" })), _jsx(Button, { className: "absolute top-[-8px] right-[-8px] w-5 h-5 rounded-full p-0 bg-gray-500 hover:bg-gray-600", type: "primary", onClick: () => onRemoveFile(file), children: _jsx(Icon, { icon: "close-b", size: 12, color: "white" }) }), isVideo && (_jsx(Icon, { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", icon: "play-b", size: 20, color: "white" }))] }, file.uid));
46
- }, [listUploadFiles]);
47
- return (_jsx("div", { className: "overflow-x-auto mb-[-4px]", children: _jsx("div", { className: "border-b py-2 px-4", children: _jsx("div", { className: "flex items-center gap-2", children: (_a = listUploadFiles === null || listUploadFiles === void 0 ? void 0 : listUploadFiles.map) === null || _a === void 0 ? void 0 : _a.call(listUploadFiles, (file) => renderFilePreview(file)) }) }) }));
39
+ return (_jsxs("div", { className: "relative rounded-md border", children: [isVideo ? (_jsx("video", { src: src, className: "w-[48px] h-[48px] object-cover rounded-lg", autoPlay: false })) : (_jsx("img", { src: src, alt: file.name, className: "w-[48px] h-[48px] object-cover rounded-lg" })), _jsx(Button, { className: "absolute top-[-8px] right-[-8px] w-5 h-5 rounded-full p-0 bg-gray-500 hover:bg-gray-600", type: "primary", onClick: () => onRemove(file), children: _jsx(Icon, { icon: "close-b", size: 12, color: "white" }) }), isVideo && (_jsx(Icon, { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", icon: "play-b", size: 20, color: "white" }))] }, file.uid));
40
+ }, [files]);
41
+ if (files.length === 0)
42
+ return null;
43
+ return (_jsx("div", { className: "overflow-x-auto mb-[-4px]", children: _jsx("div", { className: "border-b py-2 px-4", children: _jsx("div", { className: "flex items-center gap-2", children: files.map((file) => renderFilePreview(file)) }) }) }));
44
+ }
45
+ const FilePreview = () => {
46
+ const { listUploadFiles, setListUploadFiles } = useMessageFooterContext();
47
+ const onRemoveFile = (file) => {
48
+ setListUploadFiles(listUploadFiles.filter((f) => f.uid !== file.uid));
49
+ };
50
+ return _jsx(FilePreviewList, { files: listUploadFiles, onRemove: onRemoveFile });
48
51
  };
49
52
  export default FilePreview;
@@ -0,0 +1,10 @@
1
+ import { UploadFile } from "antd";
2
+ interface MediaActionsProps {
3
+ listUploadFiles: UploadFile[];
4
+ onFilesChange: (files: UploadFile[]) => void;
5
+ buttonClassName?: string;
6
+ showMediaUpload?: boolean;
7
+ }
8
+ export default function MediaActions({ listUploadFiles, onFilesChange, buttonClassName, showMediaUpload, }: MediaActionsProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=MediaActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MediaActions.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/MediaActions.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAkB,UAAU,EAA0B,MAAM,MAAM,CAAC;AAsB1E,UAAU,iBAAiB;IACzB,eAAe,EAAE,UAAU,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,eAAe,EACf,aAAa,EACb,eAAe,EACf,eAAsB,GACvB,EAAE,iBAAiB,2CAsInB"}