@droppii-org/chat-sdk 0.1.0 → 0.1.2

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 (29) hide show
  1. package/dist/assets/sdk/sql-wasm.wasm +0 -0
  2. package/dist/components/message/MessageList.d.ts.map +1 -1
  3. package/dist/components/message/MessageList.js +8 -2
  4. package/dist/components/message/item/MessageStatusIndicator.d.ts +8 -0
  5. package/dist/components/message/item/MessageStatusIndicator.d.ts.map +1 -0
  6. package/dist/components/message/item/MessageStatusIndicator.js +16 -0
  7. package/dist/components/message/item/TextMessage.d.ts.map +1 -1
  8. package/dist/components/message/item/TextMessage.js +4 -1
  9. package/dist/components/message/item/UrlTextMessage.d.ts.map +1 -1
  10. package/dist/components/message/item/UrlTextMessage.js +5 -3
  11. package/dist/components/message/item/index.d.ts.map +1 -1
  12. package/dist/components/message/item/index.js +15 -6
  13. package/dist/components/searchConversation/item/SearchItemAsMessage.js +2 -2
  14. package/dist/components/searchConversation/item/SearchItemAsUser.js +2 -2
  15. package/dist/hooks/message/useMessage.d.ts +1 -0
  16. package/dist/hooks/message/useMessage.d.ts.map +1 -1
  17. package/dist/hooks/message/useMessage.js +6 -0
  18. package/dist/hooks/message/useSendMessage.d.ts +5 -0
  19. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  20. package/dist/hooks/message/useSendMessage.js +134 -27
  21. package/dist/locales/vi/common.json +19 -2
  22. package/dist/styles/global.css +1 -1
  23. package/dist/utils/common.d.ts +13 -0
  24. package/dist/utils/common.d.ts.map +1 -1
  25. package/dist/utils/common.js +51 -0
  26. package/dist/utils/events.d.ts +1 -0
  27. package/dist/utils/events.d.ts.map +1 -1
  28. package/package.json +13 -10
  29. package/dist/tsconfig.tsbuildinfo +0 -1
