@droppii-org/chat-sdk 0.8.1 → 0.8.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.
- package/dist/components/canned-response/cannedTrigger.d.ts +2 -0
- package/dist/components/canned-response/cannedTrigger.d.ts.map +1 -0
- package/dist/components/canned-response/cannedTrigger.js +3 -0
- package/dist/components/canned-response/insertCannedContent.js +2 -2
- package/dist/components/conversation/ConversationByGroupItem.js +1 -1
- package/dist/components/conversation/ConversationBySessionItem.js +4 -4
- package/dist/components/conversation/internalNotePreview.d.ts +1 -1
- package/dist/components/conversation/internalNotePreview.d.ts.map +1 -1
- package/dist/components/conversation/internalNotePreview.js +22 -3
- package/dist/components/conversation/rowTime.d.ts +2 -2
- package/dist/components/conversation/rowTime.d.ts.map +1 -1
- package/dist/components/conversation/rowTime.js +2 -4
- package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationDetailInput.js +8 -3
- package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationMessageList.js +6 -3
- package/dist/components/conversation-detail/bot/BotMenuSheet.d.ts.map +1 -1
- package/dist/components/conversation-detail/bot/BotMenuSheet.js +1 -1
- package/dist/components/conversation-inbox/ConversationInboxList.d.ts.map +1 -1
- package/dist/components/conversation-inbox/ConversationInboxList.js +4 -0
- package/dist/components/conversation-inbox/InboxBootStatus.d.ts.map +1 -1
- package/dist/components/conversation-inbox/InboxBootStatus.js +10 -2
- package/dist/components/group/CreateGroupForm.d.ts.map +1 -1
- package/dist/components/group/CreateGroupForm.js +2 -1
- package/dist/components/group/CreateGroupFormCrmView.d.ts.map +1 -1
- package/dist/components/group/CreateGroupFormCrmView.js +1 -1
- package/dist/components/group/GroupMemberPickerCrm.d.ts.map +1 -1
- package/dist/components/group/GroupMemberPickerCrm.js +1 -1
- package/dist/components/media-collection/FileCollection.d.ts.map +1 -1
- package/dist/components/media-collection/FileCollection.js +2 -1
- package/dist/components/media-collection/ImageCollection.d.ts.map +1 -1
- package/dist/components/media-collection/ImageCollection.js +2 -1
- package/dist/components/media-collection/LinkCollection.d.ts.map +1 -1
- package/dist/components/media-collection/LinkCollection.js +14 -26
- package/dist/components/media-collection/VideoCollection.d.ts.map +1 -1
- package/dist/components/media-collection/VideoCollection.js +2 -1
- package/dist/components/media-resources/ResourceScrollContainer.d.ts.map +1 -1
- package/dist/components/media-resources/ResourceScrollContainer.js +34 -2
- package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
- package/dist/components/message/GroupMembersDrawer.js +2 -3
- package/dist/components/message/MessageList.d.ts.map +1 -1
- package/dist/components/message/MessageList.js +15 -4
- package/dist/components/message/footer/ActionBar.d.ts.map +1 -1
- package/dist/components/message/footer/ActionBar.js +10 -44
- package/dist/components/message/footer/CannedResponsePlugin.d.ts.map +1 -1
- package/dist/components/message/footer/CannedResponsePlugin.js +3 -2
- package/dist/components/message/footer/EnterHandler.d.ts.map +1 -1
- package/dist/components/message/footer/EnterHandler.js +10 -47
- package/dist/components/message/footer/FilePreview.d.ts.map +1 -1
- package/dist/components/message/footer/FilePreview.js +2 -1
- package/dist/components/message/footer/LinkInputPreview.d.ts +1 -1
- package/dist/components/message/footer/LinkInputPreview.d.ts.map +1 -1
- package/dist/components/message/footer/LinkInputPreview.js +8 -1
- package/dist/components/message/footer/MediaUploadButtons.d.ts.map +1 -1
- package/dist/components/message/footer/MediaUploadButtons.js +4 -7
- package/dist/components/message/footer/index.d.ts.map +1 -1
- package/dist/components/message/footer/index.js +57 -16
- package/dist/components/message/footer/mention/useMentionOptions.d.ts.map +1 -1
- package/dist/components/message/footer/mention/useMentionOptions.js +2 -6
- package/dist/components/message/footer/resetEditorAfterSend.d.ts +7 -0
- package/dist/components/message/footer/resetEditorAfterSend.d.ts.map +1 -0
- package/dist/components/message/footer/resetEditorAfterSend.js +62 -0
- package/dist/components/message/groupMemberMenuCrm.d.ts +4 -4
- package/dist/components/message/groupMemberMenuCrm.d.ts.map +1 -1
- package/dist/components/message/groupMemberMenuCrm.js +73 -60
- package/dist/components/message/item/SystemLogMessage.d.ts.map +1 -1
- package/dist/components/message/item/SystemLogMessage.js +5 -2
- package/dist/components/search-conversation/SearchConversationAsGroups.js +1 -1
- package/dist/components/search-conversation/SearchConversationAsMessages.js +1 -1
- package/dist/components/search-conversation/SearchConversationMyInbox.js +1 -1
- package/dist/components/thread/GroupInfoBodyCrm.d.ts.map +1 -1
- package/dist/components/thread/GroupInfoBodyCrm.js +2 -2
- package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
- package/dist/components/thread/GroupThreadInfo.js +2 -6
- package/dist/components/thread/ManualAssignPopover.d.ts.map +1 -1
- package/dist/components/thread/ManualAssignPopover.js +6 -8
- package/dist/components/thread/SessionDetailCard.d.ts.map +1 -1
- package/dist/components/thread/SessionDetailCard.js +3 -3
- package/dist/components/thread/SessionSection.d.ts.map +1 -1
- package/dist/components/thread/SessionSection.js +3 -9
- package/dist/components/thread/SessionTagPicker.d.ts +3 -1
- package/dist/components/thread/SessionTagPicker.d.ts.map +1 -1
- package/dist/components/thread/SessionTagPicker.js +2 -2
- package/dist/components/thread/sessionDateFormat.d.ts +2 -0
- package/dist/components/thread/sessionDateFormat.d.ts.map +1 -1
- package/dist/components/thread/sessionDateFormat.js +6 -0
- package/dist/constants/dateFormat.d.ts +2 -0
- package/dist/constants/dateFormat.d.ts.map +1 -0
- package/dist/constants/dateFormat.js +2 -0
- package/dist/hooks/common/useFetchUrlMetadata.d.ts +1 -0
- package/dist/hooks/common/useFetchUrlMetadata.d.ts.map +1 -1
- package/dist/hooks/conversation/useBotFlow.d.ts.map +1 -1
- package/dist/hooks/conversation/useBotFlow.js +64 -12
- package/dist/hooks/conversation/useCloseBotSession.d.ts +2 -0
- package/dist/hooks/conversation/useCloseBotSession.d.ts.map +1 -1
- package/dist/hooks/conversation/useCloseBotSession.js +17 -1
- package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
- package/dist/hooks/global/useGlobalEvent.js +148 -16
- package/dist/hooks/global/useLoginHealth.d.ts +3 -0
- package/dist/hooks/global/useLoginHealth.d.ts.map +1 -0
- package/dist/hooks/global/useLoginHealth.js +55 -0
- package/dist/hooks/group/useCreateGroup.d.ts.map +1 -1
- package/dist/hooks/group/useCreateGroup.js +75 -6
- package/dist/hooks/init/useChatBootPhase.d.ts +2 -0
- package/dist/hooks/init/useChatBootPhase.d.ts.map +1 -1
- package/dist/hooks/init/useChatBootPhase.js +11 -6
- package/dist/hooks/message/useMessage.d.ts.map +1 -1
- package/dist/hooks/message/useMessage.js +97 -28
- package/dist/hooks/message/usePinnedMessages.d.ts.map +1 -1
- package/dist/hooks/message/usePinnedMessages.js +13 -2
- package/dist/hooks/message/useRevokeMessage.d.ts.map +1 -1
- package/dist/hooks/message/useRevokeMessage.js +25 -0
- package/dist/hooks/message/useSendMessage.d.ts +15 -12
- package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
- package/dist/hooks/message/useSendMessage.js +199 -133
- package/dist/hooks/search/useSearchMessage.d.ts.map +1 -1
- package/dist/hooks/search/useSearchMessage.js +57 -3
- package/dist/hooks/session/useConversationSessionState.d.ts.map +1 -1
- package/dist/hooks/session/useConversationSessionState.js +4 -1
- package/dist/hooks/session/useSessionLatestPreview.d.ts.map +1 -1
- package/dist/hooks/session/useSessionLatestPreview.js +34 -4
- package/dist/hooks/user/useAuth.d.ts +1 -1
- package/dist/hooks/user/useAuth.d.ts.map +1 -1
- package/dist/hooks/user/useAuth.js +31 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/locales/vi/boot.json +5 -1
- package/dist/locales/vi/common.json +34 -15
- package/dist/locales/vi/conversation-inbox.json +2 -1
- package/dist/screens/conversation-detail/index.d.ts.map +1 -1
- package/dist/screens/conversation-detail/index.js +6 -0
- package/dist/screens/desk-message/DeskBootCard.d.ts.map +1 -1
- package/dist/screens/desk-message/DeskBootCard.js +6 -4
- package/dist/store/boot.d.ts +5 -0
- package/dist/store/boot.d.ts.map +1 -1
- package/dist/store/boot.js +11 -1
- package/dist/store/conversation.d.ts.map +1 -1
- package/dist/store/conversation.js +5 -1
- package/dist/store/type.d.ts +3 -1
- package/dist/store/type.d.ts.map +1 -1
- package/dist/styles/global.css +1 -1
- package/dist/types/chat.d.ts +1 -1
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/utils/common.d.ts.map +1 -1
- package/dist/utils/common.js +4 -1
- package/dist/utils/fileValidation.d.ts +7 -0
- package/dist/utils/fileValidation.d.ts.map +1 -1
- package/dist/utils/fileValidation.js +37 -13
- package/dist/utils/latestMessagePreviewable.d.ts +2 -0
- package/dist/utils/latestMessagePreviewable.d.ts.map +1 -0
- package/dist/utils/latestMessagePreviewable.js +18 -0
- package/dist/utils/mentionMembers.d.ts +3 -0
- package/dist/utils/mentionMembers.d.ts.map +1 -0
- package/dist/utils/mentionMembers.js +29 -0
- package/dist/utils/messageLog.d.ts +2 -2
- package/dist/utils/messageLog.d.ts.map +1 -1
- package/dist/utils/messageLog.js +129 -40
- package/dist/utils/notifiableMessage.d.ts +7 -0
- package/dist/utils/notifiableMessage.d.ts.map +1 -0
- package/dist/utils/notifiableMessage.js +26 -0
- package/dist/utils/withSdkTimeout.d.ts +11 -0
- package/dist/utils/withSdkTimeout.d.ts.map +1 -0
- package/dist/utils/withSdkTimeout.js +30 -0
- package/package.json +1 -1
|
@@ -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,
|
|
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
|
-
|
|
140
|
-
|
|
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 &&
|
|
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,
|
|
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":"
|
|
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 { $
|
|
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 {
|
|
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
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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":"
|
|
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 (
|
|
25
|
-
const match = before.match(
|
|
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":"
|
|
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, $
|
|
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
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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,
|
|
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)]" })) }),
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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"}
|
|
@@ -25,7 +25,7 @@ import CannedResponse from "../../../components/canned-response";
|
|
|
25
25
|
import CannedResponsePanel from "../../../components/canned-response/CannedResponsePanel";
|
|
26
26
|
import CrmComposerCard from "./CrmComposerCard";
|
|
27
27
|
import { CannedResponseTriggerPlugin } from "./CannedResponsePlugin";
|
|
28
|
-
import { useBoolean } from "ahooks";
|
|
28
|
+
import { useBoolean, useLatest } from "ahooks";
|
|
29
29
|
import useAuthStore from "../../../store/auth";
|
|
30
30
|
import useConversationStore from "../../../store/conversation";
|
|
31
31
|
import { useCurrentUserAccountType } from "../../../hooks/user/useCurrentUserAccountType";
|
|
@@ -142,6 +142,10 @@ const MessageFooterProvider = ({ currentSession, openCreateCannedModal, }) => {
|
|
|
142
142
|
};
|
|
143
143
|
}, [showLinkPreview, previewUrl, previewMetadata]);
|
|
144
144
|
const [isSessionClosedModalOpen, { setTrue: openSessionClosedModal, setFalse: closeSessionClosedModal },] = useBoolean(false);
|
|
145
|
+
// Conversations whose message is still being built. Keyed per thread so a send in
|
|
146
|
+
// one thread neither blocks nor clears the composer of the thread opened next.
|
|
147
|
+
const buildingConversationsRef = useRef(new Set());
|
|
148
|
+
const currentConversationIdRef = useLatest(conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID);
|
|
145
149
|
const pendingMessageRef = useRef(null);
|
|
146
150
|
const { mutateAsync: createSession, isPending: isCreatingSession } = useCreateSession();
|
|
147
151
|
const { mutateAsync: reopenSession, isPending: isReopeningSession } = useReopenSession();
|
|
@@ -163,6 +167,7 @@ const MessageFooterProvider = ({ currentSession, openCreateCannedModal, }) => {
|
|
|
163
167
|
? t("enter_internal_message")
|
|
164
168
|
: t("enter_message");
|
|
165
169
|
const onSendMessage = useCallback(({ plainText, richText, type, isInternal, mentions, }) => {
|
|
170
|
+
var _a;
|
|
166
171
|
if ((currentSession === null || currentSession === void 0 ? void 0 : currentSession.isClosed) && !isInternal) {
|
|
167
172
|
pendingMessageRef.current = {
|
|
168
173
|
plainText,
|
|
@@ -172,34 +177,61 @@ const MessageFooterProvider = ({ currentSession, openCreateCannedModal, }) => {
|
|
|
172
177
|
files: [...listUploadFiles],
|
|
173
178
|
urlMetadata: resolveUrlMetadata(),
|
|
174
179
|
mentions,
|
|
180
|
+
conversationId: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID,
|
|
175
181
|
};
|
|
176
182
|
openSessionClosedModal();
|
|
177
183
|
return false;
|
|
178
184
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
// A second Enter while the first message is still being built would send it twice.
|
|
186
|
+
const conversationId = (_a = conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) !== null && _a !== void 0 ? _a : "";
|
|
187
|
+
if (buildingConversationsRef.current.has(conversationId))
|
|
188
|
+
return false;
|
|
189
|
+
buildingConversationsRef.current.add(conversationId);
|
|
190
|
+
const sentFileUids = new Set(listUploadFiles.map((file) => file.uid));
|
|
191
|
+
// The composer is cleared by the caller only when this resolves true, so a failed
|
|
192
|
+
// build (hung or rejected core call) keeps the text and files for another try.
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
const settle = (shown) => {
|
|
195
|
+
buildingConversationsRef.current.delete(conversationId);
|
|
196
|
+
if (!shown)
|
|
197
|
+
toastMessage.error(t("message_send_failed_no_retry"));
|
|
198
|
+
if (shown) {
|
|
199
|
+
// The upload list is composer-wide: even after a thread switch the sent files
|
|
200
|
+
// must go, or they would ride along with the next thread's message.
|
|
201
|
+
setListUploadFiles((files) => files.filter((file) => !sentFileUids.has(file.uid)));
|
|
202
|
+
}
|
|
203
|
+
// After a thread switch the composer may still hold the sent text; the caller
|
|
204
|
+
// clears it only if it is unchanged, so a different draft is never touched.
|
|
205
|
+
resolve(shown);
|
|
206
|
+
};
|
|
207
|
+
void sendMergeMessage({
|
|
208
|
+
plainText,
|
|
209
|
+
richText,
|
|
210
|
+
files: listUploadFiles,
|
|
211
|
+
currentSession,
|
|
212
|
+
isInternal,
|
|
213
|
+
urlMetadata: resolveUrlMetadata(),
|
|
214
|
+
mentions,
|
|
215
|
+
onBuilt: settle,
|
|
216
|
+
});
|
|
187
217
|
});
|
|
188
|
-
setListUploadFiles([]);
|
|
189
|
-
return true;
|
|
190
218
|
}, [
|
|
219
|
+
t,
|
|
191
220
|
sendMergeMessage,
|
|
192
221
|
listUploadFiles,
|
|
193
222
|
currentSession,
|
|
194
223
|
openSessionClosedModal,
|
|
195
224
|
resolveUrlMetadata,
|
|
225
|
+
conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID,
|
|
226
|
+
currentConversationIdRef,
|
|
196
227
|
]);
|
|
197
228
|
const executePendingSend = useCallback((newSession) => {
|
|
198
229
|
const pending = pendingMessageRef.current;
|
|
199
230
|
if (!pending)
|
|
200
231
|
return;
|
|
201
232
|
const sessionToUse = newSession || currentSession;
|
|
202
|
-
|
|
233
|
+
pendingMessageRef.current = null;
|
|
234
|
+
void sendMergeMessage({
|
|
203
235
|
plainText: pending.plainText,
|
|
204
236
|
richText: pending.richText,
|
|
205
237
|
files: pending.files,
|
|
@@ -207,11 +239,20 @@ const MessageFooterProvider = ({ currentSession, openCreateCannedModal, }) => {
|
|
|
207
239
|
isInternal: pending.isInternal,
|
|
208
240
|
urlMetadata: pending.urlMetadata,
|
|
209
241
|
mentions: pending.mentions,
|
|
242
|
+
onBuilt: (shown) => {
|
|
243
|
+
if (!shown) {
|
|
244
|
+
toastMessage.error(t("message_send_failed_no_retry"));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const sentUids = new Set(pending.files.map((file) => file.uid));
|
|
248
|
+
setListUploadFiles((files) => files.filter((file) => !sentUids.has(file.uid)));
|
|
249
|
+
// Only the thread the message was written in has a text composer to clear.
|
|
250
|
+
if (currentConversationIdRef.current !== pending.conversationId)
|
|
251
|
+
return;
|
|
252
|
+
emit("CLEAR_COMPOSER");
|
|
253
|
+
},
|
|
210
254
|
});
|
|
211
|
-
|
|
212
|
-
pendingMessageRef.current = null;
|
|
213
|
-
emit("CLEAR_COMPOSER");
|
|
214
|
-
}, [sendMergeMessage, currentSession]);
|
|
255
|
+
}, [t, sendMergeMessage, currentSession, currentConversationIdRef]);
|
|
215
256
|
const handleSessionAction = useCallback(async (action) => {
|
|
216
257
|
const conversationId = conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID;
|
|
217
258
|
if (!conversationId)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMentionOptions.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/useMentionOptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useMentionOptions.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/useMentionOptions.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAoB7C,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,EAC3B,OAAO,MAAM,KACZ,aAAa,EAwDf,CAAC"}
|
|
@@ -2,7 +2,6 @@ import { useEffect, useMemo } from "react";
|
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
3
|
import { useGroupMembers } from "../../../../hooks/group/useGroupMembers";
|
|
4
4
|
import { useSearchGroupMembers } from "../../../../hooks/group/useSearchGroupMembers";
|
|
5
|
-
import useAuthStore from "../../../../store/auth";
|
|
6
5
|
export const ALL_MEMBERS_USER_ID = "__all__";
|
|
7
6
|
// Typing a name loads a big group's remaining members up to this many.
|
|
8
7
|
const MENTION_MEMBER_LOAD_LIMIT = 10000;
|
|
@@ -27,7 +26,6 @@ export const useMentionOptions = (groupID, query) => {
|
|
|
27
26
|
// Core keeps only part of a big group locally, so its search alone misses
|
|
28
27
|
// members; typing a name also loads the rest, merged in as pages arrive.
|
|
29
28
|
const { results: searchResults } = useSearchGroupMembers(groupID, hasMore ? query : "");
|
|
30
|
-
const currentUserID = useAuthStore((state) => state.userID);
|
|
31
29
|
const hasKeyword = query.trim().length > 0;
|
|
32
30
|
useEffect(() => {
|
|
33
31
|
if (!hasKeyword || !hasMore || isLoadingMore)
|
|
@@ -42,10 +40,8 @@ export const useMentionOptions = (groupID, query) => {
|
|
|
42
40
|
const candidates = keyword
|
|
43
41
|
? [...members, ...searchResults.filter((member) => !loadedIDs.has(member.userID))]
|
|
44
42
|
: members;
|
|
45
|
-
//
|
|
46
|
-
// knows who they are — so the current user is filtered out entirely.
|
|
43
|
+
// The sender stays in the list: the spec lists every group member.
|
|
47
44
|
const memberOptions = candidates
|
|
48
|
-
.filter((member) => member.userID !== currentUserID)
|
|
49
45
|
.map((member) => ({
|
|
50
46
|
userID: member.userID,
|
|
51
47
|
displayName: member.nickname || member.userID,
|
|
@@ -69,5 +65,5 @@ export const useMentionOptions = (groupID, query) => {
|
|
|
69
65
|
};
|
|
70
66
|
const matchesAll = !keyword || allOption.displayName.toLowerCase().includes(keyword);
|
|
71
67
|
return matchesAll ? [allOption, ...memberOptions] : memberOptions;
|
|
72
|
-
}, [members, searchResults, query, t
|
|
68
|
+
}, [members, searchResults, query, t]);
|
|
73
69
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LexicalEditor } from "lexical";
|
|
2
|
+
export declare const resetEditorIfUnchanged: (editor: LexicalEditor, sent: {
|
|
3
|
+
plainText: string;
|
|
4
|
+
richText: string;
|
|
5
|
+
}) => void;
|
|
6
|
+
export declare const resetEditorAfterSend: (editor: LexicalEditor) => void;
|
|
7
|
+
//# sourceMappingURL=resetEditorAfterSend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resetEditorAfterSend.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/resetEditorAfterSend.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAoE,MAAM,SAAS,CAAC;AAW1G,eAAO,MAAM,sBAAsB,GACjC,QAAQ,aAAa,EACrB,MAAM;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,SAU9C,CAAC;AAIF,eAAO,MAAM,oBAAoB,GAAI,QAAQ,aAAa,SAgDzD,CAAC"}
|