@droppii-org/chat-sdk 0.4.7 → 0.4.9

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 (53) hide show
  1. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  2. package/dist/components/conversation/DeskConversationList.js +8 -2
  3. package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
  4. package/dist/components/conversation-detail/ConversationDetailInput.js +21 -2
  5. package/dist/components/conversation-detail/mention/BizMentionMenu.d.ts +3 -1
  6. package/dist/components/conversation-detail/mention/BizMentionMenu.d.ts.map +1 -1
  7. package/dist/components/conversation-detail/mention/BizMentionMenu.js +4 -2
  8. package/dist/components/conversation-detail/mention/useMentionTokens.d.ts +1 -0
  9. package/dist/components/conversation-detail/mention/useMentionTokens.d.ts.map +1 -1
  10. package/dist/components/conversation-detail/mention/useMentionTokens.js +4 -0
  11. package/dist/components/message/MessageList.d.ts.map +1 -1
  12. package/dist/components/message/MessageList.js +3 -1
  13. package/dist/components/message/footer/index.d.ts.map +1 -1
  14. package/dist/components/message/footer/index.js +2 -2
  15. package/dist/components/message/footer/mention/MentionMenu.d.ts +1 -0
  16. package/dist/components/message/footer/mention/MentionMenu.d.ts.map +1 -1
  17. package/dist/components/message/footer/mention/MentionMenu.js +76 -6
  18. package/dist/components/message/footer/mention/MentionNode.d.ts +1 -0
  19. package/dist/components/message/footer/mention/MentionNode.d.ts.map +1 -1
  20. package/dist/components/message/footer/mention/MentionNode.js +15 -0
  21. package/dist/components/message/footer/mention/useMentionOptions.d.ts.map +1 -1
  22. package/dist/components/message/footer/mention/useMentionOptions.js +6 -3
  23. package/dist/components/message/item/SessionEventMessage.d.ts +7 -0
  24. package/dist/components/message/item/SessionEventMessage.d.ts.map +1 -0
  25. package/dist/components/message/item/SessionEventMessage.js +12 -0
  26. package/dist/components/message/item/index.d.ts.map +1 -1
  27. package/dist/components/message/item/index.js +10 -2
  28. package/dist/components/search-conversation/index.d.ts.map +1 -1
  29. package/dist/components/search-conversation/index.js +20 -2
  30. package/dist/components/search-conversation/item/SearchItemAsGroup.d.ts.map +1 -1
  31. package/dist/components/search-conversation/item/SearchItemAsGroup.js +2 -1
  32. package/dist/components/search-conversation/item/SearchItemAsMessage.d.ts.map +1 -1
  33. package/dist/components/search-conversation/item/SearchItemAsMessage.js +4 -1
  34. package/dist/hooks/conversation/useResolveConversation.d.ts.map +1 -1
  35. package/dist/hooks/conversation/useResolveConversation.js +2 -1
  36. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  37. package/dist/hooks/global/useGlobalEvent.js +10 -1
  38. package/dist/hooks/group/useLeaveGroupAction.d.ts.map +1 -1
  39. package/dist/hooks/group/useLeaveGroupAction.js +47 -3
  40. package/dist/hooks/search/useSearchMessage.d.ts.map +1 -1
  41. package/dist/hooks/search/useSearchMessage.js +11 -1
  42. package/dist/locales/vi/common.json +5 -4
  43. package/dist/store/conversation.d.ts.map +1 -1
  44. package/dist/store/conversation.js +22 -9
  45. package/dist/store/type.d.ts +6 -2
  46. package/dist/store/type.d.ts.map +1 -1
  47. package/dist/styles/global.css +1 -1
  48. package/dist/utils/common.d.ts.map +1 -1
  49. package/dist/utils/common.js +7 -5
  50. package/dist/utils/messageLog.d.ts +4 -0
  51. package/dist/utils/messageLog.d.ts.map +1 -1
  52. package/dist/utils/messageLog.js +31 -2
  53. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAqUzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CA2UzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -90,8 +90,12 @@ const DeskConversationList = () => {
90
90
  }
91
91
  setSelectedConversationId(threadId);
92
92
  const selectedConversation = conversationList.find((conv) => conv.conversationID === threadId);
93
+ // threadId can come from a notification/stale search click — force a
94
+ // fresh membership check instead of trusting the groupID cache.
93
95
  if (selectedConversation) {
94
- setConversationData(selectedConversation);
96
+ setConversationData(selectedConversation, undefined, {
97
+ forceRevalidate: true,
98
+ });
95
99
  }