File without changes
@@ -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,4CA+M3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
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"}
@@ -126,10 +126,11 @@ const MessageList = (props) => {
126
126
  if (!conversationData) {
127
127
  return (_jsx("div", { className: "flex flex-1 items-center justify-center h-full", children: _jsx(Empty, { description: t("no_conversation_data") }) }));
128
128
  }
129
- return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full", style: {
129
+ return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0", style: {
130
130
  backgroundImage: `url(${images.conversationBg})`,
131
131
  backgroundSize: "cover",
132
132
  backgroundPosition: "center",
133
+ overflowX: "hidden",
133
134
  }, children: [_jsx(MessageHeader, { onClose: onClose, currentSession: currentSession }), _jsx("div", { id: "scrollableMessagesDiv", ref: scrollRef, style: {
134
135
  height: "100%",
135
136
  overflowY: "auto",
@@ -137,7 +138,12 @@ const MessageList = (props) => {
137
138
  display: "flex",
138
139
  flexDirection: "column-reverse",
139
140
  paddingBottom: 12,
140
- }, children: _jsx(InfiniteScroll, { dataLength: ((_a = loadState.messageList) === null || _a === void 0 ? void 0 : _a.length) || 0, next: loadMoreOldMessage, style: { display: "flex", flexDirection: "column-reverse" }, inverse: true, hasMore: loadState.hasMoreOld, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableMessagesDiv", onScroll: (e) => {
141
+ }, children: _jsx(InfiniteScroll, { dataLength: ((_a = loadState.messageList) === null || _a === void 0 ? void 0 : _a.length) || 0, next: loadMoreOldMessage, style: {
142
+ display: "flex",
143
+ flexDirection: "column-reverse",
144
+ minWidth: 0,
145
+ width: "100%"
146
+ }, inverse: true, hasMore: loadState.hasMoreOld, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableMessagesDiv", onScroll: (e) => {
141
147
  const target = e.target;
142
148
  if (target.scrollTop > BOTTOM_THRESHOLD) {
143
149
  handleMarkConversationMessageAsRead();
@@ -0,0 +1,8 @@
1
+ import { MessageItem } from "@openim/wasm-client-sdk";
2
+ interface Props {
3
+ message: MessageItem;
4
+ isLatest: boolean;
5
+ }
6
+ declare const MessageStatusIndicator: ({ message, isLatest }: Props) => import("react/jsx-runtime").JSX.Element | null;
7
+ export default MessageStatusIndicator;
8
+ //# sourceMappingURL=MessageStatusIndicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageStatusIndicator.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/MessageStatusIndicator.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,yBAAyB,CAAC;AAOrE,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,QAAA,MAAM,sBAAsB,GAAI,uBAAuB,KAAK,mDAkB3D,CAAC;AAEF,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { MessageStatus } from "@openim/wasm-client-sdk";
3
+ import { LoadingOutlined, CheckCircleFilled, ExclamationCircleFilled, } from "@ant-design/icons";
4
+ const MessageStatusIndicator = ({ message, isLatest }) => {
5
+ if (message.status === MessageStatus.Sending) {
6
+ return _jsx(LoadingOutlined, { className: "text-gray-400 text-sm" });
7
+ }
8
+ if (message.status === MessageStatus.Succeed && isLatest) {
9
+ return _jsx(CheckCircleFilled, { className: "text-blue-500 text-sm" });
10
+ }
11
+ if (message.status === MessageStatus.Failed) {
12
+ return (_jsx(ExclamationCircleFilled, { className: "text-red-500 text-sm" }));
13
+ }
14
+ return null;
15
+ };
16
+ export default MessageStatusIndicator;
@@ -1 +1 @@
1
- {"version":3,"file":"TextMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/TextMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAe,MAAM,yBAAyB,CAAC;AAInE,UAAU,oBAAoB;IAC5B,OAAO,EAAE,WAAW,CAAC;CACtB;AACD,QAAA,MAAM,eAAe,GAAI,OAAO,oBAAoB,mDAiCnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"TextMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/TextMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAe,MAAM,yBAAyB,CAAC;AAKnE,UAAU,oBAAoB;IAC5B,OAAO,EAAE,WAAW,CAAC;CACtB;AACD,QAAA,MAAM,eAAe,GAAI,OAAO,oBAAoB,mDAoCnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { MessageType } from "@openim/wasm-client-sdk";
4
4
  import { useAuthStore } from "../../..";
5
+ import { sanitizeHtml } from "../../../utils/common";
5
6
  const TextMessageItem = (props) => {
6
7
  var _a, _b, _c;
7
8
  const { message } = props;
@@ -20,7 +21,9 @@ const TextMessageItem = (props) => {
20
21
  if (Object.keys(extendMessageInfo).length > 0 &&
21
22
  (extendMessageInfo === null || extendMessageInfo === void 0 ? void 0 : extendMessageInfo.messageInfo)) {
22
23
  const htmlContent = ((_b = (_a = extendMessageInfo === null || extendMessageInfo === void 0 ? void 0 : extendMessageInfo.messageInfo) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.content) || "";
23
- return (_jsx("div", { className: "!text-sm sm:text-base break-words whitespace-pre-line flex-1", dangerouslySetInnerHTML: { __html: htmlContent } }));
24
+ // 🔒 Sanitize HTML to prevent XSS attacks
25
+ const sanitizedContent = sanitizeHtml(htmlContent);
26
+ return (_jsx("div", { className: "!text-sm sm:text-base break-words whitespace-pre-line flex-1", style: { wordBreak: "break-word", overflowWrap: "anywhere" }, dangerouslySetInnerHTML: { __html: sanitizedContent } }));
24
27
  }
25
28
  return (_jsx("span", { className: "!text-sm sm:text-base whitespace-pre-wrap", children: ((_c = message === null || message === void 0 ? void 0 : message.textElem) === null || _c === void 0 ? void 0 : _c.content) || "" }));
26
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"UrlTextMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/UrlTextMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAMtD,UAAU,uBAAuB;IAC/B,OAAO,EAAE,WAAW,CAAC;CACtB;AACD,QAAA,MAAM,kBAAkB,GAAI,OAAO,uBAAuB,4CAiEzD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"UrlTextMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/UrlTextMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAMtD,UAAU,uBAAuB;IAC/B,OAAO,EAAE,WAAW,CAAC;CACtB;AACD,QAAA,MAAM,kBAAkB,GAAI,OAAO,uBAAuB,4CAwEzD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { getHostFromUrl, wrapLinksInHtml } from "../../../utils/common";
3
+ import { getHostFromUrl, wrapLinksInHtml, sanitizeHtml } from "../../../utils/common";
4
4
  import { useFetchExternalLink } from "../../../hooks/common/useFetchExternalLink";
5
5
  import { Image } from "antd";
6
6
  const UrlTextMessageItem = (props) => {
@@ -23,8 +23,10 @@ const UrlTextMessageItem = (props) => {
23
23
  if (Object.keys(extendMessageInfo).length > 0 &&
24
24
  (extendMessageInfo === null || extendMessageInfo === void 0 ? void 0 : extendMessageInfo.messageInfo)) {
25
25
  const htmlContent = ((_d = (_c = extendMessageInfo === null || extendMessageInfo === void 0 ? void 0 : extendMessageInfo.messageInfo) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.content) || "";
26
- return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "!text-sm sm:text-base break-words whitespace-pre-line flex-1 break-all", dangerouslySetInnerHTML: { __html: wrapLinksInHtml(htmlContent) } }), !!externalLinkData && (_jsxs("div", { className: "flex items-center rounded-md px-3 py-2 border-l-4 border-l-blue-500 bg-blue-50 gap-2 cursor-pointer", onClick: () => onPressExternalLink((externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.url) || ""), children: [_jsxs("div", { className: "flex flex-1 flex-col gap-1", children: [(externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.title) && (_jsx("span", { className: "!text-sm font-semibold truncate", children: externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.title })), (externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.description) && (_jsx("span", { className: "!text-sm text-gray-500 line-clamp-2", children: externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.description })), (externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.url) && (_jsx("span", { className: "!text-sm font-medium text-blue-500", children: getHostFromUrl(externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.url) }))] }), ((_e = externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.logo) === null || _e === void 0 ? void 0 : _e.url) && (_jsx(Image, { src: (_f = externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.logo) === null || _f === void 0 ? void 0 : _f.url, width: 48, height: 48 }))] }))] }));
26
+ // 🔒 Sanitize HTML to prevent XSS attacks
27
+ const sanitizedContent = sanitizeHtml(wrapLinksInHtml(htmlContent));
28
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("div", { className: "!text-sm sm:text-base break-words whitespace-pre-line flex-1", style: { wordBreak: "break-word", overflowWrap: "anywhere" }, dangerouslySetInnerHTML: { __html: sanitizedContent } }), !!externalLinkData && (_jsxs("div", { className: "flex items-center rounded-md px-3 py-2 border-l-4 border-l-blue-500 bg-blue-50 gap-2 cursor-pointer overflow-hidden", style: { wordBreak: "break-word", overflowWrap: "anywhere" }, onClick: () => onPressExternalLink((externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.url) || ""), children: [_jsxs("div", { className: "flex flex-1 flex-col gap-1", children: [(externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.title) && (_jsx("span", { className: "!text-sm font-semibold truncate", children: externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.title })), (externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.description) && (_jsx("span", { className: "!text-sm text-gray-500 line-clamp-2", children: externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.description })), (externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.url) && (_jsx("span", { className: "!text-sm font-medium text-blue-500", children: getHostFromUrl(externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.url) }))] }), ((_e = externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.logo) === null || _e === void 0 ? void 0 : _e.url) && (_jsx(Image, { src: (_f = externalLinkData === null || externalLinkData === void 0 ? void 0 : externalLinkData.logo) === null || _f === void 0 ? void 0 : _f.url, width: 48, height: 48 }))] }))] }));
27
29
  }
28
- return (_jsx("span", { className: "!text-sm sm:text-base break-words whitespace-pre-line flex-1 break-all", children: ((_h = (_g = message === null || message === void 0 ? void 0 : message.urlTextElem) === null || _g === void 0 ? void 0 : _g.urls) === null || _h === void 0 ? void 0 : _h[0]) || "" }));
30
+ return (_jsx("span", { className: "!text-sm sm:text-base break-words whitespace-pre-line flex-1", style: { wordBreak: "break-word", overflowWrap: "anywhere" }, children: ((_h = (_g = message === null || message === void 0 ? void 0 : message.urlTextElem) === null || _g === void 0 ? void 0 : _g.urls) === null || _h === void 0 ? void 0 : _h[0]) || "" }));
29
31
  };
30
32
  export default UrlTextMessageItem;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,WAAW,IAAI,eAAe,EAE/B,MAAM,yBAAyB,CAAC;AAgBjC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC;AAED,QAAA,MAAM,WAAW,GAAI,0BAA0B,gBAAgB,mDAgI9D,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,WAAW,IAAI,eAAe,EAG/B,MAAM,yBAAyB,CAAC;AAmBjC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC;AAED,QAAA,MAAM,WAAW,GAAI,0BAA0B,gBAAgB,mDA6J9D,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import dayjs from "dayjs";
3
3
  import clsx from "clsx";
4
4
  import { Avatar } from "antd";
5
- import { MessageType, } from "@openim/wasm-client-sdk";
5
+ import { MessageStatus, MessageType, } from "@openim/wasm-client-sdk";
6
6
  import TextMessageItem from "./TextMessage";
7
7
  import ImageMessageItem from "./ImageMessage";
8
8
  import FileMessageItem from "./FileMessage";
@@ -13,9 +13,14 @@ import { formatTimestamp } from "../../../utils/common";
13
13
  import useAuthStore from "../../../store/auth";
14
14
  import useConversationStore from "../../../store/conversation";
15
15
  import { useMemo } from "react";
16
+ import { Trans, useTranslation } from "react-i18next";
16
17
  import UrlTextMessageItem from "./UrlTextMessage";
18
+ import MessageStatusIndicator from "./MessageStatusIndicator";
19
+ import { useSendMessage, isMediaResendable } from "../../../hooks/message/useSendMessage";
17
20
  const MessageItem = ({ message, allMessages }) => {
18
- var _a, _b, _c, _d;
21
+ var _a, _b, _c, _d, _e;
22
+ const { t } = useTranslation();
23
+ const { resendMessage } = useSendMessage();
19
24
  const userID = useAuthStore((state) => state.userID);
20
25
  const isCrm = useAuthStore((state) => state.isCrm);
21
26
  const conversationData = useConversationStore((state) => state.conversationData);
@@ -49,6 +54,8 @@ const MessageItem = ({ message, allMessages }) => {
49
54
  if (!isVisibleGroup)
50
55
  return null;
51
56
  const isMine = (message === null || message === void 0 ? void 0 : message.sendID) === userID;
57
+ const isLatestMine = ((_a = allMessages.find((m) => m.sendID === userID)) === null || _a === void 0 ? void 0 : _a.clientMsgID) ===
58
+ message.clientMsgID;
52
59
  const previousMessage = getVisibleNeighbor(allMessages, message, "prev");
53
60
  const nextMessage = getVisibleNeighbor(allMessages, message, "next");
54
61
  const prevSameUser = (previousMessage === null || previousMessage === void 0 ? void 0 : previousMessage.sendID) === (message === null || message === void 0 ? void 0 : message.sendID);
@@ -61,10 +68,12 @@ const MessageItem = ({ message, allMessages }) => {
61
68
  if (!isCrm && MessageType.CustomMessage === (message === null || message === void 0 ? void 0 : message.contentType)) {
62
69
  return null;
63
70
  }
64
- return (_jsxs("div", { className: "flex flex-col gap-2 py-1 px-3 sm:p x-4", id: `${MSG_ITEM_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`, children: [showTimeBreak && (_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "text-xs text-gray-600 text-center bg-neutral-100 px-2 py-1 rounded-full", children: formatTimestamp(message.sendTime, {
71
+ return (_jsxs("div", { className: "flex flex-col gap-2 py-1 px-3 sm:px-4 min-w-0", id: `${MSG_ITEM_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`, children: [showTimeBreak && (_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "text-xs text-gray-600 text-center bg-neutral-100 px-2 py-1 rounded-full", children: formatTimestamp(message.sendTime, {
65
72
  dateMonthFormat: "DD MMMM",
66
- }) }) })), _jsx("div", { className: clsx("flex", isMine ? "justify-end" : "justify-start"), children: _jsxs("div", { className: clsx("flex flex-1 items-end gap-2", isMine ? "justify-end" : "justify-start"), children: [!isMine && showSenderInfo && (_jsx("div", { className: "flex items-center justify-center w-[32px] h-[32px]", children: showSenderAvatar && (_jsx(Avatar, { src: message === null || message === void 0 ? void 0 : message.senderFaceUrl, children: ((_b = (_a = message === null || message === void 0 ? void 0 : message.senderNickname) === null || _a === void 0 ? void 0 : _a.charAt) === null || _b === void 0 ? void 0 : _b.call(_a, 0)) || "A" })) })), _jsxs("div", { className: clsx("flex flex-col flex-[0.8]", isMine ? "items-end" : "items-start"), children: [showSenderName && showSenderInfo && (_jsx("span", { className: "text-xs text-gray-500 mb-1 px-3", children: message === null || message === void 0 ? void 0 : message.senderNickname })), _jsxs("div", { className: clsx("px-3 py-2 rounded-2xl max-w-full break-words flex flex-col flex-1 text-gray-900 gap-1", isMine ? "bg-blue-100" : "bg-white"), id: `${MSG_ITEM_CONTENT_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`, children: [(message === null || message === void 0 ? void 0 : message.contentType) === MessageType.MergeMessage ? (_jsxs("div", { children: [(_d = (_c = message === null || message === void 0 ? void 0 : message.mergeElem) === null || _c === void 0 ? void 0 : _c.multiMessage) === null || _d === void 0 ? void 0 : _d.map((item) => {
67
- return renderMessageByType(item);
68
- }), (message === null || message === void 0 ? void 0 : message.textElem) && _jsx(TextMessageItem, { message: message })] })) : (renderMessageByType(message)), _jsx("span", { className: clsx("text-xs text-gray-500 text-right"), children: dayjs(message === null || message === void 0 ? void 0 : message.sendTime).format("HH:mm") })] })] })] }) }, message === null || message === void 0 ? void 0 : message.clientMsgID)] }, message === null || message === void 0 ? void 0 : message.clientMsgID));
73
+ }) }) })), _jsx("div", { className: clsx("flex min-w-0", isMine ? "justify-end" : "justify-start"), children: _jsxs("div", { className: clsx("flex flex-1 items-end gap-2 min-w-0", isMine ? "justify-end" : "justify-start"), children: [!isMine && showSenderInfo && (_jsx("div", { className: "flex items-center justify-center w-[32px] h-[32px]", children: showSenderAvatar && (_jsx(Avatar, { src: message === null || message === void 0 ? void 0 : message.senderFaceUrl, children: ((_c = (_b = message === null || message === void 0 ? void 0 : message.senderNickname) === null || _b === void 0 ? void 0 : _b.charAt) === null || _c === void 0 ? void 0 : _c.call(_b, 0)) || "A" })) })), _jsxs("div", { className: clsx("flex flex-col flex-[0.8] min-w-0", isMine ? "items-end" : "items-start"), children: [showSenderName && showSenderInfo && (_jsx("span", { className: "text-xs text-gray-500 mb-1 px-3", children: message === null || message === void 0 ? void 0 : message.senderNickname })), _jsxs("div", { className: "flex items-end gap-1.5", children: [isMine && _jsx(MessageStatusIndicator, { message: message, isLatest: isLatestMine }), _jsxs("div", { className: clsx("px-3 py-2 rounded-2xl max-w-full min-w-0 break-words flex flex-col flex-1 text-gray-900 gap-1", isMine ? "bg-blue-100" : "bg-white"), style: { wordBreak: "break-word", overflowWrap: "anywhere" }, id: `${MSG_ITEM_CONTENT_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`, children: [(message === null || message === void 0 ? void 0 : message.contentType) === MessageType.MergeMessage ? (_jsxs("div", { children: [(_e = (_d = message === null || message === void 0 ? void 0 : message.mergeElem) === null || _d === void 0 ? void 0 : _d.multiMessage) === null || _e === void 0 ? void 0 : _e.map((item) => {
74
+ return renderMessageByType(item);
75
+ }), (message === null || message === void 0 ? void 0 : message.textElem) && _jsx(TextMessageItem, { message: message })] })) : (renderMessageByType(message)), _jsx("span", { className: clsx("text-xs text-gray-500 text-right"), children: dayjs(message === null || message === void 0 ? void 0 : message.sendTime).format("HH:mm") })] })] }), isMine && message.status === MessageStatus.Failed && (_jsx("span", { className: "text-red-500 text-xs mt-1", children: isMediaResendable(message) ? (_jsx(Trans, { i18nKey: "message_send_failed", components: {
76
+ bold: (_jsx("strong", { className: "cursor-pointer", onClick: () => resendMessage(message) })),
77
+ } })) : (t("message_send_failed_no_retry")) }))] })] }) }, message === null || message === void 0 ? void 0 : message.clientMsgID)] }, message === null || message === void 0 ? void 0 : message.clientMsgID));
69
78
  };
