@droppii-org/chat-sdk 0.8.1 → 0.8.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 (191) hide show
  1. package/dist/components/canned-response/cannedTrigger.d.ts +2 -0
  2. package/dist/components/canned-response/cannedTrigger.d.ts.map +1 -0
  3. package/dist/components/canned-response/cannedTrigger.js +3 -0
  4. package/dist/components/canned-response/insertCannedContent.js +2 -2
  5. package/dist/components/conversation/ConversationByGroupItem.js +1 -1
  6. package/dist/components/conversation/ConversationBySessionItem.js +4 -4
  7. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  8. package/dist/components/conversation/DeskConversationList.js +6 -4
  9. package/dist/components/conversation/folderEmptyState.d.ts.map +1 -1
  10. package/dist/components/conversation/folderEmptyState.js +2 -0
  11. package/dist/components/conversation/internalNotePreview.d.ts +1 -1
  12. package/dist/components/conversation/internalNotePreview.d.ts.map +1 -1
  13. package/dist/components/conversation/internalNotePreview.js +22 -3
  14. package/dist/components/conversation/listHeading.d.ts +1 -1
  15. package/dist/components/conversation/listHeading.d.ts.map +1 -1
  16. package/dist/components/conversation/listHeading.js +7 -1
  17. package/dist/components/conversation/rowTime.d.ts +2 -2
  18. package/dist/components/conversation/rowTime.d.ts.map +1 -1
  19. package/dist/components/conversation/rowTime.js +2 -4
  20. package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
  21. package/dist/components/conversation-detail/ConversationDetailInput.js +8 -3
  22. package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
  23. package/dist/components/conversation-detail/ConversationMessageList.js +6 -3
  24. package/dist/components/conversation-detail/bot/BotMenuSheet.d.ts.map +1 -1
  25. package/dist/components/conversation-detail/bot/BotMenuSheet.js +1 -1
  26. package/dist/components/conversation-inbox/ConversationInboxList.d.ts.map +1 -1
  27. package/dist/components/conversation-inbox/ConversationInboxList.js +4 -0
  28. package/dist/components/conversation-inbox/InboxBootStatus.d.ts.map +1 -1
  29. package/dist/components/conversation-inbox/InboxBootStatus.js +10 -2
  30. package/dist/components/group/CreateGroupForm.d.ts.map +1 -1
  31. package/dist/components/group/CreateGroupForm.js +2 -1
  32. package/dist/components/group/CreateGroupFormCrmView.d.ts.map +1 -1
  33. package/dist/components/group/CreateGroupFormCrmView.js +1 -1
  34. package/dist/components/group/GroupMemberPickerCrm.d.ts.map +1 -1
  35. package/dist/components/group/GroupMemberPickerCrm.js +1 -1
  36. package/dist/components/media-collection/FileCollection.d.ts.map +1 -1
  37. package/dist/components/media-collection/FileCollection.js +4 -3
  38. package/dist/components/media-collection/ImageCollection.d.ts.map +1 -1
  39. package/dist/components/media-collection/ImageCollection.js +2 -1
  40. package/dist/components/media-collection/LinkCollection.d.ts.map +1 -1
  41. package/dist/components/media-collection/LinkCollection.js +14 -26
  42. package/dist/components/media-collection/VideoCollection.d.ts.map +1 -1
  43. package/dist/components/media-collection/VideoCollection.js +2 -1
  44. package/dist/components/media-resources/FileResourceList.js +2 -2
  45. package/dist/components/media-resources/ResourceScrollContainer.d.ts.map +1 -1
  46. package/dist/components/media-resources/ResourceScrollContainer.js +34 -2
  47. package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
  48. package/dist/components/message/GroupMembersDrawer.js +2 -3
  49. package/dist/components/message/MessageList.d.ts.map +1 -1
  50. package/dist/components/message/MessageList.js +15 -4
  51. package/dist/components/message/footer/ActionBar.d.ts.map +1 -1
  52. package/dist/components/message/footer/ActionBar.js +10 -44
  53. package/dist/components/message/footer/CannedResponsePlugin.d.ts.map +1 -1
  54. package/dist/components/message/footer/CannedResponsePlugin.js +3 -2
  55. package/dist/components/message/footer/EnterHandler.d.ts.map +1 -1
  56. package/dist/components/message/footer/EnterHandler.js +10 -47
  57. package/dist/components/message/footer/FilePreview.d.ts.map +1 -1
  58. package/dist/components/message/footer/FilePreview.js +2 -1
  59. package/dist/components/message/footer/LinkInputPreview.d.ts +1 -1
  60. package/dist/components/message/footer/LinkInputPreview.d.ts.map +1 -1
  61. package/dist/components/message/footer/LinkInputPreview.js +8 -1
  62. package/dist/components/message/footer/MediaUploadButtons.d.ts.map +1 -1
  63. package/dist/components/message/footer/MediaUploadButtons.js +4 -7
  64. package/dist/components/message/footer/index.d.ts.map +1 -1
  65. package/dist/components/message/footer/index.js +57 -16
  66. package/dist/components/message/footer/mention/useMentionOptions.d.ts.map +1 -1
  67. package/dist/components/message/footer/mention/useMentionOptions.js +2 -6
  68. package/dist/components/message/footer/resetEditorAfterSend.d.ts +7 -0
  69. package/dist/components/message/footer/resetEditorAfterSend.d.ts.map +1 -0
  70. package/dist/components/message/footer/resetEditorAfterSend.js +62 -0
  71. package/dist/components/message/groupMemberMenuCrm.d.ts +4 -4
  72. package/dist/components/message/groupMemberMenuCrm.d.ts.map +1 -1
  73. package/dist/components/message/groupMemberMenuCrm.js +73 -60
  74. package/dist/components/message/item/FileMessage.js +2 -2
  75. package/dist/components/message/item/SystemLogMessage.d.ts.map +1 -1
  76. package/dist/components/message/item/SystemLogMessage.js +5 -2
  77. package/dist/components/search-conversation/SearchConversationAsGroups.js +1 -1
  78. package/dist/components/search-conversation/SearchConversationAsMessages.js +1 -1
  79. package/dist/components/search-conversation/SearchConversationMyInbox.js +1 -1
  80. package/dist/components/session/SessionFolderList.d.ts +4 -3
  81. package/dist/components/session/SessionFolderList.d.ts.map +1 -1
  82. package/dist/components/session/SessionFolderList.js +14 -5
  83. package/dist/components/thread/GroupInfoBodyCrm.d.ts.map +1 -1
  84. package/dist/components/thread/GroupInfoBodyCrm.js +2 -2
  85. package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
  86. package/dist/components/thread/GroupThreadInfo.js +2 -6
  87. package/dist/components/thread/ManualAssignPopover.d.ts.map +1 -1
  88. package/dist/components/thread/ManualAssignPopover.js +6 -8
  89. package/dist/components/thread/SessionDetailCard.d.ts.map +1 -1
  90. package/dist/components/thread/SessionDetailCard.js +3 -3
  91. package/dist/components/thread/SessionSection.d.ts.map +1 -1
  92. package/dist/components/thread/SessionSection.js +3 -9
  93. package/dist/components/thread/SessionTagPicker.d.ts +3 -1
  94. package/dist/components/thread/SessionTagPicker.d.ts.map +1 -1
  95. package/dist/components/thread/SessionTagPicker.js +2 -2
  96. package/dist/components/thread/sessionDateFormat.d.ts +2 -0
  97. package/dist/components/thread/sessionDateFormat.d.ts.map +1 -1
  98. package/dist/components/thread/sessionDateFormat.js +6 -0
  99. package/dist/constants/dateFormat.d.ts +2 -0
  100. package/dist/constants/dateFormat.d.ts.map +1 -0
  101. package/dist/constants/dateFormat.js +2 -0
  102. package/dist/hooks/common/useFetchUrlMetadata.d.ts +1 -0
  103. package/dist/hooks/common/useFetchUrlMetadata.d.ts.map +1 -1
  104. package/dist/hooks/conversation/useBotFlow.d.ts.map +1 -1
  105. package/dist/hooks/conversation/useBotFlow.js +64 -12
  106. package/dist/hooks/conversation/useCloseBotSession.d.ts +2 -0
  107. package/dist/hooks/conversation/useCloseBotSession.d.ts.map +1 -1
  108. package/dist/hooks/conversation/useCloseBotSession.js +17 -1
  109. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  110. package/dist/hooks/global/useGlobalEvent.js +148 -16
  111. package/dist/hooks/global/useLoginHealth.d.ts +3 -0
  112. package/dist/hooks/global/useLoginHealth.d.ts.map +1 -0
  113. package/dist/hooks/global/useLoginHealth.js +55 -0
  114. package/dist/hooks/group/useCreateGroup.d.ts.map +1 -1
  115. package/dist/hooks/group/useCreateGroup.js +75 -6
  116. package/dist/hooks/init/useChatBootPhase.d.ts +2 -0
  117. package/dist/hooks/init/useChatBootPhase.d.ts.map +1 -1
  118. package/dist/hooks/init/useChatBootPhase.js +11 -6
  119. package/dist/hooks/message/useMessage.d.ts.map +1 -1
  120. package/dist/hooks/message/useMessage.js +97 -28
  121. package/dist/hooks/message/usePinnedMessages.d.ts.map +1 -1
  122. package/dist/hooks/message/usePinnedMessages.js +13 -2
  123. package/dist/hooks/message/useRevokeMessage.d.ts.map +1 -1
  124. package/dist/hooks/message/useRevokeMessage.js +25 -0
  125. package/dist/hooks/message/useSendMessage.d.ts +15 -12
  126. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  127. package/dist/hooks/message/useSendMessage.js +199 -133
  128. package/dist/hooks/search/useSearchMessage.d.ts.map +1 -1
  129. package/dist/hooks/search/useSearchMessage.js +57 -3
  130. package/dist/hooks/session/useConversationSessionState.d.ts.map +1 -1
  131. package/dist/hooks/session/useConversationSessionState.js +4 -1
  132. package/dist/hooks/session/useGetSession.d.ts.map +1 -1
  133. package/dist/hooks/session/useGetSession.js +2 -0
  134. package/dist/hooks/session/useGetSessionList.d.ts +235 -0
  135. package/dist/hooks/session/useGetSessionList.d.ts.map +1 -0
  136. package/dist/hooks/session/useGetSessionList.js +23 -0
  137. package/dist/hooks/session/useSessionLatestPreview.d.ts.map +1 -1
  138. package/dist/hooks/session/useSessionLatestPreview.js +34 -4
  139. package/dist/hooks/user/useAuth.d.ts +1 -1
  140. package/dist/hooks/user/useAuth.d.ts.map +1 -1
  141. package/dist/hooks/user/useAuth.js +31 -15
  142. package/dist/index.d.ts +1 -0
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +1 -0
  145. package/dist/locales/vi/boot.json +5 -1
  146. package/dist/locales/vi/common.json +35 -15
  147. package/dist/locales/vi/conversation-inbox.json +2 -1
  148. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  149. package/dist/screens/conversation-detail/index.js +6 -0
  150. package/dist/screens/desk-message/DeskBootCard.d.ts.map +1 -1
  151. package/dist/screens/desk-message/DeskBootCard.js +6 -4
  152. package/dist/store/boot.d.ts +5 -0
  153. package/dist/store/boot.d.ts.map +1 -1
  154. package/dist/store/boot.js +11 -1
  155. package/dist/store/conversation.d.ts.map +1 -1
  156. package/dist/store/conversation.js +5 -1
  157. package/dist/store/session.d.ts +1 -13
  158. package/dist/store/session.d.ts.map +1 -1
  159. package/dist/store/session.js +10 -9
  160. package/dist/store/sessionFolder.d.ts +2 -0
  161. package/dist/store/sessionFolder.d.ts.map +1 -1
  162. package/dist/store/sessionFolder.js +3 -1
  163. package/dist/store/type.d.ts +7 -1
  164. package/dist/store/type.d.ts.map +1 -1
  165. package/dist/styles/global.css +1 -1
  166. package/dist/types/chat.d.ts +1 -1
  167. package/dist/types/chat.d.ts.map +1 -1
  168. package/dist/utils/common.d.ts.map +1 -1
  169. package/dist/utils/common.js +4 -1
  170. package/dist/utils/download.d.ts +10 -0
  171. package/dist/utils/download.d.ts.map +1 -1
  172. package/dist/utils/download.js +51 -0
  173. package/dist/utils/fileValidation.d.ts +7 -0
  174. package/dist/utils/fileValidation.d.ts.map +1 -1
  175. package/dist/utils/fileValidation.js +37 -13
  176. package/dist/utils/latestMessagePreviewable.d.ts +2 -0
  177. package/dist/utils/latestMessagePreviewable.d.ts.map +1 -0
  178. package/dist/utils/latestMessagePreviewable.js +18 -0
  179. package/dist/utils/mentionMembers.d.ts +3 -0
  180. package/dist/utils/mentionMembers.d.ts.map +1 -0
  181. package/dist/utils/mentionMembers.js +29 -0
  182. package/dist/utils/messageLog.d.ts +2 -2
  183. package/dist/utils/messageLog.d.ts.map +1 -1
  184. package/dist/utils/messageLog.js +129 -40
  185. package/dist/utils/notifiableMessage.d.ts +7 -0
  186. package/dist/utils/notifiableMessage.d.ts.map +1 -0
  187. package/dist/utils/notifiableMessage.js +26 -0
  188. package/dist/utils/withSdkTimeout.d.ts +11 -0
  189. package/dist/utils/withSdkTimeout.d.ts.map +1 -0
  190. package/dist/utils/withSdkTimeout.js +30 -0
  191. package/package.json +1 -1