96
100
  else {
97
101
  DChatSDK.getMultipleConversation([threadId]).then((res) => {
@@ -99,7 +103,9 @@ const DeskConversationList = () => {
99
103
  // (e.g. the user was removed from that group mid-flight) — only
100
104
  // apply the result if the URL still points at this conversation.
101
105
  if (res.data.length > 0 && searchParams.get("threadId") === threadId) {
102
- setConversationData(res.data[0]);
106
+ setConversationData(res.data[0], undefined, {
107
+ forceRevalidate: true,
108
+ });
103
109
  updateConversationList(res.data, "filter");
104
110
  }
105
111
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAiB/D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,QAAA,MAAM,uBAAuB,GAAI,4EAM9B,4BAA4B,4CA0lB9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAkB/D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,QAAA,MAAM,uBAAuB,GAAI,4EAM9B,4BAA4B,4CAynB9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -26,6 +26,7 @@ import { buildMentionPayload } from "./mention/buildMentionPayload";
26
26
  const MAX_LINES = 5;
27
27
  const CONTAINER_MIN_HEIGHT = 48;
28
28
  const FALLBACK_LINE_HEIGHT = 25.6;
29
+ const MAX_MENTIONED_USERS = 10;
29
30
  const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, }) => {
30
31
  var _a;
31
32
  const { t } = useTranslation("conversation-inbox");
@@ -62,10 +63,22 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
62
63
  const mentionTrigger = (rawMentionTrigger === null || rawMentionTrigger === void 0 ? void 0 : rawMentionTrigger.triggerStart) === dismissedTriggerStart
63
64
  ? null
64
65
  : rawMentionTrigger;
65
- const mentionOptions = useMentionOptions(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID, (_a = mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.query) !== null && _a !== void 0 ? _a : "");
66
+ const rawMentionOptions = useMentionOptions(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID, (_a = mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.query) !== null && _a !== void 0 ? _a : "");
66
67
  const isMentionMenuOpen = mentionTrigger !== null;
67
68
  const [selectedMentionIndex, setSelectedMentionIndex] = useState(0);
68
69
  const { tokens: mentionTokens, addToken, onValueChange, resetTokens } = useMentionTokens();
70
+ // "Tất cả" doesn't count against the per-user cap and stays selectable;
71
+ // only specific-member options are hidden once the limit is hit.
72
+ const mentionedUserIDs = useMemo(() => new Set(mentionTokens.filter((token) => !token.isAtAll).map((token) => token.userID)), [mentionTokens]);
73
+ const mentionedUsersCount = mentionedUserIDs.size;
74
+ const mentionLimitReached = mentionedUsersCount >= MAX_MENTIONED_USERS;
75
+ // Already-tagged users don't need to appear again in the same draft.
76
+ const mentionOptions = useMemo(() => {
77
+ const notYetMentioned = rawMentionOptions.filter((option) => option.isAtAll || !mentionedUserIDs.has(option.userID));
78
+ return mentionLimitReached
79
+ ? notYetMentioned.filter((option) => option.isAtAll)
80
+ : notYetMentioned;
81
+ }, [rawMentionOptions, mentionedUserIDs, mentionLimitReached]);
69
82
  const [mentionMenuPosition, setMentionMenuPosition] = useState(null);
70
83
  const previewUrl = useMemo(() => {
71
84
  const urls = extractLinkUrls(value);
@@ -115,6 +128,12 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
115
128
  useEffect(() => {
116
129
  setSelectedMentionIndex(0);
117
130
  }, [mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.triggerStart, mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.query]);
131
+ // The cap can shrink mentionOptions independent of the trigger identity
132
+ // (reaching the limit filters out non-"Tất cả" entries) — clamp so a
133
+ // stale index left over from a longer list doesn't point out of range.
134
+ useEffect(() => {
135
+ setSelectedMentionIndex((prev) => prev >= mentionOptions.length ? Math.max(0, mentionOptions.length - 1) : prev);
136
+ }, [mentionOptions.length]);
118
137
  useEffect(() => {
119
138
  const textarea = textareaRef.current;
120
139
  if (!isMentionMenuOpen || !textarea) {
@@ -396,6 +415,6 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
396
415
  : "border-gray-50"), style: { minHeight: CONTAINER_MIN_HEIGHT }, children: _jsxs("div", { className: "flex min-w-0 flex-[1_0_0] items-center gap-2 self-stretch px-3 py-1", children: [onOpenBotMenu && (_jsx("button", { type: "button", onClick: onOpenBotMenu, "aria-label": t("thread_detail.bot.menu.open_menu"), "data-testid": "conversation-detail-open-bot-menu", className: "flex shrink-0 items-center justify-center", children: _jsx(Icon, { icon: "chat-suggest-o", size: 24, className: "text-gray-600" }) })), _jsx("textarea", { ref: textareaRef, value: value, rows: 1, onChange: (event) => {
397
416
  var _a;
398
417
  return handleChange(event.target.value, (_a = event.target.selectionStart) !== null && _a !== void 0 ? _a : event.target.value.length);
399
- }, onSelect: handleSelectionChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t("thread_detail.input_placeholder"), "data-testid": "conversation-detail-message-input", className: "conversation-message-input-scrollbar min-h-0 min-w-0 flex-1 resize-none appearance-none border-0 bg-transparent p-0 text-[16px] leading-[1.6] tracking-[0.16px] text-black outline-none ring-0 placeholder:text-gray-400 focus:border-0 focus:outline-none focus:ring-0" }), isMentionMenuOpen && mentionMenuPosition && (_jsx(BizMentionMenu, { options: mentionOptions, selectedIndex: selectedMentionIndex, onSelect: selectMentionOption, onHoverIndex: setSelectedMentionIndex, position: mentionMenuPosition, visible: true }))] }) }), _jsx("div", { className: "flex shrink-0 items-center pb-1", children: _jsx("button", { type: "button", onClick: () => void handleSend(), disabled: !canSend, "data-testid": "conversation-detail-send", className: clsx("flex h-10 w-10 items-center justify-center rounded-full", canSend ? "bg-chat-send" : "bg-chat-send opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(ConversationMoreOptionPanel, { disabled: !isConversationReady, options: moreOptions, onTriggerMediaInput: () => { var _a; return (_a = mediaInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, onTriggerDocumentInput: () => { var _a; return (_a = documentInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } }))] }));
418
+ }, onSelect: handleSelectionChange, onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t("thread_detail.input_placeholder"), "data-testid": "conversation-detail-message-input", className: "conversation-message-input-scrollbar min-h-0 min-w-0 flex-1 resize-none appearance-none border-0 bg-transparent p-0 text-[16px] leading-[1.6] tracking-[0.16px] text-black outline-none ring-0 placeholder:text-gray-400 focus:border-0 focus:outline-none focus:ring-0" }), isMentionMenuOpen && mentionMenuPosition && (_jsx(BizMentionMenu, { options: mentionOptions, selectedIndex: selectedMentionIndex, onSelect: selectMentionOption, onHoverIndex: setSelectedMentionIndex, position: mentionMenuPosition, limitReached: mentionLimitReached, limitMax: MAX_MENTIONED_USERS, visible: true }))] }) }), _jsx("div", { className: "flex shrink-0 items-center pb-1", children: _jsx("button", { type: "button", onClick: () => void handleSend(), disabled: !canSend, "data-testid": "conversation-detail-send", className: clsx("flex h-10 w-10 items-center justify-center rounded-full", canSend ? "bg-chat-send" : "bg-chat-send opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(ConversationMoreOptionPanel, { disabled: !isConversationReady, options: moreOptions, onTriggerMediaInput: () => { var _a; return (_a = mediaInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, onTriggerDocumentInput: () => { var _a; return (_a = documentInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } }))] }));
400
419
  };
401
420
  export default ConversationDetailInput;
@@ -11,7 +11,9 @@ export interface BizMentionMenuProps {
11
11
  direction: "up" | "down";
12
12
  };
13
13
  visible: boolean;
14
+ limitReached?: boolean;
15
+ limitMax?: number;
14
16
  }
15
- declare const BizMentionMenu: ({ options, selectedIndex, onSelect, onHoverIndex, position, visible, }: BizMentionMenuProps) => import("react/jsx-runtime").JSX.Element | null;
17
+ declare const BizMentionMenu: ({ options, selectedIndex, onSelect, onHoverIndex, position, visible, limitReached, limitMax, }: BizMentionMenuProps) => import("react/jsx-runtime").JSX.Element | null;
16
18
  export default BizMentionMenu;
17
19
  //# sourceMappingURL=BizMentionMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BizMentionMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/mention/BizMentionMenu.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAE3F,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE;QACR,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;KAC1B,CAAC;IACF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,QAAA,MAAM,cAAc,GAAI,wEAOrB,mBAAmB,mDA2DrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"BizMentionMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/mention/BizMentionMenu.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uDAAuD,CAAC;AAE3F,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE;QACR,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;KAC1B,CAAC;IACF,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,QAAA,MAAM,cAAc,GAAI,gGASrB,mBAAmB,mDA6DrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslation } from "react-i18next";
4
4
  import { Avatar } from "antd";
5
- const BizMentionMenu = ({ options, selectedIndex, onSelect, onHoverIndex, position, visible, }) => {
5
+ const BizMentionMenu = ({ options, selectedIndex, onSelect, onHoverIndex, position, visible, limitReached, limitMax = 10, }) => {
6
6
  const { t } = useTranslation();
7
7
  if (!visible)
8
8
  return null;
@@ -15,6 +15,8 @@ const BizMentionMenu = ({ options, selectedIndex, onSelect, onHoverIndex, positi
15
15
  const anchorStyle = position.direction === "up"
16
16
  ? { bottom: position.bottom, left: position.left }
17
17
  : { top: position.top, left: position.left };
18
- return (_jsxs("div", { "data-cp-name": "BizMentionMenu", "data-testid": "biz-mention-menu", className: "fixed z-50 max-h-[220px] w-[260px] overflow-y-auto rounded-md border border-gray-200 bg-white py-1 shadow-lg", style: anchorStyle, children: [_jsx("div", { className: "px-3 py-1 text-xs font-medium text-gray-400", children: t("mention.menu_title") }), options.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-gray-500", children: t("mention.no_result") })) : (options.map((option, index) => (_jsxs("button", { type: "button", "data-selected": index === selectedIndex, className: `flex w-full items-center gap-2 px-3 py-2 text-left text-sm ${index === selectedIndex ? "bg-blue-50" : "hover:bg-gray-50"}`, onMouseEnter: () => onHoverIndex(index), onClick: () => onSelect(option), children: [option.isAtAll ? (_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-500", children: "@" })) : (_jsx(Avatar, { size: 24, src: option.faceURL, children: option.displayName.charAt(0) })), _jsx("span", { className: "min-w-0 flex-1 truncate", children: option.displayName })] }, option.userID))))] }));
18
+ return (_jsxs("div", { "data-cp-name": "BizMentionMenu", "data-testid": "biz-mention-menu", className: "fixed z-50 max-h-[220px] w-[260px] overflow-y-auto rounded-md border border-gray-200 bg-white py-1 shadow-lg", style: anchorStyle, children: [_jsx("div", { className: "px-3 py-1 text-xs font-medium text-gray-400", children: t("mention.menu_title") }), options.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-gray-500", children: limitReached
19
+ ? t("mention.limit_reached", { max: limitMax })
20
+ : t("mention.no_result") })) : (options.map((option, index) => (_jsxs("button", { type: "button", "data-selected": index === selectedIndex, className: `flex w-full items-center gap-2 px-3 py-2 text-left text-sm ${index === selectedIndex ? "bg-blue-50" : "hover:bg-gray-50"}`, onMouseEnter: () => onHoverIndex(index), onClick: () => onSelect(option), children: [option.isAtAll ? (_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-500", children: "@" })) : (_jsx(Avatar, { size: 24, src: option.faceURL, children: option.displayName.charAt(0) })), _jsx("span", { className: "min-w-0 flex-1 truncate", children: option.displayName })] }, option.userID))))] }));
19
21
  };
20
22
  export default BizMentionMenu;
@@ -10,6 +10,7 @@ export interface EditRange {
10
10
  deletedLength: number;
11
11
  insertedLength: number;
12
12
  }
13
+ export declare const countMentionedUsers: (tokens: MentionToken[]) => number;
13
14
  export declare const computeEditRange: (oldValue: string, newValue: string) => EditRange;
14
15
  export interface UseMentionTokensResult {
15
16
  tokens: MentionToken[];
@@ -1 +1 @@
1
- {"version":3,"file":"useMentionTokens.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/mention/useMentionTokens.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAMD,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,SA4BF,CAAC;AA6BF,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,gBAAgB,QAAO,sBAqBnC,CAAC"}
1
+ {"version":3,"file":"useMentionTokens.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/mention/useMentionTokens.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB;AAID,eAAO,MAAM,mBAAmB,GAAI,QAAQ,YAAY,EAAE,KAAG,MAEpD,CAAC;AAMV,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,EAChB,UAAU,MAAM,KACf,SA4BF,CAAC;AA6BF,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,eAAO,MAAM,gBAAgB,QAAO,sBAqBnC,CAAC"}
@@ -1,4 +1,8 @@
1
1
  import { useCallback, useState } from "react";
2
+ // Distinct specific users tagged so far — "Tất cả" (isAtAll) mentions aren't
3
+ // individual users and don't count toward the per-user cap.
4
+ export const countMentionedUsers = (tokens) => new Set(tokens.filter((token) => !token.isAtAll).map((token) => token.userID))
5
+ .size;
2
6
  // Textarea edits are always a single contiguous range replacement (one
3
7
  // keystroke, one backspace, or one paste/selection-replace) — so the exact
4
8
  // edit range is derivable from the common prefix/suffix between old and new
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAgCA,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,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAsT3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAgCA,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,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAwT3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -160,6 +160,8 @@ const MessageList = (props) => {
160
160
  if (!conversationData) {
161
161
  return (_jsx("div", { className: "flex flex-1 items-center justify-center h-full", children: connectStatus !== ConnectStatus.Connected ? (_jsx(Spin, {})) : (_jsx(Empty, { description: t("no_conversation_data") })) }));
162
162
  }
163
- return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 bg-white", style: { overflowX: "hidden" }, "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (isGroupChat && isCheckingMembership) || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : isViewOnlyMember ? (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute ? "send_message_disabled_group_mute" : "send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(ConfirmModal, { open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
163
+ return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 bg-white", style: { overflowX: "hidden" }, "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (isGroupChat && isCheckingMembership) || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
164
+ // Brief fallback while isJoined catches up after the kick event.
165
+ _jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute ? "send_message_disabled_group_mute" : "send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(ConfirmModal, { open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
164
166
  };
165
167
  export default MessageList;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/index.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAGL,wBAAwB,EAEzB,MAAM,cAAc,CAAC;AAStB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAoB/C,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;CACpC;AASD,eAAO,MAAM,oBAAoB,mDAc/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,gCAAyC,CAAC;AAE9E,QAAA,MAAM,qBAAqB,GAAI,4CAG5B,kBAAkB,4CAuUpB,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/index.tsx"],"names":[],"mappings":"AAiBA,OAAO,EAGL,wBAAwB,EAEzB,MAAM,cAAc,CAAC;AAStB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAoB/C,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;CACpC;AASD,eAAO,MAAM,oBAAoB,mDAc/B,CAAC;AAEH,eAAO,MAAM,uBAAuB,gCAAyC,CAAC;AAE9E,QAAA,MAAM,qBAAqB,GAAI,4CAG5B,kBAAkB,4CA0UpB,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -27,7 +27,7 @@ import useConversationStore from "../../../store/conversation";
27
27
  import { useCurrentUserAccountType } from "../../../hooks/user/useCurrentUserAccountType";
28
28
  import ComposerTabs from "./ComposerTabs";
29
29
  import ComposerEditor from "./ComposerEditor";
30
- import MentionMenu from "./mention/MentionMenu";
30
+ import MentionMenu, { COMPOSER_AREA_CP_NAME } from "./mention/MentionMenu";
31
31
  import SessionClosedModal from "./SessionClosedModal";
32
32
  import { useCreateSession, useReopenSession, } from "../../../hooks/session/useReopenOrCreateSession";
33
33
  import { emit } from "../../../utils/events";
@@ -234,6 +234,6 @@ const MessageFooterProvider = ({ currentSession, openCreateCannedModal, }) => {
234
234
  setIsMentionMenuOpen,
235
235
  }, children: [_jsxs(LexicalComposer, { initialConfig: initialConfig, children: [_jsx(Popover, { open: isOpenCanned, content: _jsx(CannedResponse, { onClose: closeCanned, openCreateCannedModal: onOpenCreateCannedModal, cannedQuery: cannedQuery }), placement: "topLeft", trigger: "click", arrow: false, classNames: {
236
236
  body: "mx-1 !p-0",
237
- }, destroyOnHidden: true, children: _jsxs("div", { className: "border-t pb-2 flex flex-col gap-1 bg-white", children: [_jsx(ReplyPreviewBar, {}), listUploadFiles.length > 0 && _jsx(FilePreview, {}), showLinkPreview && previewUrl && (_jsx(LinkInputPreview, { url: previewUrl, onRemove: () => setIsLinkPreviewDismissed(true) })), _jsx(ToolbarPlugin, {}), _jsxs("div", { children: [_jsx(ComposerTabs, { canUseInternal: canUseInternal, hintText: t("canned_response_hint"), isInternal: isInternal, lockToInternal: lockToInternal, setIsInternal: setIsInternal }), _jsx(ComposerEditor, { isInternal: isInternal, placeholderText: placeholderText })] }), _jsx(ActionBar, {})] }) }), _jsx(LinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(ComposerTextSyncPlugin, { onChange: setComposerText }), _jsx(EnterHandler, {}), _jsx(PasteAndDropPlugin, {}), isGroupConversation(conversationData !== null && conversationData !== void 0 ? conversationData : undefined) && (_jsx(MentionMenu, { groupID: conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID })), isCrm && (_jsx(CannedResponseTriggerPlugin, { onClose: closeCanned, onOpen: onOpenCanned }))] }), _jsx(SessionClosedModal, { open: isSessionClosedModalOpen, loading: isSessionActionLoading, onReopen: handleReopenSession, onCreateNew: handleCreateNewSession, onClose: handleCloseSessionModal })] }));
237
+ }, destroyOnHidden: true, children: _jsxs("div", { "data-cp-name": COMPOSER_AREA_CP_NAME, className: "border-t pb-2 flex flex-col gap-1 bg-white", children: [_jsx(ReplyPreviewBar, {}), listUploadFiles.length > 0 && _jsx(FilePreview, {}), showLinkPreview && previewUrl && (_jsx(LinkInputPreview, { url: previewUrl, onRemove: () => setIsLinkPreviewDismissed(true) })), _jsx(ToolbarPlugin, {}), _jsxs("div", { children: [_jsx(ComposerTabs, { canUseInternal: canUseInternal, hintText: t("canned_response_hint"), isInternal: isInternal, lockToInternal: lockToInternal, setIsInternal: setIsInternal }), _jsx(ComposerEditor, { isInternal: isInternal, placeholderText: placeholderText })] }), _jsx(ActionBar, {})] }) }), _jsx(LinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(ComposerTextSyncPlugin, { onChange: setComposerText }), _jsx(EnterHandler, {}), _jsx(PasteAndDropPlugin, {}), isGroupConversation(conversationData !== null && conversationData !== void 0 ? conversationData : undefined) && (_jsx(MentionMenu, { groupID: conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID })), isCrm && (_jsx(CannedResponseTriggerPlugin, { onClose: closeCanned, onOpen: onOpenCanned }))] }), _jsx(SessionClosedModal, { open: isSessionClosedModalOpen, loading: isSessionActionLoading, onReopen: handleReopenSession, onCreateNew: handleCreateNewSession, onClose: handleCloseSessionModal })] }));
238
238
  };