70
79
  export default MessageItem;
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { SessionType } from "@openim/wasm-client-sdk";
4
4
  import { Avatar } from "antd";
5
- import { formatTimestamp, highlightSearch } from "../../../utils/common";
5
+ import { formatTimestamp, highlightSearch, sanitizeHtml } from "../../../utils/common";
6
6
  import { DChatSDK } from "../../../constants/sdk";
7
7
  import { useChatContext } from "../../../context/ChatContext";
8
8
  import { message as antdMessage } from "antd";
@@ -63,7 +63,7 @@ const SearchItemAsMessage = (props) => {
63
63
  : conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName }), _jsx("span", { className: "text-xs text-gray-500", children: formatTimestamp(message.sendTime, {
64
64
  hasTime: true,
65
65
  }) })] }), _jsx("div", { className: "flex flex-col flex-1 min-w-0", children: _jsx("span", { className: "text-xs flex-1 text-gray-500 truncate", dangerouslySetInnerHTML: {
66
- __html: highlightSearch(msgContent, searchTerm),
66
+ __html: sanitizeHtml(highlightSearch(msgContent, searchTerm)),
67
67
  } }) })] })] }, message.clientMsgID));
68
68
  };
69
69
  export default SearchItemAsMessage;
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Avatar, message } from "antd";
4
- import { highlightSearch } from "../../../utils/common";
4
+ import { highlightSearch, sanitizeHtml } from "../../../utils/common";
5
5
  import useConversationStore from "../../../store/conversation";