@@ -3,36 +3,24 @@ import { Dropdown, Empty, Spin } from "antd";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import useConversationStore from "../../store/conversation";
5
5
  import { useSearchMessage } from "../../hooks/search/useSearchMessage";
6
- import { useCallback, useEffect, useState } from "react";
6
+ import { useCallback } from "react";
7
7
  import dayjs from "dayjs";
8
+ import { LIBRARY_DATE_HEADER_FORMAT } from "../../constants/dateFormat";
8
9
  import InfiniteScroll from "react-infinite-scroll-component";
9
10
  import { MessageType, SessionType } from "@droppii-org/wasm-client-sdk";
10
11
  import { Icon } from "../../components/icon";
11
12
  import { DChatSDK } from "../../constants/sdk";
12
13
  import { useChatContext } from "../../context/ChatContext";
13
14
  import { message as antdMessage } from "antd";
14
- const LinkPreview = ({ url }) => {
15
- const [title, setTitle] = useState("");
16
- const [isLoading, setIsLoading] = useState(true);
17
- useEffect(() => {
18
- if (!url)
19
- return;
20
- setIsLoading(true);
21
- fetch(`https://api.microlink.io?url=${encodeURIComponent(url)}`)
22
- .then((res) => res.json())
23
- .then((res) => {
24
- var _a;
25
- if ((_a = res === null || res === void 0 ? void 0 : res.data) === null || _a === void 0 ? void 0 : _a.title) {
26
- setTitle(res.data.title);
27
- }
28
- })
29
- .catch(() => {
30
- setTitle("");
31
- })
32
- .finally(() => {
33
- setIsLoading(false);
34
- });
35
- }, [url]);
15
+ import { useFetchUrlMetadata } from "../../hooks/common/useFetchUrlMetadata";
16
+ import { parseUrlMetadata } from "../../utils/common";
17
+ const LinkPreview = ({ url, messageMetadata, }) => {
18
+ // Title saved on the message wins; otherwise ask the in-house metadata
19
+ // endpoint instead of a third-party service.
20
+ const hasMessageTitle = Boolean(messageMetadata === null || messageMetadata === void 0 ? void 0 : messageMetadata.title);
21
+ const { data, isLoading: isFetching } = useFetchUrlMetadata(url, !hasMessageTitle);
22
+ const title = (messageMetadata === null || messageMetadata === void 0 ? void 0 : messageMetadata.title) || ((data === null || data === void 0 ? void 0 : data.isEmpty) ? "" : data === null || data === void 0 ? void 0 : data.title) || "";
23
+ const isLoading = !hasMessageTitle && isFetching;
36
24
  return (_jsxs("a", { href: url, target: "_blank", rel: "noopener noreferrer", "data-cp-name": "LinkPreview", className: "flex flex-col truncate text-[var(--dc-color-link)] cursor-pointer", title: title || url, children: [isLoading && (_jsx("span", { className: "font-medium truncate text-gray-900", children: url })), !isLoading &&
37
25
  (title ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-medium truncate text-gray-900", children: title }), _jsx("span", { className: "text-sm text-gray-500 truncate", children: url })] })) : (_jsx("span", { className: "font-medium truncate text-gray-900", children: url })))] }));
38
26
  };
@@ -67,7 +55,7 @@ const LinkCollection = ({ onClose }) => {
67
55
  },
68
56
  });
69
57
  const renderItem = useCallback((date, items) => {
70
- return (_jsxs("div", { className: "px-3", children: [_jsx("span", { className: "text-sm font-medium text-gray-500 px-3", children: dayjs(date).format("DD MMMM, YYYY") }), _jsx("div", { className: "flex flex-col gap-1 mt-2", children: items.map((item) => {
58
+ return (_jsxs("div", { className: "px-3", children: [_jsx("span", { className: "text-sm font-medium text-gray-500 px-3", children: dayjs(date).format(LIBRARY_DATE_HEADER_FORMAT) }), _jsx("div", { className: "flex flex-col gap-1 mt-2", children: items.map((item) => {
71
59
  var _a, _b, _c;
72
60
  const urls = ((_b = JSON.parse(((_a = item === null || item === void 0 ? void 0 : item.chatLog) === null || _a === void 0 ? void 0 : _a.content) || "{}")) === null || _b === void 0 ? void 0 : _b.urls) || [];
73
61
  const menuItems = [
@@ -78,8 +66,8 @@ const LinkCollection = ({ onClose }) => {
78
66
  },
79
67
  ];
80
68
  return (_jsx("div", { children: urls.map((url, index) => {
81
- var _a;
82
- return (_jsxs("div", { className: "bg-gray-100 flex gap-2 items-center p-2 mb-1", children: [_jsx("div", { className: "flex items-center justify-center shrink-0", children: _jsx(Icon, { icon: "link-b", size: 16 }) }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx(LinkPreview, { url: url }) }), _jsx(Dropdown, { menu: { items: menuItems }, trigger: ["click"], placement: "bottomRight", children: _jsx("div", { className: "flex items-center justify-center shrink-0 cursor-pointer p-1 hover:bg-gray-200 rounded ml-2", onClick: (e) => e.stopPropagation(), children: _jsx(Icon, { icon: "more-horizontal-b", size: 16 }) }) })] }, `${(_a = item.chatLog) === null || _a === void 0 ? void 0 : _a.clientMsgID}-${index}`));
69
+ var _a, _b;
70
+ return (_jsxs("div", { className: "bg-gray-100 flex gap-2 items-center p-2 mb-1", children: [_jsx("div", { className: "flex items-center justify-center shrink-0", children: _jsx(Icon, { icon: "link-b", size: 16 }) }), _jsx("div", { className: "flex-1 min-w-0", children: _jsx(LinkPreview, { url: url, messageMetadata: index === 0 ? parseUrlMetadata((_a = item.chatLog) === null || _a === void 0 ? void 0 : _a.ex) : undefined }) }), _jsx(Dropdown, { menu: { items: menuItems }, trigger: ["click"], placement: "bottomRight", children: _jsx("div", { className: "flex items-center justify-center shrink-0 cursor-pointer p-1 hover:bg-gray-200 rounded ml-2", onClick: (e) => e.stopPropagation(), children: _jsx(Icon, { icon: "more-horizontal-b", size: 16 }) }) })] }, `${(_b = item.chatLog) === null || _b === void 0 ? void 0 : _b.clientMsgID}-${index}`));
83
71
  }) }, (_c = item.chatLog) === null || _c === void 0 ? void 0 : _c.clientMsgID));
84
72
  }) })] }, date));
85
73
  }, []);
@@ -1 +1 @@
1
- {"version":3,"file":"VideoCollection.d.ts","sourceRoot":"","sources":["../../../src/components/media-collection/VideoCollection.tsx"],"names":[],"mappings":"AAiBA,QAAA,MAAM,eAAe,+CAqGpB,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"VideoCollection.d.ts","sourceRoot":"","sources":["../../../src/components/media-collection/VideoCollection.tsx"],"names":[],"mappings":"AAkBA,QAAA,MAAM,eAAe,+CAqGpB,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -5,6 +5,7 @@ import InfiniteScroll from "react-infinite-scroll-component";
5
5
  import { Empty, Image, Spin } from "antd";
6
6
  import { useState } from "react";
7
7
  import dayjs from "dayjs";
8
+ import { LIBRARY_DATE_HEADER_FORMAT } from "../../constants/dateFormat";
8
9
  import useConversationStore from "../../store/conversation";
9
10
  import { useBoolean } from "ahooks";
10
11
  import { images } from "../../constants/images";
@@ -29,7 +30,7 @@ const VideoCollection = () => {
29
30
  setTrue();
30
31
  };
31
32
  const renderItem = (date, items) => {
32
- return (_jsxs("div", { className: "mb-2", children: [_jsx("span", { className: "text-sm font-medium text-gray-500 px-3", children: dayjs(date).format("DD MMMM, YYYY") }), _jsx("div", { className: "grid grid-cols-3 justify-start gap-px py-2", children: items.map((item) => {
33
+ return (_jsxs("div", { className: "mb-2", children: [_jsx("span", { className: "text-sm font-medium text-gray-500 px-3", children: dayjs(date).format(LIBRARY_DATE_HEADER_FORMAT) }), _jsx("div", { className: "grid grid-cols-3 justify-start gap-px py-2", children: items.map((item) => {
33
34
  var _a, _b, _c, _d;
34
35
  const videoContent = parseMessageContent((_a = item === null || item === void 0 ? void 0 : item.chatLog) === null || _a === void 0 ? void 0 : _a.content);
35
36
  const globalIndex = (_c = videoIndexByMsgId.get((_b = item.chatLog) === null || _b === void 0 ? void 0 : _b.clientMsgID)) !== null && _c !== void 0 ? _c : 0;
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { documentIcon } from "../../assets/svg";
4
4
  import { parseMessageContent, renderFileSize, shortenFileName, } from "../../utils/common";
5
- import { downloadFileFromUrl } from "../../utils/download";
5
+ import { openFileOrDownload } from "../../utils/download";
6
6
  import { MediaResourceTabKey } from "./constants";
7
7
  import ResourceScrollContainer from "./ResourceScrollContainer";
8
8
  import { useResourceTab } from "./useResourceTab";
@@ -11,7 +11,7 @@ const FileResourceList = () => {
11
11
  return (_jsx(ResourceScrollContainer, Object.assign({ "data-cp-name": "FileResourceList" }, containerProps, { itemCount: items.length, children: _jsx("div", { className: "flex flex-col gap-2 p-3", children: items.map((item) => {
12
12
  var _a, _b;
13
13
  const fileContent = parseMessageContent((_a = item === null || item === void 0 ? void 0 : item.chatLog) === null || _a === void 0 ? void 0 : _a.content);
14
- return (_jsxs("div", { className: "flex w-full cursor-pointer items-center gap-2 rounded-[12px] bg-[#f5f5f5]/20 p-3", onClick: () => downloadFileFromUrl((fileContent === null || fileContent === void 0 ? void 0 : fileContent.sourceUrl) || "", (fileContent === null || fileContent === void 0 ? void 0 : fileContent.fileName) || ""), children: [_jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center [&>svg]:h-full [&>svg]:w-auto", children: documentIcon }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-[16px] font-medium leading-[1.6] text-gray-900", children: shortenFileName(fileContent === null || fileContent === void 0 ? void 0 : fileContent.fileName) }), _jsx("span", { className: "text-[13px] font-normal leading-[1.6] text-gray-400", children: renderFileSize((fileContent === null || fileContent === void 0 ? void 0 : fileContent.fileSize) || 0) })] })] }, (_b = item.chatLog) === null || _b === void 0 ? void 0 : _b.clientMsgID));
14
+ return (_jsxs("div", { className: "flex w-full cursor-pointer items-center gap-2 rounded-[12px] bg-[#f5f5f5]/20 p-3", onClick: () => void openFileOrDownload((fileContent === null || fileContent === void 0 ? void 0 : fileContent.sourceUrl) || "", (fileContent === null || fileContent === void 0 ? void 0 : fileContent.fileName) || ""), children: [_jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center [&>svg]:h-full [&>svg]:w-auto", children: documentIcon }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-[16px] font-medium leading-[1.6] text-gray-900", children: shortenFileName(fileContent === null || fileContent === void 0 ? void 0 : fileContent.fileName) }), _jsx("span", { className: "text-[13px] font-normal leading-[1.6] text-gray-400", children: renderFileSize((fileContent === null || fileContent === void 0 ? void 0 : fileContent.fileSize) || 0) })] })] }, (_b = item.chatLog) === null || _b === void 0 ? void 0 : _b.clientMsgID));
15
15
  }) }) })));
16
16
  };
17
17
  export default FileResourceList;
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceScrollContainer.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/ResourceScrollContainer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAa,MAAM,OAAO,CAAC;AAI7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAQD,QAAA,MAAM,uBAAuB,GAAI,+GAU9B,4BAA4B,4CAoD9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ResourceScrollContainer.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/ResourceScrollContainer.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAA+B,MAAM,OAAO,CAAC;AAI/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAQD,QAAA,MAAM,uBAAuB,GAAI,+GAU9B,4BAA4B,4CAmF9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect } from "react";
3
+ import { useEffect, useRef, useState } from "react";
4
4
  import InfiniteScroll from "react-infinite-scroll-component";
5
5
  import { Button, Spin } from "antd";
6
6
  import { useTranslation } from "react-i18next";
@@ -14,6 +14,38 @@ const ResourceScrollContainer = ({ tabKey, itemCount, isLoading, isError, hasNex
14
14
  fetchNextPage();
15
15
  }
16
16
  }, [isEmptyWithMorePages, isFetchingNextPage, fetchNextPage]);
17
+ // A page that fits without scrolling never fires the scroll trigger, so
18
+ // keep loading until the content overflows or the pages run out.
19
+ // The panel is mounted inside a closed drawer (display:none, 0 height), so a
20
+ // hidden container must not trigger background fetching; the resize tick
21
+ // re-checks once the drawer becomes visible.
22
+ const scrollRef = useRef(null);
23
+ const [resizeTick, setResizeTick] = useState(0);
24
+ const hasContainer = !isLoading && !isError && itemCount > 0;
25
+ useEffect(() => {
26
+ const el = scrollRef.current;
27
+ if (!el || typeof ResizeObserver === "undefined")
28
+ return;
29
+ const observer = new ResizeObserver(() => setResizeTick((n) => n + 1));
30
+ observer.observe(el);
31
+ return () => observer.disconnect();
32
+ }, [hasContainer]);
33
+ useEffect(() => {
34
+ const el = scrollRef.current;
35
+ if (!el || !hasNextPage || isFetchingNextPage || isError)
36
+ return;
37
+ if (!el.isConnected || el.clientHeight === 0)
38
+ return;
39
+ if (el.scrollHeight <= el.clientHeight)
40
+ fetchNextPage();
41
+ }, [
42
+ itemCount,
43
+ hasNextPage,
44
+ isFetchingNextPage,
45
+ isError,
46
+ fetchNextPage,
47
+ resizeTick,
48
+ ]);
17
49
  if (isLoading || isEmptyWithMorePages) {
18
50
  return (_jsx("div", { className: "flex h-full items-center justify-center py-6", children: _jsx(Spin, {}) }));
19
51
  }
@@ -24,6 +56,6 @@ const ResourceScrollContainer = ({ tabKey, itemCount, isLoading, isError, hasNex
24
56
  return _jsx(ResourceEmptyState, { tabKey: tabKey });
25
57
  }
26
58
  const scrollableId = `resource-scroll-${tabKey}`;
27
- return (_jsx("div", { id: scrollableId, "data-cp-name": "ResourceScrollContainer", className: "h-full overflow-auto", children: _jsx(InfiniteScroll, { dataLength: itemCount, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx(LoadingIndicator, {}), scrollableTarget: scrollableId, children: children }) }));
59
+ return (_jsx("div", { ref: scrollRef, id: scrollableId, "data-cp-name": "ResourceScrollContainer", className: "h-full overflow-auto", children: _jsx(InfiniteScroll, { dataLength: itemCount, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx(LoadingIndicator, {}), scrollableTarget: scrollableId, children: children }) }));
28
60
  };
29
61
  export default ResourceScrollContainer;
@@ -1 +1 @@
1
- {"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8B/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA6BD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,4CAusBzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8B/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA6BD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,4CAwsBzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -250,7 +250,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
250
250
  });
251
251
  message.success(member.isAdmin
252
252
  ? t("remove_from_admin_success")
253
- : t("assign_as_admin_success"));
253
+ : t("assign_as_admin_success", { name: member.nickname }));
254
254
  // Optimistically patch the role in place instead of refetching the
255
255
  // whole list — a full refetch replaces every member's object
256
256
  // identity and re-sorts, which visibly re-renders/reorders rows that
@@ -283,7 +283,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
283
283
  groupID,
284
284
  newOwnerUserID: confirmMemberAction.member.userID,
285
285
  });
286
- message.success(t("transfer_owner_success"));
286
+ message.success(t("transfer_owner_success", { name: confirmMemberAction.member.nickname }));
287
287
  void refetch();
288
288
  }
289
289
  catch (error) {
@@ -314,7 +314,6 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
314
314
  }, [groupID]);
315
315
  const renderCrmPanel = (closeOnEscape) => (_jsx(GroupMembersPanelCrm, { totalCount: totalCount, members: sortedMembers.map((member) => isRealGroup ? member : Object.assign(Object.assign({}, member), { roleLabel: "" })), isRealGroup: isRealGroup, isLoading: isLoading || (isSearchActive && isSearching), isLoadingMore: isLoadingMore && !isSearchActive, onListScroll: handleListScroll, error: error, isSearchActive: isSearchActive, search: search, onSearchChange: setSearch, canAddMember: canAddMember, onAddMember: () => setShowAddMember(true), onClose: handleCloseDrawer, closeOnEscape: closeOnEscape, buildMenu: (member) => buildCrmMemberMenu({
316
316
  member,
317
- isRealGroup,
318
317
  permissions,
319
318
  actingIsOwner,
320
319
  leaveBlockedReason,
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAIxD,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;IACrB,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,uEAAuE;IACvE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAyb3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAIxD,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;IACrB,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAChE,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,uEAAuE;IACvE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAoc3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -86,6 +86,7 @@ const MessageList = (props) => {
86
86
  const { revokeMessage, loading: isRevoking } = useRevokeMessage();
87
87
  const { pinMessage, unpinMessage } = usePinMessage();
88
88
  const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
89
+ const pinnedBackendTotalCount = useConversationStore((state) => state.pinnedBackendTotalCount);
89
90
  const pinnedRemainingCount = useConversationStore((state) => state.pinnedRemainingCount);
90
91
  const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
91
92
  const { rawGroupId, latestConversationSession, refetchConversationSessions } = useConversationSessionState({
@@ -136,13 +137,23 @@ const MessageList = (props) => {
136
137
  closeConfirmRevoke();
137
138
  }, [closeConfirmRevoke]);
138
139
  const onRevokeMessage = useCallback(async () => {
139
- await revokeMessage((conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "", selectedItem || "");
140
- handleCloseRevoke();
140
+ try {
141
+ await revokeMessage((conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "", selectedItem || "");
142
+ }
143
+ catch (error) {
144
+ // Keep the cause visible: the toast alone hides why core rejected it.
145
+ console.warn("revokeMessage failed", error);
146
+ toastMessage.error(t("revoke_message_failed"));
147
+ }
148
+ finally {
149
+ handleCloseRevoke();
150
+ }
141
151
  }, [
142
152
  conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID,
143
153
  selectedItem,
144
154
  revokeMessage,
145
155
  handleCloseRevoke,
156
+ t,
146
157
  ]);
147
158
  const onPressQuoteMessage = useCallback((clientMsgID) => {
148
159
  const hasMessage = loadState.messageList.some((msg) => msg.clientMsgID === clientMsgID);
@@ -160,7 +171,7 @@ const MessageList = (props) => {
160
171
  onPressQuoteMessage(firstUnreadMessageId);
161
172
  }, [firstUnreadMessageId, markDividerSeen, onPressQuoteMessage]);
162
173
  const handlePinMessage = useCallback(async (message) => {
163
- if (pinnedRemainingCount <= 0 && pinnedTotalCount > 0) {
174
+ if (pinnedRemainingCount <= 0 && pinnedBackendTotalCount > 0) {
164
175
  setPinLimitModalOpen(true);
165
176
  return;
166
177
  }
@@ -171,7 +182,7 @@ const MessageList = (props) => {
171
182
  catch (_a) {
172
183
  toastMessage.error(t("pin_message_failed"));
173
184
  }
174
- }, [conversationId, pinMessage, pinnedRemainingCount, pinnedTotalCount, t]);
185
+ }, [conversationId, pinMessage, pinnedRemainingCount, pinnedBackendTotalCount, t]);
175
186
  const handleUnpinMessage = useCallback(async (message) => {
176
187
  try {
177
188
  await unpinMessage(conversationId, message.clientMsgID);
@@ -1 +1 @@
1
- {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ActionBar.tsx"],"names":[],"mappings":"AA4BA,UAAU,cAAc;IAEtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,QAAA,MAAM,SAAS,GAAI,gCAAwC,cAAc,4CAmKxE,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"ActionBar.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/ActionBar.tsx"],"names":[],"mappings":"AAiBA,UAAU,cAAc;IAEtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,QAAA,MAAM,SAAS,GAAI,gCAAwC,cAAc,4CA2HxE,CAAC;AAEF,eAAe,SAAS,CAAC"}
@@ -8,13 +8,11 @@ import { useTranslation } from "react-i18next";
8
8
  import { Icon } from "../../../components/icon";
9
9
  import useAuthStore from "../../../store/auth";
10
10
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
11
- import { $createParagraphNode, $getRoot, $getSelection, $isRangeSelection, } from "lexical";
11
+ import { $getRoot } from "lexical";
12
12
  import { $generateHtmlFromNodes } from "@lexical/html";
13
- import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
14
- import { $isQuoteNode } from "@lexical/rich-text";
15
13
  import { useMessageFooterContext } from ".";
16
14
  import MediaActions from "./MediaActions";
17
- import { clearComposerHistoryAfterUpdate } from "./clearComposerHistory";
15
+ import { resetEditorAfterSend, resetEditorIfUnchanged } from "./resetEditorAfterSend";
18
16
  import useConversationStore from "../../../store/conversation";
19
17
  import { extractMentions } from "./mention/extractMentions";
20
18
  const ActionBar = ({ formatOpen = false, onToggleFormat }) => {
@@ -39,53 +37,21 @@ const ActionBar = ({ formatOpen = false, onToggleFormat }) => {
39
37
  });
40
38
  if (canSend) {
41
39
  const mentions = extractMentions(editor);
42
- const sent = onSendMessage({
40
+ const sending = onSendMessage({
43
41
  plainText,
44
42
  richText,
45
43
  type: listUploadFiles.length > 0 ? "file" : "text",
46
44
  isInternal,
47
45
  mentions,
48
46
  });
49
- if (!sent) {
50
- return;
51
- }
47
+ // Clear only once the message is built; a failed build keeps the draft.
48
+ void Promise.resolve(sending).then((sent) => {
49
+ if (sent)
50
+ resetEditorIfUnchanged(editor, { plainText, richText });
51
+ });
52
+ return;
52
53
  }
53
- clearComposerHistoryAfterUpdate(editor);
54
- editor.update(() => {
55
- const root = $getRoot();
56
- root.clear();
57
- const paragraph = $createParagraphNode();
58
- root.append(paragraph);
59
- paragraph.select();
60
- const selection = $getSelection();
61
- if ($isRangeSelection(selection)) {
62
- const anchorNode = selection.anchor.getNode();
63
- const topLevelNode = anchorNode.getTopLevelElementOrThrow();
64
- const isListNode = $isListNode(topLevelNode);
65
- const isQuoteNode = $isQuoteNode(topLevelNode);
66
- const listType = isListNode ? topLevelNode.getListType() : undefined;
67
- if (selection.hasFormat("bold")) {
68
- selection.formatText("bold");
69
- }
70
- if (selection.hasFormat("italic")) {
71
- selection.formatText("italic");
72
- }
73
- if (selection.hasFormat("strikethrough")) {
74
- selection.formatText("strikethrough");
75
- }
76
- if (isQuoteNode) {
77
- const paragraph = $createParagraphNode();
78
- const children = topLevelNode.getChildren();
79
- children.forEach((child) => paragraph.append(child));
80
- topLevelNode.replace(paragraph);
81
- }
82
- if (isListNode && listType) {
83
- editor.dispatchCommand(listType === "number"
84
- ? INSERT_ORDERED_LIST_COMMAND
85
- : INSERT_UNORDERED_LIST_COMMAND, undefined);
86
- }
87
- }
88
- });
54
+ resetEditorAfterSend(editor);
89
55
  }, [editor, onSendMessage, canSend, isInternal, listUploadFiles.length]);
90
56
  useEffect(() => {
91
57
  return editor.registerUpdateListener(({ editorState }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"CannedResponsePlugin.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/CannedResponsePlugin.tsx"],"names":[],"mappings":"AAYA,KAAK,KAAK,GAAG;IACX,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,QAwGrE"}
1
+ {"version":3,"file":"CannedResponsePlugin.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/CannedResponsePlugin.tsx"],"names":[],"mappings":"AAaA,KAAK,KAAK,GAAG;IACX,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,KAAK,QAwGrE"}
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
2
2
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
3
3
  import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ESCAPE_COMMAND, } from "lexical";
4
4
  import { useMessageFooterContext } from ".";
5
+ import { CANNED_TRIGGER_BEFORE_CURSOR } from "../../../components/canned-response/cannedTrigger";
5
6
  export function CannedResponseTriggerPlugin({ onOpen, onClose }) {
6
7
  const [editor] = useLexicalComposerContext();
7
8
  const { activeCannedIndex, setActiveCannedIndex, isOpenCanned, maxActiveCannedIndex, } = useMessageFooterContext();
@@ -21,8 +22,8 @@ export function CannedResponseTriggerPlugin({ onOpen, onClose }) {
21
22
  const text = node.getTextContent();
22
23
  const offset = anchor.offset;
23
24
  const before = text.slice(0, offset);
24
- // match #abc (KHÔNG có space phía sau)
25
- const match = before.match(/(^|\s)#(\w*)$/);
25
+ // match #abc or #abc-def (no space after the cursor)
26
+ const match = before.match(CANNED_TRIGGER_BEFORE_CURSOR);
26
27
  lastBeforeRef.current = before;
27
28
  if (match && dismissedRef.current !== before) {
28
29
  onOpen(match[2] || "");
@@ -1 +1 @@
1
- {"version":3,"file":"EnterHandler.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EnterHandler.tsx"],"names":[],"mappings":"AA2BA,MAAM,CAAC,OAAO,UAAU,YAAY,SA+JnC"}
1
+ {"version":3,"file":"EnterHandler.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/EnterHandler.tsx"],"names":[],"mappings":"AAoBA,MAAM,CAAC,OAAO,UAAU,YAAY,SAkHnC"}
@@ -1,11 +1,10 @@
1
1
  "use client";
2
2
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
3
- import { KEY_ENTER_COMMAND, COMMAND_PRIORITY_NORMAL, $getSelection, $isRangeSelection, $createParagraphNode, $getRoot, } from "lexical";
3
+ import { KEY_ENTER_COMMAND, COMMAND_PRIORITY_NORMAL, $createParagraphNode, $getRoot, } from "lexical";
4
4
  import { clearComposerHistoryAfterUpdate } from "./clearComposerHistory";
5
+ import { resetEditorAfterSend, resetEditorIfUnchanged } from "./resetEditorAfterSend";
5
6
  import { useEffect, useRef } from "react";
6
7
  import { $generateHtmlFromNodes } from "@lexical/html";
7
- import { $isListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
8
- import { $isQuoteNode } from "@lexical/rich-text";
9
8
  import { useMessageFooterContext } from ".";
10
9
  import useConversationStore from "../../../store/conversation";
11
10
  import emitter, { emit } from "../../../utils/events";
@@ -56,57 +55,21 @@ export default function EnterHandler() {
56
55
  const canSend = hasQuote ? hasText : hasText || hasFiles;
57
56
  if (canSend) {
58
57
  const mentions = extractMentions(editor);
59
- const sent = onSendMessage({
58
+ const sending = onSendMessage({
60
59
  plainText,
61
60
  richText,
62
61
  type: hasFiles ? "file" : "text",
63
62
  isInternal,
64
63
  mentions,
65
64
  });
66
- if (!sent) {
67
- return true;
68
- }
65
+ // Clear only once the message is built; a failed build keeps the draft.
66
+ void Promise.resolve(sending).then((sent) => {
67
+ if (sent)
68
+ resetEditorIfUnchanged(editor, { plainText, richText });
69
+ });
70
+ return true;
69
71
  }
70
- clearComposerHistoryAfterUpdate(editor);
71
- editor.update(() => {
72
- const root = $getRoot();
73
- root.clear();
74
- const paragraph = $createParagraphNode();
75
- root.append(paragraph);
76
- paragraph.select();
77
- const selection = $getSelection();
78
- if ($isRangeSelection(selection)) {
79
- const anchorNode = selection.anchor.getNode();
80
- const topLevelNode = anchorNode.getTopLevelElementOrThrow();
81
- const isListNode = $isListNode(topLevelNode);
82
- const isQuoteNode = $isQuoteNode(topLevelNode);
83
- const listType = isListNode
84
- ? topLevelNode.getListType()
85
- : undefined;
86
- if (selection.hasFormat("bold")) {
87
- selection.formatText("bold");
88
- }
89
- if (selection.hasFormat("italic")) {
90
- selection.formatText("italic");
91
- }
92
- if (selection.hasFormat("strikethrough")) {
93
- selection.formatText("strikethrough");
94
- }
95
- if (isQuoteNode) {
96
- // Nếu đang là QuoteNode → chuyển về Paragraph (bình thường)
97
- const paragraph = $createParagraphNode();
98
- // copy con của quote sang paragraph
99
- const children = topLevelNode.getChildren();
100
- children.forEach((child) => paragraph.append(child));
101
- topLevelNode.replace(paragraph);
102
- }
103
- if (isListNode && listType) {
104
- editor.dispatchCommand(listType === "number"
105
- ? INSERT_ORDERED_LIST_COMMAND
106
- : INSERT_UNORDERED_LIST_COMMAND, undefined);
107
- }
108
- }
109
- });
72
+ resetEditorAfterSend(editor);
110
73
  return true;
111
74
  }
112
75
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"FilePreview.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/FilePreview.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AA8IlC,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,YAAY,GACb,EAAE;IACD,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,kDAsBA;AAED,QAAA,MAAM,WAAW,+CAQhB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"FilePreview.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/FilePreview.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AA+IlC,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,QAAQ,EACR,YAAY,GACb,EAAE;IACD,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,kDAsBA;AAED,QAAA,MAAM,WAAW,+CAQhB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -45,7 +45,8 @@ const MediaThumb = ({ file, onRemove, onVideoReady }) => {
45
45
  return () => URL.revokeObjectURL(url);
46
46
  }, [file.originFileObj, file.url]);
47
47
  const handleLoadedMetadata = (event) => {
48
- setDuration(event.currentTarget.duration);
48
+ const { duration: videoDuration } = event.currentTarget;
49
+ setDuration(Number.isFinite(videoDuration) ? videoDuration : null);
49
50
  onVideoReady === null || onVideoReady === void 0 ? void 0 : onVideoReady(file.uid);
50
51
  };
51
52
  return (_jsxs("div", { "data-cp-name": "MediaThumb", className: "relative h-12 w-12 shrink-0", children: [isVideo ? (_jsx("video", { src: src, className: "h-12 w-12 rounded-lg object-cover", preload: "metadata", muted: true, onLoadedMetadata: handleLoadedMetadata })) : (_jsx("img", { src: src, alt: file.name, className: "h-12 w-12 rounded-lg object-cover" })), isLoading && (_jsx("div", { className: "absolute inset-0 flex items-center justify-center rounded-lg bg-black/60", children: _jsxs("span", { className: "text-xs font-semibold text-white", children: [percent, "%"] }) })), isVideo && duration !== null && (_jsx("div", { className: "absolute bottom-1 left-1/2 flex w-9 -translate-x-1/2 items-center justify-center rounded-xl bg-black/70 px-1 text-center text-[10px] font-normal leading-4 text-white", children: formatDuration(duration) })), _jsx(RemoveButton, { file: file, onRemove: onRemove })] }));
@@ -2,6 +2,6 @@ export interface LinkInputPreviewProps {
2
2
  url: string;
3
3
  onRemove: () => void;
4
4
  }
5
- declare const LinkInputPreview: ({ url, onRemove }: LinkInputPreviewProps) => import("react/jsx-runtime").JSX.Element;
5
+ declare const LinkInputPreview: ({ url, onRemove }: LinkInputPreviewProps) => import("react/jsx-runtime").JSX.Element | null;
6
6
  export default LinkInputPreview;
7
7
  //# sourceMappingURL=LinkInputPreview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LinkInputPreview.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/LinkInputPreview.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,gBAAgB,GAAI,mBAAmB,qBAAqB,4CAqEjE,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"LinkInputPreview.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/LinkInputPreview.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,gBAAgB,GAAI,mBAAmB,qBAAqB,mDAwEjE,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -8,9 +8,16 @@ import { useTranslation } from "react-i18next";
8
8
  const LinkInputPreview = ({ url, onRemove }) => {
9
9
  const { t } = useTranslation("conversation-inbox");
10
10
  const { data, isLoading, isError } = useFetchUrlMetadata(url);
11
+ // A link whose preview cannot be loaded is shown as a plain link only.
12
+ const hasNoPreview = isError ||
13
+ !!(data === null || data === void 0 ? void 0 : data.isEmpty) ||
14
+ (!!data && !data.title && !data.description && !data.image) ||
15
+ data === null;
16
+ if (!isLoading && hasNoPreview)
17
+ return null;
11
18
  const handleOpen = () => {
12
19
  openLink(url, t);
13
20
  };
14
- return (_jsxs("div", { "data-testid": "link-input-preview", "data-cp-name": "LinkInputPreview", className: "flex w-full shrink-0 items-center gap-2 overflow-hidden bg-blue-25 pr-2", children: [_jsxs("button", { type: "button", onClick: handleOpen, className: "flex min-w-0 flex-1 items-center gap-2 py-0 pl-0 text-left", children: [_jsx("div", { className: "flex h-[51px] w-[51px] shrink-0 items-center justify-center overflow-hidden bg-blue-25", children: isLoading ? (_jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-[var(--dc-color-primary)] border-t-transparent" })) : (data === null || data === void 0 ? void 0 : data.image) ? (_jsx("img", { src: data.image, alt: "", className: "h-full w-full object-cover" })) : (_jsx(AdaptiveIcon, { stroke: "link", font: "link-alt-b", size: 24, className: "text-[var(--dc-color-primary)]" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [isLoading ? (_jsx("p", { className: "truncate text-[14px] font-medium leading-[1.6] text-gray-900", children: t("link_preview.loading") })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "truncate text-[14px] font-medium leading-[1.6] text-gray-900", children: (data === null || data === void 0 ? void 0 : data.title) || getHostLabel(url) }), ((data === null || data === void 0 ? void 0 : data.description) || !(data === null || data === void 0 ? void 0 : data.title)) && (_jsx("p", { className: "truncate text-[13px] leading-[1.6] text-gray-400", children: (data === null || data === void 0 ? void 0 : data.description) || getHostLabel(url) }))] })), isError && !isLoading && (_jsx("p", { className: "truncate text-[13px] leading-[1.6] text-gray-400", children: getHostLabel(url) }))] })] }), _jsx("button", { type: "button", onClick: onRemove, "data-testid": "link-input-preview-remove", className: "flex h-4 w-4 shrink-0 items-center justify-center", "aria-label": t("link_preview.remove"), children: _jsx(AdaptiveIcon, { stroke: "x", font: "close-b", size: 16, className: "text-gray-900" }) })] }));
21
+ return (_jsxs("div", { "data-testid": "link-input-preview", "data-cp-name": "LinkInputPreview", className: "flex w-full shrink-0 items-center gap-2 overflow-hidden bg-blue-25 pr-2", children: [_jsxs("button", { type: "button", onClick: handleOpen, className: "flex min-w-0 flex-1 items-center gap-2 py-0 pl-0 text-left", children: [_jsx("div", { className: "flex h-[51px] w-[51px] shrink-0 items-center justify-center overflow-hidden bg-blue-25", children: isLoading ? (_jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-[var(--dc-color-primary)] border-t-transparent" })) : (data === null || data === void 0 ? void 0 : data.image) ? (_jsx("img", { src: data.image, alt: "", className: "h-full w-full object-cover" })) : (_jsx(AdaptiveIcon, { stroke: "link", font: "link-alt-b", size: 24, className: "text-[var(--dc-color-primary)]" })) }), _jsx("div", { className: "min-w-0 flex-1", children: isLoading ? (_jsx("p", { className: "truncate text-[14px] font-medium leading-[1.6] text-gray-900", children: t("link_preview.loading") })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "truncate text-[14px] font-medium leading-[1.6] text-gray-900", children: (data === null || data === void 0 ? void 0 : data.title) || getHostLabel(url) }), ((data === null || data === void 0 ? void 0 : data.description) || !(data === null || data === void 0 ? void 0 : data.title)) && (_jsx("p", { className: "truncate text-[13px] leading-[1.6] text-gray-400", children: (data === null || data === void 0 ? void 0 : data.description) || getHostLabel(url) }))] })) })] }), _jsx("button", { type: "button", onClick: onRemove, "data-testid": "link-input-preview-remove", className: "flex h-4 w-4 shrink-0 items-center justify-center", "aria-label": t("link_preview.remove"), children: _jsx(AdaptiveIcon, { stroke: "x", font: "close-b", size: 16, className: "text-gray-900" }) })] }));
15
22
  };
16
23
  export default LinkInputPreview;
@@ -1 +1 @@
1
- {"version":3,"file":"MediaUploadButtons.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/MediaUploadButtons.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,UAAU,EAA0B,MAAM,MAAM,CAAC;AA2B1E,UAAU,uBAAuB;IAC/B,eAAe,EAAE,UAAU,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAE9D,WAAW,CAAC,EAAE,OAAO,CAAC;IAGtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,eAAe,EACf,aAAa,EACb,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,WAAkB,EAClB,cAAsB,GACvB,EAAE,uBAAuB,2CA6FzB"}
1
+ {"version":3,"file":"MediaUploadButtons.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/MediaUploadButtons.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,UAAU,EAA0B,MAAM,MAAM,CAAC;AAiC1E,UAAU,uBAAuB;IAC/B,eAAe,EAAE,UAAU,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAE9D,WAAW,CAAC,EAAE,OAAO,CAAC;IAGtB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,eAAe,EACf,aAAa,EACb,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,WAAkB,EAClB,cAAsB,GACvB,EAAE,uBAAuB,2CA0FzB"}
@@ -3,7 +3,7 @@ import { Button, Upload, message as antdMessage } from "antd";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { Icon } from "../../../components/icon";
5
5
  import { StrokeIcon } from "../../../components/icon/StrokeIcon";
6
- import { MAX_DOCUMENT_SIZE_MB, validateFile } from "../../../utils/fileValidation";
6
+ import { getAttachmentSizeError, getDocumentFormatError, getImageFormatError, MAX_DOCUMENT_SIZE_MB, validateFile, } from "../../../utils/fileValidation";
7
7
  import { DOCUMENT_TYPES } from "./editorConfig";
8
8
  import { mergePickedUploadFiles } from "./mergePickedUploadFiles";
9
9
  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" }) }));
@@ -13,7 +13,7 @@ export default function MediaUploadButtons({ listUploadFiles, onFilesChange, btn
13
13
  listUploadFiles.some((f) => DOCUMENT_TYPES.includes(f.type || ""));
14
14
  const beforeUploadImagesAndVideo = (file) => {
15
15
  if (!acceptVideo && file.type.startsWith("video/")) {
16
- antdMessage.error(t("invalid_image_format", { fileName: file.name }));
16
+ antdMessage.error(getImageFormatError(t, file));
17
17
  return Upload.LIST_IGNORE;
18
18
  }
19
19
  const validation = validateFile(file, t);
@@ -29,14 +29,11 @@ export default function MediaUploadButtons({ listUploadFiles, onFilesChange, btn
29
29
  return Upload.LIST_IGNORE;
30
30
  const isAllowed = DOCUMENT_TYPES.includes(file.type);
31
31
  if (!isAllowed) {
32
- antdMessage.error(t("invalid_document_format", { fileName: file.name }));
32
+ antdMessage.error(getDocumentFormatError(t, file));
33
33
  return Upload.LIST_IGNORE;
34
34
  }
35
35
  if (file.size / 1024 / 1024 > MAX_DOCUMENT_SIZE_MB) {
36
- antdMessage.error(t("file_size_exceeded", {
37
- fileName: file.name,
38
- maxSize: MAX_DOCUMENT_SIZE_MB,
39
- }));
36
+ antdMessage.error(getAttachmentSizeError(t, file, "document", MAX_DOCUMENT_SIZE_MB));
40
37
  return Upload.LIST_IGNORE;
41
38
  }
42
39
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/index.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAEL,mBAAmB,EAEnB,wBAAwB,EAEzB,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAwB/C,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACjE;AASD,eAAO,MAAM,oBAAoB,mDAc/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,gCAAyC,CAAC;AAE9E,QAAA,MAAM,qBAAqB,GAAI,4CAG5B,kBAAkB,4CA+apB,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/index.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAEL,mBAAmB,EAEnB,wBAAwB,EAEzB,MAAM,cAAc,CAAC;AAUtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAwB/C,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,qBAAqB,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACjE;AASD,eAAO,MAAM,oBAAoB,mDAc/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,gCAAyC,CAAC;AAE9E,QAAA,MAAM,qBAAqB,GAAI,4CAG5B,kBAAkB,4CAydpB,CAAC;AAEF,eAAe,qBAAqB,CAAC"}