239
239
  export default MessageFooterProvider;
@@ -1,3 +1,4 @@
1
+ export declare const COMPOSER_AREA_CP_NAME = "MessageFooterComposerArea";
1
2
  interface MentionMenuProps {
2
3
  groupID: string | undefined;
3
4
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MentionMenu.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/MentionMenu.tsx"],"names":[],"mappings":"AA0BA,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,QAAA,MAAM,WAAW,GAAI,aAAa,gBAAgB,mDA4GjD,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MentionMenu.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/MentionMenu.tsx"],"names":[],"mappings":"AA8BA,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AAWjE,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,QAAA,MAAM,WAAW,GAAI,aAAa,gBAAgB,mDAyIjD,CAAC;AA+CF,eAAe,WAAW,CAAC"}
@@ -1,15 +1,32 @@
1
1
  "use client";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
2
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useMemo, useState } from "react";
14
+ import { useCallback, useLayoutEffect, useMemo, useRef, useState, } from "react";
4
15
  import { createPortal } from "react-dom";
5
16
  import { useTranslation } from "react-i18next";
6
17
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
7
18
  import { LexicalTypeaheadMenuPlugin, MenuOption, useBasicTypeaheadTriggerMatch, } from "@lexical/react/LexicalTypeaheadMenuPlugin";