6
6
  import { useTranslation } from "react-i18next";
7
7
  const SearchItemAsUser = (props) => {
@@ -19,7 +19,7 @@ const SearchItemAsUser = (props) => {
19
19
  .setSelectedConversationId(session.conversationId);
20
20
  };
21
21
  return (_jsxs("div", { className: "py-3 px-2 flex items-center gap-3 hover:bg-gray-100 hover:rounded-sm cursor-pointer border-b mx-1", onClick: onPressItem, children: [_jsx(Avatar, { size: "large", src: (_d = session.owner) === null || _d === void 0 ? void 0 : _d.avatar, alt: (_e = session.owner) === null || _e === void 0 ? void 0 : _e.username, children: (_f = session.owner) === null || _f === void 0 ? void 0 : _f.fullName.charAt(0).toUpperCase() }), _jsx("div", { className: "flex flex-col flex-1 min-w-0", children: _jsx("p", { className: "text-sm font-semibold truncate", dangerouslySetInnerHTML: {
22
- __html: highlightSearch(ownerName, searchTerm),
22
+ __html: sanitizeHtml(highlightSearch(ownerName, searchTerm)),
23
23
  } }) })] }, session.id));
24
24
  };
25
25
  export default SearchItemAsUser;
@@ -20,5 +20,6 @@ export declare const useMessage: (conversationId?: string, searchClientMsgID?: s
20
20
  };