8
- import { $createTextNode } from "lexical";
9
- import { $createMentionNode } from "./MentionNode";
19
+ import { $createTextNode, $getRoot } from "lexical";
20
+ import { $createMentionNode, $getMentionedUserIDs } from "./MentionNode";
10
21
  import { useMentionOptions } from "./useMentionOptions";
11
22
  import { Avatar } from "antd";
12
23
  import { useMessageFooterContext } from "..";
24
+ const MAX_MENTIONED_USERS = 10;
25
+ // Marks the composer's outer boundary (footer/index.tsx) so
26
+ // MentionMenuOffset below can measure against it despite Lexical's
27
+ // typeahead container being portaled outside the composer's own DOM
28
+ // subtree. Exported so both sides of this coupling share one literal.
29
+ export const COMPOSER_AREA_CP_NAME = "MessageFooterComposerArea";
13
30
  class MentionTypeaheadOption extends MenuOption {
14
31
  constructor(option) {
15
32
  super(option.userID);
@@ -25,7 +42,21 @@ const MentionMenu = ({ groupID }) => {
25
42
  minLength: 0,
26
43
  });
27
44
  const options = useMentionOptions(groupID, query !== null && query !== void 0 ? query : "");
28
- const typeaheadOptions = useMemo(() => options.map((option) => new MentionTypeaheadOption(option)), [options]);
45
+ // Re-evaluated per query change (menu re-opens/re-queries on every "@"),
46
+ // so this stays in sync with mentions added/removed since the last count.
47
+ const mentionedUserIDs = useMemo(() => editor.getEditorState().read(() => $getMentionedUserIDs($getRoot())), [editor, query]);
48
+ const mentionedUsersCount = mentionedUserIDs.size;
49
+ const limitReached = mentionedUsersCount >= MAX_MENTIONED_USERS;
50
+ // Already-tagged users don't need to appear again in the same draft.
51
+ // "Tất cả" doesn't count against the per-user cap and stays selectable;
52
+ // only specific-member options are hidden once the limit is hit.
53
+ const visibleOptions = useMemo(() => {
54
+ const notYetMentioned = options.filter((option) => option.isAtAll || !mentionedUserIDs.has(option.userID));
55
+ return limitReached
56
+ ? notYetMentioned.filter((option) => option.isAtAll)
57
+ : notYetMentioned;
58
+ }, [options, mentionedUserIDs, limitReached]);
59
+ const typeaheadOptions = useMemo(() => visibleOptions.map((option) => new MentionTypeaheadOption(option)), [visibleOptions]);
29
60
  const onSelectOption = useCallback((selected, textNodeContainingQuery, closeMenu) => {
30
61
  editor.update(() => {
31
62
  const mentionNode = $createMentionNode(selected.option.userID, selected.option.isAtAll, `@${selected.option.displayName}`);
@@ -52,8 +83,47 @@ const MentionMenu = ({ groupID }) => {
52
83
  // that much room, so it always renders downward and gets clipped
53
84
  // by the page. Anchoring this menu to the container's bottom
54
85
  // edge instead makes it grow upward from the caret line
55
- // unconditionally, independent of that flip heuristic.
56
- _jsxs("div", { "data-cp-name": "MentionMenu", className: "absolute bottom-0 left-0 z-50 max-h-[220px] w-[260px] overflow-y-auto rounded-md border border-gray-200 bg-white py-1 shadow-lg", children: [_jsx("div", { className: "px-3 py-1 text-xs font-medium text-gray-400", children: t("mention.menu_title") }), typeaheadOptions.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-gray-500", children: t("mention.no_result") })) : (typeaheadOptions.map((option, index) => (_jsxs("button", { type: "button", className: `flex w-full items-center gap-2 px-3 py-2 text-left text-sm ${index === selectedIndex ? "bg-blue-50" : "hover:bg-gray-50"}`, onMouseEnter: () => setHighlightedIndex(index), onClick: () => selectOptionAndCleanUp(option), children: [option.option.isAtAll ? (_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-500", children: "@" })) : (_jsx(Avatar, { size: 24, src: option.option.faceURL, children: option.option.displayName.charAt(0) })), _jsx("span", { className: "min-w-0 flex-1 truncate", children: option.option.displayName })] }, option.key))))] }), anchorElementRef.current);
86
+ // unconditionally, independent of that flip heuristic. But the
87
+ // caret line itself sits inside the composer box (it's the text
88
+ // cursor position), not above it — so "grow up from here" still
89
+ // overlapped the composer's own top portion. MentionMenuOffset
90
+ // pushes the extra distance from the caret up to the composer's
91
+ // actual top edge, so the menu clears the input entirely.
92
+ _jsxs(MentionMenuOffset, { "data-cp-name": "MentionMenu", className: "absolute left-0 z-50 max-h-[220px] w-[260px] overflow-y-auto rounded-md border border-gray-200 bg-white py-1 shadow-lg", children: [_jsx("div", { className: "px-3 py-1 text-xs font-medium text-gray-400", children: t("mention.menu_title") }), typeaheadOptions.length === 0 ? (_jsx("div", { className: "px-3 py-2 text-sm text-gray-500", children: limitReached
93
+ ? t("mention.limit_reached", { max: MAX_MENTIONED_USERS })
94
+ : t("mention.no_result") })) : (typeaheadOptions.map((option, index) => (_jsxs("button", { type: "button", className: `flex w-full items-center gap-2 px-3 py-2 text-left text-sm ${index === selectedIndex ? "bg-blue-50" : "hover:bg-gray-50"}`, onMouseEnter: () => setHighlightedIndex(index), onClick: () => selectOptionAndCleanUp(option), children: [option.option.isAtAll ? (_jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-gray-100 text-gray-500", children: "@" })) : (_jsx(Avatar, { size: 24, src: option.option.faceURL, children: option.option.displayName.charAt(0) })), _jsx("span", { className: "min-w-0 flex-1 truncate", children: option.option.displayName })] }, option.key))))] }), anchorElementRef.current);
57
95
  } }));
58
96
  };
97
+ // Renders inside Lexical's own typeahead container, which it portals
98
+ // straight to document.body — detached from the composer's own DOM
99
+ // subtree, so `closest()` can't find the composer from here. Look it up
100
+ // globally instead: only one composer is ever focused/active at a time.
101
+ // Measures the distance from the caret-position anchor up to the
102
+ // composer's top edge and applies it as `bottom`, so the menu's own bottom
103
+ // edge lands there instead of at the caret's — clearing the input instead
104
+ // of overlapping it.
105
+ //
106
+ // No dependency array: this component's identity survives across the
107
+ // re-renders LexicalTypeaheadMenuPlugin fires on every keystroke while the
108
+ // menu stays open (same tree position, not remounted), so a one-time
109
+ // measurement would go stale the moment the composer's own height changes
110
+ // mid-session (e.g. a reply preview or file attachment toggling on/off).
111
+ // Re-measuring every render keeps it correct at the cost of one cheap
112
+ // getBoundingClientRect() call per keystroke.
113
+ const MentionMenuOffset = (_a) => {
114
+ var { className, children } = _a, rest = __rest(_a, ["className", "children"]);
115
+ const ref = useRef(null);
116
+ const [bottomOffset, setBottomOffset] = useState(0);
117
+ useLayoutEffect(() => {
118
+ var _a;
119
+ const anchorNode = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.parentElement;
120
+ const composer = document.querySelector(`[data-cp-name="${COMPOSER_AREA_CP_NAME}"]`);
121
+ if (!anchorNode || !composer)
122
+ return;
123
+ const anchorRect = anchorNode.getBoundingClientRect();
124
+ const composerRect = composer.getBoundingClientRect();
125
+ setBottomOffset(Math.max(0, anchorRect.top - composerRect.top));
126
+ });
127
+ return (_jsx("div", Object.assign({ ref: ref, className: className, style: { bottom: bottomOffset } }, rest, { children: children })));
128
+ };
59
129
  export default MentionMenu;
@@ -17,4 +17,5 @@ export declare class MentionNode extends TextNode {
17
17
  }
18
18
  export declare function $createMentionNode(userID: string, isAtAll: boolean, displayText: string): MentionNode;
19
19
  export declare function $isMentionNode(node: LexicalNode | null | undefined): node is MentionNode;
20
+ export declare function $getMentionedUserIDs(root: LexicalNode): Set<string>;
20
21
  //# sourceMappingURL=MentionNode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MentionNode.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/MentionNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG,MAAM,CACxC;IACE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,EACD,kBAAkB,CACnB,CAAC;AAEF,qBAAa,WAAY,SAAQ,QAAQ;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IAEnB,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW;gBAU1C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,OAAO;IAOf,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAM5C,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,qBAAqB,GAAG,WAAW;IAQrE,UAAU,IAAI,qBAAqB;IASnC,mBAAmB,IAAI,OAAO;IAI9B,YAAY,IAAI,IAAI;CAGrB;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,GAClB,WAAW,CAIb;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACnC,IAAI,IAAI,WAAW,CAErB"}
1
+ {"version":3,"file":"MentionNode.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/MentionNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,QAAQ,EACR,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,OAAO,EACZ,KAAK,kBAAkB,EACvB,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG,MAAM,CACxC;IACE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB,EACD,kBAAkB,CACnB,CAAC;AAEF,qBAAa,WAAY,SAAQ,QAAQ;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IAEnB,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW;gBAU1C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,OAAO;IAOf,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW;IAM5C,MAAM,CAAC,UAAU,CAAC,cAAc,EAAE,qBAAqB,GAAG,WAAW;IAQrE,UAAU,IAAI,qBAAqB;IASnC,mBAAmB,IAAI,OAAO;IAI9B,YAAY,IAAI,IAAI;CAGrB;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,GAClB,WAAW,CAIb;AAED,wBAAgB,cAAc,CAC5B,IAAI,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,GACnC,IAAI,IAAI,WAAW,CAErB;AAID,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CAcnE"}
@@ -37,3 +37,18 @@ export function $createMentionNode(userID, isAtAll, displayText) {
37
37
  export function $isMentionNode(node) {
38
38
  return node instanceof MentionNode;
39
39
  }
40
+ // Distinct specific users tagged so far — "Tất cả" (isAtAll) mentions aren't
41
+ // individual users and don't count toward the per-user cap.
42
+ export function $getMentionedUserIDs(root) {
43
+ const userIDs = new Set();
44
+ const walk = (node) => {
45
+ if ($isMentionNode(node) && !node.__isAtAll) {
46
+ userIDs.add(node.__userID);
47
+ }
48
+ if ("getChildren" in node && typeof node.getChildren === "function") {
49
+ node.getChildren().forEach(walk);
50
+ }
51
+ };
52
+ walk(root);
53
+ return userIDs;
54
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"useMentionOptions.d.ts","sourceRoot":"","sources":["../../../../../src/components/message/footer/mention/useMentionOptions.ts"],"names":[],"mappings":"AAIA,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;AAW7C,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,EAC3B,OAAO,MAAM,KACZ,aAAa,EAuCf,CAAC"}
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;AAW7C,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,GAAG,SAAS,EAC3B,OAAO,MAAM,KACZ,aAAa,EAyCf,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { useMemo } from "react";
2
2
  import { useTranslation } from "react-i18next";
3
3
  import { useGroupMembers } from "../../../../hooks/group/useGroupMembers";
4
+ import useAuthStore from "../../../../store/auth";
4
5
  export const ALL_MEMBERS_USER_ID = "__all__";
5
6
  // Prefix matches rank above mid-string matches, then alphabetical — matches
6
7
  // the "closest match first" ranking spec'd for this feature (not just a
@@ -14,11 +15,13 @@ const matchRank = (displayName, keyword) => {
14
15
  export const useMentionOptions = (groupID, query) => {
15
16
  const { t } = useTranslation();
16
17
  const { members } = useGroupMembers(groupID);
18
+ const currentUserID = useAuthStore((state) => state.userID);
17
19
  return useMemo(() => {
18
20
  const keyword = query.trim().toLowerCase();
19
- // Self-tagging is a valid, ordinary mention target per spec the
20
- // sender selecting their own name is processed like any other tag.
21
+ // Self-tagging isn't a useful mention target the sender already
22
+ // knows who they are so the current user is filtered out entirely.
21
23
  const memberOptions = members
24
+ .filter((member) => member.userID !== currentUserID)
22
25
  .map((member) => ({
23
26
  userID: member.userID,
24
27
  displayName: member.nickname || member.userID,
@@ -41,5 +44,5 @@ export const useMentionOptions = (groupID, query) => {
41
44
  };
42
45
  const matchesAll = !keyword || allOption.displayName.toLowerCase().includes(keyword);
43
46
  return matchesAll ? [allOption, ...memberOptions] : memberOptions;
44
- }, [members, query, t]);
47
+ }, [members, query, t, currentUserID]);
45
48
  };
@@ -0,0 +1,7 @@
1
+ import { MessageItem } from "@droppii-org/wasm-client-sdk";
2
+ interface SessionEventMessageItemProps {
3
+ message: MessageItem;
4
+ }
5
+ declare const SessionEventMessageItem: ({ message }: SessionEventMessageItemProps) => import("react/jsx-runtime").JSX.Element | null;
6
+ export default SessionEventMessageItem;
7
+ //# sourceMappingURL=SessionEventMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SessionEventMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/SessionEventMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAI3D,UAAU,4BAA4B;IACpC,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,uBAAuB,GAAI,aAAa,4BAA4B,mDAazE,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import { getSessionEventText } from "../../../utils/messageLog";
5
+ const SessionEventMessageItem = ({ message }) => {
6
+ const { t } = useTranslation();
7
+ const text = getSessionEventText(message, t);
8
+ if (!text)
9
+ return null;
10
+ return (_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "rounded-full bg-gray-200 px-3 py-1.5 text-center text-[13px] font-normal text-gray-900", children: text }) }));
11
+ };
12
+ export default SessionEventMessageItem;
@@ -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,EAG/B,MAAM,8BAA8B,CAAC;AAmCtC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA0BD,QAAA,MAAM,WAAW,GAAI,qMAalB,gBAAgB,mDA+SlB,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,8BAA8B,CAAC;AAwCtC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA0BD,QAAA,MAAM,WAAW,GAAI,qMAalB,gBAAgB,mDA0TlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -20,7 +20,8 @@ import { useSendMessage, isMediaResendable, } from "../../../hooks/message/useSe
20
20
  import { Icon } from "../../../components/icon";
21
21
  import SystemLogMessageItem from "./SystemLogMessage";
22
22
  import GroupNotificationMessageItem from "./GroupNotificationMessage";
23
- import { isGroupNotificationMessageType, isSystemLogMessageType } from "../../../utils/messageLog";
23
+ import SessionEventMessageItem from "./SessionEventMessage";
24
+ import { isGroupNotificationMessageType, isSessionEventMessage, isSystemLogMessageType, } from "../../../utils/messageLog";
24
25
  import { buildMessageToolboxMenuItems, getCopyableMessageText, isCopyableMessageType, isPinnableMessageType, MessageToolboxActionKey, MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, useMessageToolboxVisibility, } from "../../../components/message/toolbox";
25
26
  const MESSAGE_AVATAR_SIZE = 24;
26
27
  const DIVIDER_GAP_MINUTES = 30;
@@ -41,7 +42,8 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
41
42
  const contextMenuItems = useMemo(() => {
42
43
  if ((message === null || message === void 0 ? void 0 : message.contentType) === MessageType.RevokeMessage ||
43
44
  isSystemLogMessageType(message === null || message === void 0 ? void 0 : message.contentType) ||
44
- isGroupNotificationMessageType(message === null || message === void 0 ? void 0 : message.contentType)) {
45
+ isGroupNotificationMessageType(message === null || message === void 0 ? void 0 : message.contentType) ||
46
+ isSessionEventMessage(message)) {
45
47
  return [];
46
48
  }
47
49
  const closeToolbox = () => onContextMenuOpenChange === null || onContextMenuOpenChange === void 0 ? void 0 : onContextMenuOpenChange(false);
@@ -125,6 +127,7 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
125
127
  const isInternalMessage = Boolean(message === null || message === void 0 ? void 0 : message.isInternal);
126
128
  const isSystemLogMessage = isSystemLogMessageType(message === null || message === void 0 ? void 0 : message.contentType);
127
129
  const isGroupNotificationMessage = isGroupNotificationMessageType(message === null || message === void 0 ? void 0 : message.contentType);
130
+ const isSessionEvent = isSessionEventMessage(message);
128
131
  const isStickerMessage = (message === null || message === void 0 ? void 0 : message.contentType) === MessageType.StickerMessage;
129
132
  const hasContextMenu = contextMenuItems.length > 0;
130
133
  const isLatestMine = ((_a = allMessages.find((m) => m.sendID === userID)) === null || _a === void 0 ? void 0 : _a.clientMsgID) ===
@@ -170,6 +173,11 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
170
173
  timeBreakNode,
171
174
  _jsx(GroupNotificationMessageItem, { message: message })));
172
175
  }
176
+ if (isSessionEvent) {
177
+ return (_createElement("div", Object.assign({}, messageContainerProps, { key: message === null || message === void 0 ? void 0 : message.clientMsgID }),
178
+ timeBreakNode,
179
+ _jsx(SessionEventMessageItem, { message: message })));
180
+ }
173
181
  const bubbleNode = (_jsx(MessageBubble, { isMine: isMine, isInternal: isInternalMessage, senderName: message === null || message === void 0 ? void 0 : message.senderNickname, showSenderName: showSenderName, showTip: showTip, id: `${MSG_ITEM_CONTENT_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`, content: renderBubbleContent(message, onPressQuoteMessage), isTransparent: isStickerMessage, timeLabel: dayjs.tz(message.sendTime).format("HH:mm") }));
174
182
  const showPinnedBadge = Boolean(message === null || message === void 0 ? void 0 : message.isPinned);
175
183
  const pinnedBadgeNode = showPinnedBadge ? (_jsx("span", { "data-testid": "message-pinned-badge", className: clsx("absolute -top-2.5 z-[1] flex h-[22px] w-[26px] items-center justify-center rounded-full border border-white bg-gray-25", isMine ? "left-0" : "right-0"), children: _jsx(Icon, { icon: "thumbtack-b", size: 12, className: "text-primary-400" }) })) : null;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/search-conversation/index.tsx"],"names":[],"mappings":"AAWA,oBAAY,wBAAwB;IAClC,GAAG,QAAQ;IACX,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,UAAU,uBAAuB;IAC/B,UAAU,EAAE,MAAM,CAAC;CACpB;AACD,QAAA,MAAM,kBAAkB,GAAI,gBAAgB,uBAAuB,4CAqDlE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/search-conversation/index.tsx"],"names":[],"mappings":"AAWA,oBAAY,wBAAwB;IAClC,GAAG,QAAQ;IACX,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED,UAAU,uBAAuB;IAC/B,UAAU,EAAE,MAAM,CAAC;CACpB;AACD,QAAA,MAAM,kBAAkB,GAAI,gBAAgB,uBAAuB,4CA4ElE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { Tabs } from "antd";
4
- import { useMemo, useState } from "react";
4
+ import { useEffect, useMemo, useState } from "react";
5
5
  import { useTranslation } from "react-i18next";
6
6
  import SearchConversationAll from "./SearchAll";
7
7
  import SearchConversationMyInbox from "./SearchConversationMyInbox";
@@ -48,6 +48,24 @@ const SearchConversation = ({ searchTerm }) => {
48
48
  },
49
49
  ];
50
50
  }, [t, searchTerm]);