21
21
  export declare const pushNewMessage: (message: MessageItem) => void;
22
22
  export declare const updateOneMessage: (message: MessageItem) => void;
23
+ export declare const removeOneMessage: (clientMsgID: string) => void;
23
24
  export declare const getVisibleNeighbor: (allMessages: MessageItem[], current: MessageItem, direction: "prev" | "next") => MessageItem | undefined;
24
25
  //# sourceMappingURL=useMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessage.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,WAAW,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQ7E,eAAO,MAAM,kBAAkB,eAc9B,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,iBAAiB,MAAM,EACvB,oBAAoB,MAAM;;;;;qBAML,WAAW,EAAE;;;;;;qBAAb,WAAW,EAAE;;;;;;CAuMnC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,SACpB,CAAC;AAChC,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,SACpB,CAAC;AAElC,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,KACzB,WAAW,GAAG,SAkBhB,CAAC"}
1
+ {"version":3,"file":"useMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessage.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,WAAW,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAQ7E,eAAO,MAAM,kBAAkB,eAc9B,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,iBAAiB,MAAM,EACvB,oBAAoB,MAAM;;;;;qBAML,WAAW,EAAE;;;;;;qBAAb,WAAW,EAAE;;;;;;CAkNnC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,SACpB,CAAC;AAChC,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,SACpB,CAAC;AAClC,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,SACf,CAAC;AAEtC,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,KACzB,WAAW,GAAG,SAkBhB,CAAC"}
@@ -143,11 +143,16 @@ export const useMessage = (conversationId, searchClientMsgID) => {
143
143
  return Object.assign(Object.assign({}, preState), { messageList: tmpList });
144
144
  });
145
145
  };
146
+ const removeOneMessage = (clientMsgID) => {
147
+ setLoadState((preState) => (Object.assign(Object.assign({}, preState), { messageList: preState.messageList.filter((msg) => msg.clientMsgID !== clientMsgID) })));
148
+ };
146
149
  emitter.on("PUSH_NEW_MSG", pushNewMessage);
147
150
  emitter.on("UPDATE_ONE_MSG", updateOneMessage);
151
+ emitter.on("REMOVE_ONE_MSG", removeOneMessage);
148
152
  return () => {
149
153
  emitter.off("PUSH_NEW_MSG", pushNewMessage);
150
154
  emitter.off("UPDATE_ONE_MSG", updateOneMessage);
155
+ emitter.off("REMOVE_ONE_MSG", removeOneMessage);
151
156
  };
152
157
  }, []);