51
- return (_jsx("div", { className: "h-full", "data-cp-name": "SearchConversation", children: _jsx(Tabs, { defaultActiveKey: SearchConversationTabKey.All, items: items, className: "h-full", activeKey: activeKey, onChange: (key) => setActiveKey(key), destroyOnHidden: true }) }));
51
+ // Clicking a result row while on "All" switches activeKey without any
52
+ // click/focus on the tab header itself — antd's own scroll-into-view for
53
+ // that path relies on measuring .ant-tabs-nav against its scroll parent,
54
+ // which the fixed-width nav below permanently reports as "already fits"
55
+ // (see the renderTabBar comment). Do it ourselves instead.
56
+ useEffect(() => {
57
+ var _a;
58
+ (_a = document
59
+ .querySelector('[data-cp-name="SearchConversation"] .ant-tabs-tab-active')) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ inline: "nearest", block: "nearest" });
60
+ }, [activeKey]);
61
+ return (_jsx("div", { className: "h-full", "data-cp-name": "SearchConversation", children: _jsx(Tabs, { defaultActiveKey: SearchConversationTabKey.All, items: items, className: "h-full", activeKey: activeKey, onChange: (key) => setActiveKey(key), destroyOnHidden: true,
62
+ // Ant's built-in tab-overflow ("...") dropdown never recovers from
63
+ // its initial off-screen measurement inside this Drawer (rendered
64
+ // with getContainer={false} for unrelated scroll-position reasons —
65
+ // see DeskConversationList) — the last tab (Nhóm) becomes
66
+ // permanently unreachable. A plain horizontally-scrollable strip
67
+ // sidesteps that positioning bug entirely: every tab stays a real,
68
+ // always-clickable element: no hidden dropdown involved.
69
+ renderTabBar: (tabBarProps, DefaultTabBar) => (_jsx("div", { className: "overflow-x-auto", children: _jsx(DefaultTabBar, Object.assign({}, tabBarProps, { className: "!w-max" })) })) }) }));
52
70
  };