153
158
  useEffect(() => {
@@ -179,6 +184,7 @@ export const useMessage = (conversationId, searchClientMsgID) => {
179
184
  };
180
185
  export const pushNewMessage = (message) => emit("PUSH_NEW_MSG", message);
181
186
  export const updateOneMessage = (message) => emit("UPDATE_ONE_MSG", message);
187
+ export const removeOneMessage = (clientMsgID) => emit("REMOVE_ONE_MSG", clientMsgID);
182
188
  export const getVisibleNeighbor = (allMessages, current, direction) => {
183
189
  const currentIndex = allMessages.findIndex((m) => m.clientMsgID === current.clientMsgID);
184
190
  if (currentIndex === -1)
@@ -2,6 +2,10 @@ import { MergerMsgParams, MessageItem } from "@openim/wasm-client-sdk";
2
2
  import { ExtendMessageInfo, FileMsgParamsByFile, ImageMsgParamsByFile, VideoMsgParamsByFile } from "../../types/chat";
3
3
  import { UploadFile } from "antd";
4
4
  import { ISessionByStatus } from "../../store/type";
5
+ export declare const isMediaResendable: (message: {
6
+ clientMsgID: string;
7
+ contentType: number;
8
+ }) => boolean;
5
9
  export declare const createTextMessage: (text: string) => Promise<MessageItem | null>;
6
10
  export declare const createImageMessageByFile: (file: ImageMsgParamsByFile) => Promise<MessageItem | null>;
7
11
  export declare const createMergerMessage: (mergerMsgParams: MergerMsgParams) => Promise<MessageItem | null>;
@@ -20,6 +24,7 @@ export declare const useSendMessage: () => {
20
24
  files: UploadFile[];
21
25
  currentSession?: ISessionByStatus;
22
26
  }) => Promise<void>;
27
+ resendMessage: (failedMessage: MessageItem) => Promise<void>;
23
28
  };
24
29
  export declare const generateExtendMessageInfo: ({ richText, currentSession, }: {
25
30
  richText?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"useSendMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useSendMessage.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,WAAW,EAEZ,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAQlC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,eAAO,MAAM,iBAAiB,GAAU,MAAM,MAAM,gCAanD,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,MAAM,oBAAoB,gCAaxE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAU,iBAAiB,eAAe,gCAazE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,MAAM,oBAAoB,gCAaxE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAU,MAAM,mBAAmB,gCAatE,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAU,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,gCActE,CAAC;AAEF,eAAO,MAAM,cAAc;gEAsDpB;QACD,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,gBAAgB,CAAC;KACnC;wEA+BE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,cAAc,CAAC,EAAE,gBAAgB,CAAC;KACnC;CA0GJ,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,+BAGvC;IACD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,gBAAgB,CAAC;CACnC,KAWM,iBACN,CAAC"}
1
+ {"version":3,"file":"useSendMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useSendMessage.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,WAAW,EAGZ,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAQlC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAepD,eAAO,MAAM,iBAAiB,GAAI,SAAS;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,YACK,CAAC;AAE7F,eAAO,MAAM,iBAAiB,GAAU,MAAM,MAAM,gCAanD,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,MAAM,oBAAoB,gCAaxE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAAU,iBAAiB,eAAe,gCAazE,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAU,MAAM,oBAAoB,gCAaxE,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAU,MAAM,mBAAmB,gCAatE,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAU,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,gCActE,CAAC;AAEF,eAAO,MAAM,cAAc;gEAmJpB;QACD,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,gBAAgB,CAAC;KACnC;wEA+BE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,UAAU,EAAE,CAAC;QACpB,cAAc,CAAC,EAAE,gBAAgB,CAAC;KACnC;mCAtIqB,WAAW;CA+PpC,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,+BAGvC;IACD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,gBAAgB,CAAC;CACnC,KAWM,iBACN,CAAC"}
@@ -1,13 +1,20 @@
1
- import { MessageStatus, } from "@openim/wasm-client-sdk";
1
+ import { MessageStatus, MessageType, } from "@openim/wasm-client-sdk";
2
2
  import { DChatSDK } from "../../constants/sdk";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
  import { useChatContext } from "../../context/ChatContext";
5
5
  import { useCallback } from "react";
6
- import { pushNewMessage, updateOneMessage } from "./useMessage";
6
+ import { pushNewMessage, removeOneMessage, updateOneMessage } from "./useMessage";
7
7
  import { emit } from "../../utils/events";
8
8
  import useConversationStore from "../../store/conversation";
9
9
  import useAuthStore from "../../store/auth";
10
10
  import { extractLinks, generateContentBasedOnMessageType, } from "../../utils/common";
11
+ const messageFileStore = new Map();
12
+ const MEDIA_CONTENT_TYPES = new Set([
13
+ MessageType.PictureMessage,
14
+ MessageType.VideoMessage,
15
+ MessageType.FileMessage,
16
+ ]);
17
+ export const isMediaResendable = (message) => !MEDIA_CONTENT_TYPES.has(message.contentType) || messageFileStore.has(message.clientMsgID);
11
18
  export const createTextMessage = async (text) => {
12
19
  let textMessage = await DChatSDK.createTextMessage(text, new Date().getTime().toString())
13
20
  .then(({ data }) => {
@@ -78,34 +85,119 @@ export const useSendMessage = () => {
78
85
  const { user } = useChatContext();
79
86
  const conversationData = useConversationStore((state) => state.conversationData);
80
87
  const { userID: recvID, groupID } = conversationData || {};
88
+ const dispatchMessage = useCallback(async (message) => {
89
+ var _a;
90
+ const desc = generateContentBasedOnMessageType(message.contentType, ((_a = message === null || message === void 0 ? void 0 : message.textElem) === null || _a === void 0 ? void 0 : _a.content) || "") || "Bạn có tin nhắn mới";
91
+ const { data: successMessage } = await DChatSDK.sendMessage({
92
+ recvID: recvID || "",
93
+ groupID: groupID || "",
94
+ message,
95
+ offlinePushInfo: {
96
+ title: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName) || "Droppii Chat",
97
+ desc,
98
+ ex: JSON.stringify({
99
+ icon: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.faceURL) || "",
100
+ conversationId: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "",
101
+ title: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName) || "Droppii Chat",
102
+ desc,
103
+ }),
104
+ iOSPushSound: "default",
105
+ iOSBadgeCount: true,
106
+ },
107
+ });
108
+ updateOneMessage(successMessage);
109
+ messageFileStore.delete(message.clientMsgID);
110
+ }, [recvID, groupID]);
81
111
  const sendMessage = useCallback(async (message) => {
82
- var _a, _b;
112
+ pushNewMessage(message);
113
+ emit("CHAT_LIST_SCROLL_TO_BOTTOM");
83
114
  try {
84
- pushNewMessage(message);
85
- emit("CHAT_LIST_SCROLL_TO_BOTTOM");
86
- const { data: successMessage } = await DChatSDK.sendMessage({
87
- recvID: recvID || "",
88
- groupID: groupID || "",
89
- message: message,
90
- offlinePushInfo: {
91
- title: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName) || "Droppii Chat",
92
- desc: `${generateContentBasedOnMessageType(message.contentType, ((_a = message === null || message === void 0 ? void 0 : message.textElem) === null || _a === void 0 ? void 0 : _a.content) || "")}` || "Bạn có tin nhắn mới",
93
- ex: JSON.stringify({
94
- icon: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.faceURL) || "",
95
- conversationId: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "",
96
- title: (conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName) || "Droppii Chat",
97
- desc: `${generateContentBasedOnMessageType(message.contentType, ((_b = message === null || message === void 0 ? void 0 : message.textElem) === null || _b === void 0 ? void 0 : _b.content) || "")}` || "Bạn có tin nhắn mới",
98
- }),
99
- iOSPushSound: "default",
100
- iOSBadgeCount: true,
101
- },
102
- });
103
- updateOneMessage(successMessage);
115
+ await dispatchMessage(message);
104
116
  }
105
- catch (error) {
117
+ catch (_a) {
106
118
  updateOneMessage(Object.assign(Object.assign({}, message), { status: MessageStatus.Failed }));
107
119
  }
108
- }, [recvID, groupID]);
120
+ }, [dispatchMessage]);
121
+ const resendMessage = useCallback(async (failedMessage) => {
122
+ var _a;
123
+ const storedFiles = messageFileStore.get(failedMessage.clientMsgID);
124
+ if (!storedFiles) {
125
+ let newTextPayload = null;
126
+ const textContent = ((_a = failedMessage.textElem) === null || _a === void 0 ? void 0 : _a.content) || failedMessage.content || "";
127
+ if (failedMessage.contentType === MessageType.UrlTextMessage && failedMessage.urlTextElem) {
128
+ newTextPayload = await createUrlTextMessage(textContent, failedMessage.urlTextElem.urls);
129
+ }
130
+ else {
131
+ newTextPayload = await createTextMessage(textContent);
132
+ }
133
+ if (!newTextPayload) {
134
+ updateOneMessage(Object.assign(Object.assign({}, failedMessage), { status: MessageStatus.Failed }));
135
+ return;
136
+ }
137
+ const conversationID = (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "";
138
+ await DChatSDK.deleteMessageFromLocalStorage({
139
+ conversationID,
140
+ clientMsgID: failedMessage.clientMsgID,
141
+ });
142
+ removeOneMessage(failedMessage.clientMsgID);
143
+ await sendMessage(Object.assign(Object.assign({}, newTextPayload), { ex: failedMessage.ex }));
144
+ return;
145
+ }
146
+ let newPayload = null;
147
+ if (storedFiles.type === "image" && failedMessage.pictureElem) {
148
+ const { sourcePicture, bigPicture, snapshotPicture, sourcePath } = failedMessage.pictureElem;
149
+ newPayload = await createImageMessageByFile({
150
+ sourcePicture,
151
+ bigPicture,
152
+ snapshotPicture,
153
+ sourcePath,
154
+ file: storedFiles.file,
155
+ });
156
+ }
157
+ else if (storedFiles.type === "video" && failedMessage.videoElem) {
158
+ const elem = failedMessage.videoElem;
159
+ newPayload = await createVideoMessageByFile({
160
+ videoPath: elem.videoPath,
161
+ duration: elem.duration,
162
+ videoType: elem.videoType,
163
+ snapshotPath: elem.snapshotPath,
164
+ videoUUID: elem.videoUUID,
165
+ videoUrl: elem.videoUrl,
166
+ videoSize: elem.videoSize,
167
+ snapshotUUID: elem.snapshotUUID,
168
+ snapshotSize: elem.snapshotSize,
169
+ snapshotUrl: elem.snapshotUrl,
170
+ snapshotWidth: elem.snapshotWidth,
171
+ snapshotHeight: elem.snapshotHeight,
172
+ videoFile: storedFiles.videoFile,
173
+ snapshotFile: storedFiles.snapshotFile,
174
+ });
175
+ }
176
+ else if (storedFiles.type === "file" && failedMessage.fileElem) {
177
+ const elem = failedMessage.fileElem;
178
+ newPayload = await createFileMessageByFile({
179
+ filePath: elem.filePath,
180
+ fileName: elem.fileName,
181
+ uuid: elem.uuid,
182
+ sourceUrl: elem.sourceUrl,
183
+ fileSize: elem.fileSize,
184
+ file: storedFiles.file,
185
+ });
186
+ }
187
+ if (!newPayload) {
188
+ updateOneMessage(Object.assign(Object.assign({}, failedMessage), { status: MessageStatus.Failed }));
189
+ return;
190
+ }
191
+ messageFileStore.delete(failedMessage.clientMsgID);
192
+ messageFileStore.set(newPayload.clientMsgID, storedFiles);
193
+ const conversationID = (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "";
194
+ await DChatSDK.deleteMessageFromLocalStorage({
195
+ conversationID,
196
+ clientMsgID: failedMessage.clientMsgID,
197
+ });
198
+ removeOneMessage(failedMessage.clientMsgID);
199
+ await sendMessage(Object.assign(Object.assign({}, newPayload), { ex: failedMessage.ex }));
200
+ }, [sendMessage]);
109
201
  const sendTextMessage = useCallback(async ({ plainText, richText, currentSession, }) => {
110
202
  if (!recvID && !groupID)
111
203
  return;
@@ -159,6 +251,10 @@ export const useSendMessage = () => {
159
251
  const imageMessage = await createImageMessageByFile(parsedImage);
160
252
  if (!imageMessage)
161
253
  continue;
254
+ messageFileStore.set(imageMessage.clientMsgID, {
255
+ type: "image",
256
+ file: file,
257
+ });
162
258
  messageList.push(imageMessage);
163
259
  }
164
260
  else if (isVideo) {
@@ -185,10 +281,15 @@ export const useSendMessage = () => {
185
281
  });
186
282
  if (!videoMessage)
187
283
  continue;
284
+ messageFileStore.set(videoMessage.clientMsgID, {
285
+ type: "video",
286
+ videoFile: file,
287
+ snapshotFile: thumbFile,
288
+ });
188
289
  messageList.push(videoMessage);
189
290
  }
190
291
  else if (isDocument) {
191
- const fileMessage = await createFileMessageByFile({
292
+ const fileParams = {
192
293
  filePath: "",
193
294
  fileName: file.name,
194
295
  uuid: uuidv4(),
@@ -196,9 +297,14 @@ export const useSendMessage = () => {
196
297
  fileSize: file.size,
197
298
  fileType: file.type,
198
299
  file: file,
199
- });
300
+ };
301
+ const fileMessage = await createFileMessageByFile(fileParams);
200
302
  if (!fileMessage)
201
303
  continue;
304
+ messageFileStore.set(fileMessage.clientMsgID, {
305
+ type: "file",
306
+ file: file,
307
+ });
202
308
  messageList.push(fileMessage);
203
309
  }
204
310
  }
@@ -224,6 +330,7 @@ export const useSendMessage = () => {
224
330
  return {
225
331
  sendTextMessage,
226
332
  sendMergeMessage,
333
+ resendMessage,
227
334
  };
228
335
  };
229
336
  export const generateExtendMessageInfo = ({ richText, currentSession, }) => {
@@ -74,9 +74,26 @@
74
74
  "create_new_group": "Tạo nhóm CS mới",
75
75
  "open_session": "Mở phiên",
76
76
  "close_session": "Đóng phiên",
77
+ "message_send_failed": "Gửi tin nhắn thất bại, bạn vui lòng <bold>nhấn để gửi lại</bold>",
78
+ "message_send_failed_no_retry": "Gửi tin nhắn thất bại",
79
+ "general_info": "Thông tin chung",
80
+ "member_name": "Tên thành viên",
81
+ "activity_status": "Trạng thái hoạt động",
82
+ "online": "Trực tuyến",
83
+ "offline": "Ngoại tuyến",
84
+ "add_member_button": "Thêm thành viên",
85
+ "member_username": "Tên thành viên",
86
+ "change_status": "Thay đổi trạng thái",
87
+ "change_status_online_to_offline": "<bold>Bạn đang ở trạng thái Trực tuyến</bold>, khi chuyển sang Ngoại tuyến hệ thống sẽ không tự động gửi yêu cầu hỗ trợ mới cho bạn.\nBạn có chắc chắn muốn thay đổi trạng thái?",
88
+ "change_status_offline_to_online": "<bold>Bạn đang ở trạng thái Ngoại tuyến</bold>, khi chuyển sang Trực tuyến hệ thống sẽ tự động thêm bạn vào phiên hỗ trợ.\nBạn có chắc chắn muốn thay đổi trạng thái?",
89
+ "confirm": "Xác nhận",
90
+ "update_status_success": "Cập nhật trạng thái thành công",
91
+ "update_status_failed": "Cập nhật trạng thái thất bại",
92
+ "missing_field": "Thiếu {{field}}",
77
93
  "validation": {
78
94
  "default_message": "Nhập nội dung tin nhắn mặc định",
79
95
  "group_name": "Thiếu tên nhóm",
80
- "group_name_length": "Số ký tự tối đa của tên nhóm là 200"
96
+ "group_name_length": "Số ký tự tối đa của tên nhóm là 200",
97
+ "group_description_length": "Số ký tự đối đa là 200."
81
98
  }
82
- }
99
+ }