53
71
  export default SearchConversation;
@@ -1 +1 @@
1
- {"version":3,"file":"SearchItemAsGroup.d.ts","sourceRoot":"","sources":["../../../../src/components/search-conversation/item/SearchItemAsGroup.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAOxF,UAAU,sBAAsB;IAC9B,KAAK,EAAE,4BAA4B,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,iBAAiB,GAAI,OAAO,sBAAsB,4CAgEvD,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"SearchItemAsGroup.d.ts","sourceRoot":"","sources":["../../../../src/components/search-conversation/item/SearchItemAsGroup.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAOxF,UAAU,sBAAsB;IAC9B,KAAK,EAAE,4BAA4B,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,QAAA,MAAM,iBAAiB,GAAI,OAAO,sBAAsB,4CAiEvD,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -16,7 +16,8 @@ const SearchItemAsGroup = (props) => {
16
16
  const { conversation } = group;
17
17
  const isPublic = group.visibility === GroupVisibility.Public;
18
18
  const onPressItem = () => {
19
- setConversationData(conversation);
19
+ // Result can go stale before the click — force a fresh membership check.
20
+ setConversationData(conversation, undefined, { forceRevalidate: true });
20
21
  setSelectedConversationId(conversation.conversationID);
21
22
  const newSearchParams = new URLSearchParams(window.location.search);
22
23
  newSearchParams.set("threadId", conversation.conversationID);
@@ -1 +1 @@
1
- {"version":3,"file":"SearchItemAsMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/search-conversation/item/SearchItemAsMessage.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAA4B,MAAM,8BAA8B,CAAC;AAerF,UAAU,wBAAwB;IAChC,OAAO,EAAE,WAAW,GAAG;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,mBAAmB,GAAI,OAAO,wBAAwB,4CAwG3D,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"SearchItemAsMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/search-conversation/item/SearchItemAsMessage.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAA4B,MAAM,8BAA8B,CAAC;AAerF,UAAU,wBAAwB;IAChC,OAAO,EAAE,WAAW,GAAG;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,QAAA,MAAM,mBAAmB,GAAI,OAAO,wBAAwB,4CA2G3D,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -36,7 +36,10 @@ const SearchItemAsMessage = (props) => {
36
36
  }
37
37
  useConversationStore
38
38
  .getState()
39
- .setConversationData(data, message.clientMsgID);
39
+ // Result can be stale — force a fresh membership check.
40
+ .setConversationData(data, message.clientMsgID, {
41
+ forceRevalidate: true,
42
+ });
40
43
  useConversationStore
41
44
  .getState()
42
45
  .setSelectedConversationId(data.conversationID);
@@ -1 +1 @@
1
- {"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqED,eAAO,MAAM,sBAAsB,GAAI,6CAKpC,4BAA4B;;;CA0E9B,CAAC"}
1
+ {"version":3,"file":"useResolveConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useResolveConversation.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqED,eAAO,MAAM,sBAAsB,GAAI,6CAKpC,4BAA4B;;;CA2E9B,CAAC"}