@droppii-org/chat-sdk 0.7.7 → 0.7.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 (108) hide show
  1. package/dist/components/conversation/ThreadCard.d.ts +2 -1
  2. package/dist/components/conversation/ThreadCard.d.ts.map +1 -1
  3. package/dist/components/conversation/ThreadCard.js +2 -2
  4. package/dist/components/conversation-detail/ConversationDetailHeader.d.ts +5 -1
  5. package/dist/components/conversation-detail/ConversationDetailHeader.d.ts.map +1 -1
  6. package/dist/components/conversation-detail/ConversationDetailHeader.js +3 -3
  7. package/dist/components/conversation-detail/ConversationMoreOptionPanel.d.ts.map +1 -1
  8. package/dist/components/conversation-detail/ConversationMoreOptionPanel.js +3 -1
  9. package/dist/components/conversation-detail/ThreadProfilePanel.d.ts +7 -1
  10. package/dist/components/conversation-detail/ThreadProfilePanel.d.ts.map +1 -1
  11. package/dist/components/conversation-detail/ThreadProfilePanel.js +13 -3
  12. package/dist/components/conversation-detail/profile/PinThreadLimitModal.d.ts +7 -0
  13. package/dist/components/conversation-detail/profile/PinThreadLimitModal.d.ts.map +1 -0
  14. package/dist/components/conversation-detail/profile/PinThreadLimitModal.js +14 -0
  15. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts +15 -0
  16. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts.map +1 -0
  17. package/dist/components/conversation-detail/profile/ThreadProfileActionRows.js +20 -0
  18. package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts +13 -0
  19. package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts.map +1 -0
  20. package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.js +36 -0
  21. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts +13 -0
  22. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts.map +1 -0
  23. package/dist/components/conversation-detail/thread-search/ThreadSearchContent.js +70 -0
  24. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts +11 -0
  25. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts.map +1 -0
  26. package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.js +12 -0
  27. package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.d.ts +11 -0
  28. package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.d.ts.map +1 -0
  29. package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.js +24 -0
  30. package/dist/components/conversation-detail/thread-search/utils.d.ts +8 -0
  31. package/dist/components/conversation-detail/thread-search/utils.d.ts.map +1 -0
  32. package/dist/components/conversation-detail/thread-search/utils.js +17 -0
  33. package/dist/components/conversation-inbox/ConversationInboxItem.d.ts +3 -1
  34. package/dist/components/conversation-inbox/ConversationInboxItem.d.ts.map +1 -1
  35. package/dist/components/conversation-inbox/ConversationInboxItem.js +4 -6
  36. package/dist/components/conversation-inbox/ConversationInboxList.d.ts +3 -1
  37. package/dist/components/conversation-inbox/ConversationInboxList.d.ts.map +1 -1
  38. package/dist/components/conversation-inbox/ConversationInboxList.js +2 -2
  39. package/dist/components/conversation-inbox/ConversationThreadActions.d.ts +12 -0
  40. package/dist/components/conversation-inbox/ConversationThreadActions.d.ts.map +1 -0
  41. package/dist/components/conversation-inbox/ConversationThreadActions.js +58 -0
  42. package/dist/components/conversation-inbox/index.d.ts +2 -2
  43. package/dist/components/conversation-inbox/index.d.ts.map +1 -1
  44. package/dist/components/conversation-inbox/index.js +1 -1
  45. package/dist/components/group/MemberPermissionPanel.d.ts.map +1 -1
  46. package/dist/components/group/MemberPermissionPanel.js +12 -23
  47. package/dist/components/message/toolbox/MessageToolboxMenu.d.ts +7 -0
  48. package/dist/components/message/toolbox/MessageToolboxMenu.d.ts.map +1 -1
  49. package/dist/components/message/toolbox/MessageToolboxMenu.js +1 -1
  50. package/dist/constants/moreOption.d.ts +1 -0
  51. package/dist/constants/moreOption.d.ts.map +1 -1
  52. package/dist/hooks/conversation/useBotFlow.d.ts.map +1 -1
  53. package/dist/hooks/conversation/useBotFlow.js +42 -23
  54. package/dist/hooks/conversation/useConversationTarget.d.ts +4 -0
  55. package/dist/hooks/conversation/useConversationTarget.d.ts.map +1 -0
  56. package/dist/hooks/conversation/useConversationTarget.js +23 -0
  57. package/dist/hooks/conversation/usePinConversation.d.ts +10 -0
  58. package/dist/hooks/conversation/usePinConversation.d.ts.map +1 -0
  59. package/dist/hooks/conversation/usePinConversation.js +52 -0
  60. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  61. package/dist/hooks/global/useGlobalEvent.js +3 -0
  62. package/dist/hooks/group/useJoinPublicGroup.d.ts.map +1 -1
  63. package/dist/hooks/group/useJoinPublicGroup.js +9 -1
  64. package/dist/hooks/message/holdMessageHighlight.d.ts +6 -0
  65. package/dist/hooks/message/holdMessageHighlight.d.ts.map +1 -0
  66. package/dist/hooks/message/holdMessageHighlight.js +22 -0
  67. package/dist/hooks/message/useMessage.d.ts +1 -0
  68. package/dist/hooks/message/useMessage.d.ts.map +1 -1
  69. package/dist/hooks/message/useMessage.js +14 -9
  70. package/dist/hooks/message/useMessageListScroll.d.ts +3 -1
  71. package/dist/hooks/message/useMessageListScroll.d.ts.map +1 -1
  72. package/dist/hooks/message/useMessageListScroll.js +35 -3
  73. package/dist/hooks/message/useSendMessage.d.ts +1 -1
  74. package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
  75. package/dist/hooks/message/useSendMessage.js +358 -285
  76. package/dist/hooks/search/useSearchThreadMessages.d.ts +222 -0
  77. package/dist/hooks/search/useSearchThreadMessages.d.ts.map +1 -0
  78. package/dist/hooks/search/useSearchThreadMessages.js +60 -0
  79. package/dist/locales/vi/conversation-inbox.json +31 -0
  80. package/dist/screens/conversation-detail/index.d.ts +5 -1
  81. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  82. package/dist/screens/conversation-detail/index.js +29 -3
  83. package/dist/screens/conversation-inbox/index.d.ts +6 -0
  84. package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
  85. package/dist/screens/conversation-inbox/index.js +3 -3
  86. package/dist/services/query.d.ts +3 -0
  87. package/dist/services/query.d.ts.map +1 -1
  88. package/dist/services/query.js +3 -0
  89. package/dist/services/routes.d.ts +2 -0
  90. package/dist/services/routes.d.ts.map +1 -1
  91. package/dist/services/routes.js +2 -0
  92. package/dist/store/conversation.d.ts.map +1 -1
  93. package/dist/store/conversation.js +10 -6
  94. package/dist/store/type.d.ts +3 -0
  95. package/dist/store/type.d.ts.map +1 -1
  96. package/dist/styles/global.css +1 -1
  97. package/dist/types/conversation.d.ts +8 -0
  98. package/dist/types/conversation.d.ts.map +1 -1
  99. package/dist/utils/common.d.ts +7 -0
  100. package/dist/utils/common.d.ts.map +1 -1
  101. package/dist/utils/common.js +42 -13
  102. package/dist/utils/sendQueue.d.ts +2 -0
  103. package/dist/utils/sendQueue.d.ts.map +1 -0
  104. package/dist/utils/sendQueue.js +14 -0
  105. package/package.json +1 -1
  106. package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts +0 -8
  107. package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts.map +0 -1
  108. package/dist/components/conversation-inbox/ConversationMuteSwipeAction.js +0 -31
@@ -13,7 +13,7 @@ import InboxBootStatus from "./InboxBootStatus";
13
13
  import InboxListSkeleton from "./InboxListSkeleton";
14
14
  const INBOX_SLOW_AFTER_MS = 8000;
15
15
  const FooterSkeleton = () => (_jsxs("div", { className: "flex items-center gap-3 p-3", children: [_jsx("div", { className: "h-10 w-10 animate-pulse rounded-full bg-neutral-200" }), _jsxs("div", { className: "flex flex-1 flex-col gap-2", children: [_jsx("div", { className: "h-3 w-1/2 animate-pulse rounded bg-neutral-200" }), _jsx("div", { className: "h-2.5 w-1/3 animate-pulse rounded bg-neutral-200" })] })] }));
16
- const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPage, onLoadMore, onSelectThread, }) => {
16
+ const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPage, onLoadMore, onSelectThread, showPinThread = false, }) => {
17
17
  const { t } = useTranslation("conversation-inbox");
18
18
  const onNavigate = useChatNavigate();
19
19
  const boot = useChatBootPhase({ slowAfterMs: INBOX_SLOW_AFTER_MS });
@@ -41,6 +41,6 @@ const ConversationInboxList = ({ items, isLoading, isFetchingNextPage, hasNextPa
41
41
  return (_jsxs("div", { "data-testid": "conversation-inbox-thread-list", className: "min-h-0 w-full flex-1 overflow-hidden", children: [!isListSettled && (_jsx(InboxBootStatus, { boot: boot.phase === "ready"
42
42
  ? Object.assign(Object.assign({}, boot), { phase: "sync", syncProgress: 100 }) : boot })), boot.phase !== "error" && _jsx(InboxListSkeleton, { rows: 6 })] }));
43
43
  }
44
- return (_jsx("div", { id: "scrollableConversationThreadsDiv", "data-testid": "conversation-inbox-thread-list", "data-cp-name": "ConversationInboxList", className: "min-h-0 w-full flex-1 overflow-auto", children: _jsx(InfiniteScroll, { dataLength: items.length, next: handleEndReached, hasMore: hasNextPage, loader: isFetchingNextPage ? _jsx(FooterSkeleton, {}) : null, scrollableTarget: "scrollableConversationThreadsDiv", children: items.length === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx("div", { className: "flex justify-center", children: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-[var(--dc-color-primary-light)]" }) }), description: t("empty.no_conversations") }) })) : (items.map((item) => (_jsx(ConversationInboxItem, { item: item, onClick: handleSelectThread }, item.conversationID)))) }) }));
44
+ return (_jsx("div", { id: "scrollableConversationThreadsDiv", "data-testid": "conversation-inbox-thread-list", "data-cp-name": "ConversationInboxList", className: "min-h-0 w-full flex-1 overflow-auto", children: _jsx(InfiniteScroll, { dataLength: items.length, next: handleEndReached, hasMore: hasNextPage, loader: isFetchingNextPage ? _jsx(FooterSkeleton, {}) : null, scrollableTarget: "scrollableConversationThreadsDiv", children: items.length === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx("div", { className: "flex justify-center", children: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-[var(--dc-color-primary-light)]" }) }), description: t("empty.no_conversations") }) })) : (items.map((item) => (_jsx(ConversationInboxItem, { item: item, onClick: handleSelectThread, showPinThread: showPinThread }, item.conversationID)))) }) }));
45
45
  };
46
46
  export default ConversationInboxList;
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ConversationItem } from "@droppii-org/wasm-client-sdk";
3
+ export interface ConversationThreadActionsProps {
4
+ conversation: ConversationItem;
5
+ isMuted: boolean;
6
+ /** Adds Ghim / Bỏ ghim to the swipe actions and a right-click menu on the card. */
7
+ showPin: boolean;
8
+ children: ReactNode;
9
+ }
10
+ declare const ConversationThreadActions: ({ conversation, isMuted, showPin, children, }: ConversationThreadActionsProps) => import("react/jsx-runtime").JSX.Element;
11
+ export default ConversationThreadActions;
12
+ //# sourceMappingURL=ConversationThreadActions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConversationThreadActions.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/ConversationThreadActions.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAcrE,MAAM,WAAW,8BAA8B;IAC7C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,QAAA,MAAM,yBAAyB,GAAI,+CAKhC,8BAA8B,4CAkHhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { Dropdown } from "antd";
5
+ import { useTranslation } from "react-i18next";
6
+ import SwipeableRow from "../../components/conversation/SwipeableRow";
7
+ import SwipeActionButton, { SWIPE_ACTION_WIDTH, } from "../../components/conversation/SwipeActionButton";
8
+ import { MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, ToolboxMenuRow, } from "../../components/message/toolbox";
9
+ import PinThreadLimitModal from "../../components/conversation-detail/profile/PinThreadLimitModal";
10
+ import { useMuteConversation } from "../../hooks/session/useMuteConversation";
11
+ import { usePinConversation } from "../../hooks/conversation/usePinConversation";
12
+ import MuteConversationConfirmModal from "./MuteConversationConfirmModal";
13
+ const ConversationThreadActions = ({ conversation, isMuted, showPin, children, }) => {
14
+ const { t } = useTranslation("conversation-inbox");
15
+ const { muteConversation, unmuteConversation, isPending: isMutePending, } = useMuteConversation();
16
+ const { togglePin, isLimitOpen, closeLimit } = usePinConversation();
17
+ const [isMuteConfirmOpen, setIsMuteConfirmOpen] = useState(false);
18
+ const { conversationID } = conversation;
19
+ const isPinned = !!conversation.isPinned;
20
+ const muteLabel = t(isMuted ? "thread_card.mute.unmute_action" : "thread_card.mute.mute_action");
21
+ const pinLabel = t(isPinned ? "thread_card.pin.unpin_action" : "thread_card.pin.pin_action");
22
+ // Muting asks first; unmuting doesn't.
23
+ const handleToggleMute = () => {
24
+ if (isMuted) {
25
+ void unmuteConversation(conversationID);
26
+ return;
27
+ }
28
+ setIsMuteConfirmOpen(true);
29
+ };
30
+ const handleConfirmMute = () => {
31
+ setIsMuteConfirmOpen(false);
32
+ void muteConversation(conversationID);
33
+ };
34
+ const handleTogglePin = () => {
35
+ void togglePin(conversation);
36
+ };
37
+ const swipeRow = (_jsx(SwipeableRow, { actionsWidth: SWIPE_ACTION_WIDTH * (showPin ? 2 : 1), actions: (closeSwipe) => (_jsxs(_Fragment, { children: [_jsx(SwipeActionButton, { testId: "conversation-inbox-mute-action", icon: isMuted ? "bell-b" : "bell-off-b", label: muteLabel, onClick: () => {
38
+ closeSwipe();
39
+ handleToggleMute();
40
+ }, className: "bg-gray-400" }), showPin && (_jsx(SwipeActionButton, { testId: "conversation-inbox-pin-action", icon: isPinned ? "thumbtack-off-b" : "thumbtack-b", label: pinLabel, onClick: () => {
41
+ closeSwipe();
42
+ handleTogglePin();
43
+ }, className: "bg-[var(--dc-color-primary)]" }))] })), children: children }));
44
+ const menuItems = [
45
+ {
46
+ key: "mute",
47
+ label: (_jsx(ToolboxMenuRow, { icon: isMuted ? "bell-o" : "bell-off-o", label: muteLabel })),
48
+ onClick: handleToggleMute,
49
+ },
50
+ {
51
+ key: "pin",
52
+ label: (_jsx(ToolboxMenuRow, { icon: isPinned ? "thumbtack-off-o" : "thumbtack-o", label: pinLabel })),
53
+ onClick: handleTogglePin,
54
+ },
55
+ ];
56
+ return (_jsxs(_Fragment, { children: [showPin ? (_jsx(Dropdown, { menu: { items: menuItems }, trigger: ["contextMenu"], overlayClassName: MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, children: _jsx("div", { "data-testid": "conversation-inbox-thread-actions", children: swipeRow }) })) : (swipeRow), _jsx(MuteConversationConfirmModal, { open: isMuteConfirmOpen, loading: isMutePending, onCancel: () => setIsMuteConfirmOpen(false), onConfirm: handleConfirmMute }), showPin && _jsx(PinThreadLimitModal, { open: isLimitOpen, onClose: closeLimit })] }));
57
+ };
58
+ export default ConversationThreadActions;
@@ -7,8 +7,8 @@ export { default as ConversationInboxItem } from "./ConversationInboxItem";
7
7
  export type { ConversationInboxItemProps } from "./ConversationInboxItem";
8
8
  export { default as ConversationInboxList } from "./ConversationInboxList";
9
9
  export type { ConversationInboxListProps } from "./ConversationInboxList";
10
- export { default as ConversationMuteSwipeAction } from "./ConversationMuteSwipeAction";
11
- export type { ConversationMuteSwipeActionProps } from "./ConversationMuteSwipeAction";
10
+ export { default as ConversationThreadActions } from "./ConversationThreadActions";
11
+ export type { ConversationThreadActionsProps } from "./ConversationThreadActions";
12
12
  export { default as MuteConversationConfirmModal } from "./MuteConversationConfirmModal";
13
13
  export type { MuteConversationConfirmModalProps } from "./MuteConversationConfirmModal";
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AAEtF,OAAO,EAAE,OAAO,IAAI,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AACzF,YAAY,EAAE,iCAAiC,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-inbox/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAC7E,YAAY,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAE5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAC3E,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAE1E,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnF,YAAY,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAElF,OAAO,EAAE,OAAO,IAAI,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AACzF,YAAY,EAAE,iCAAiC,EAAE,MAAM,gCAAgC,CAAC"}
@@ -3,5 +3,5 @@ export { default as ConversationPeerAvatar } from "./ConversationPeerAvatar";
3
3
  export { default as ConversationInboxTitle } from "./ConversationInboxTitle";
4
4
  export { default as ConversationInboxItem } from "./ConversationInboxItem";
5
5
  export { default as ConversationInboxList } from "./ConversationInboxList";
6
- export { default as ConversationMuteSwipeAction } from "./ConversationMuteSwipeAction";
6
+ export { default as ConversationThreadActions } from "./ConversationThreadActions";
7
7
  export { default as MuteConversationConfirmModal } from "./MuteConversationConfirmModal";
@@ -1 +1 @@
1
- {"version":3,"file":"MemberPermissionPanel.d.ts","sourceRoot":"","sources":["../../../src/components/group/MemberPermissionPanel.tsx"],"names":[],"mappings":"AAqBA,UAAU,0BAA0B;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA8BD,QAAA,MAAM,qBAAqB,GAAI,4BAA4B,0BAA0B,mDA0IpF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"MemberPermissionPanel.d.ts","sourceRoot":"","sources":["../../../src/components/group/MemberPermissionPanel.tsx"],"names":[],"mappings":"AAoBA,UAAU,0BAA0B;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA6BD,QAAA,MAAM,qBAAqB,GAAI,4BAA4B,0BAA0B,mDAgHpF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { ConfigProvider, Switch, message } from "antd";
5
5
  import { useTranslation } from "react-i18next";
@@ -9,24 +9,22 @@ import { useSetMemberRolePermissions } from "../../hooks/group/useGroupMemberAct
9
9
  import { Icon } from "../../components/icon";
10
10
  import useAuthStore from "../../store/auth";
11
11
  import PermissionPanelCrm from "./PermissionPanelCrm";
12
- import DisableSendPermissionConfirm from "./DisableSendPermissionConfirm";
13
12
  import { ANTD_PRIMARY_COLOR_VAR } from "../../constants";
14
13
  import { GROUP_PERMISSION_KEY } from "../../constants/im";
15
14
  import { changedSwitches, commonSwitchStates, toRolePermissionChange, } from "../../utils/groupPermissionSwitches";
15
+ // No "Gửi tin nhắn" switch: members are muted from the group send toggle or one by one,
16
+ // and saving here never touches SEND_MESSAGE.
16
17
  const SWITCH_PERMISSION_KEYS = {
17
- sendMessage: [GROUP_PERMISSION_KEY.sendMessage],
18
18
  addMember: [GROUP_PERMISSION_KEY.addMember],
19
19
  pinMessage: [GROUP_PERMISSION_KEY.pinMessage],
20
20
  viewMemberInformation: [GROUP_PERMISSION_KEY.viewMemberInformation],
21
21
  };
22
22
  const SWITCH_LABELS = {
23
- sendMessage: "permission_send_message",
24
23
  addMember: "permission_add_member",
25
24
  pinMessage: "permission_pin_message",
26
25
  viewMemberInformation: "permission_view_member_info",
27
26
  };
28
27
  const SWITCH_DESCRIPTIONS = {
29
- sendMessage: "permission_desc_send_message",
30
28
  addMember: "permission_desc_add_member",
31
29
  pinMessage: "permission_desc_pin_message",
32
30
  viewMemberInformation: "permission_desc_view_member_info",
@@ -41,7 +39,6 @@ const MemberPermissionPanel = ({ open, groupID, onClose }) => {
41
39
  const { mutateAsync: setMemberRolePermissions } = useSetMemberRolePermissions();
42
40
  const isCrm = useAuthStore((state) => state.isCrm);
43
41
  const [isSaving, setIsSaving] = useState(false);
44
- const [isConfirmOpen, setIsConfirmOpen] = useState(false);
45
42
  const [edits, setEdits] = useState({});
46
43
  useEffect(() => {
47
44
  if (open)
@@ -52,17 +49,9 @@ const MemberPermissionPanel = ({ open, groupID, onClose }) => {
52
49
  const pending = Object.assign(Object.assign({}, baseline), edits);
53
50
  const changes = changedSwitches(baseline, pending);
54
51
  const hasChanges = Object.keys(changes).length > 0;
55
- const requestSave = () => {
56
- if (isCrm && changes.sendMessage === false) {
57
- setIsConfirmOpen(true);
58
- return;
59
- }
60
- void handleSave();
61
- };
62
52
  const handleSave = async () => {
63
53
  if (!groupID || !hasChanges)
64
54
  return;
65
- setIsConfirmOpen(false);
66
55
  setIsSaving(true);
67
56
  try {
68
57
  await setMemberRolePermissions(Object.assign({ groupID }, toRolePermissionChange(SWITCH_PERMISSION_KEYS, changes)));
@@ -80,15 +69,15 @@ const MemberPermissionPanel = ({ open, groupID, onClose }) => {
80
69
  if (!open)
81
70
  return null;
82
71
  if (isCrm) {
83
- return (_jsxs(_Fragment, { children: [_jsx(PermissionPanelCrm, { title: t("member_permission_row"), rows: SWITCHES.map((name) => ({
84
- key: name,
85
- label: t(SWITCH_LABELS[name]),
86
- description: t(SWITCH_DESCRIPTIONS[name]),
87
- checked: pending[name],
88
- disabled: isLoading,
89
- onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))),
90
- })), canSave: hasChanges && !isLoading, isSaving: isSaving, onBack: onClose, onSave: requestSave }), _jsx(DisableSendPermissionConfirm, { open: isConfirmOpen, title: t("disable_send_permission_title_all_members"), loading: isSaving, onConfirm: handleSave, onCancel: () => setIsConfirmOpen(false) })] }));
72
+ return (_jsx(PermissionPanelCrm, { title: t("member_permission_row"), rows: SWITCHES.map((name) => ({
73
+ key: name,
74
+ label: t(SWITCH_LABELS[name]),
75
+ description: t(SWITCH_DESCRIPTIONS[name]),
76
+ checked: pending[name],
77
+ disabled: isLoading,
78
+ onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))),
79
+ })), canSave: hasChanges && !isLoading, isSaving: isSaving, onBack: onClose, onSave: handleSave }));
91
80
  }
92
- return (_jsxs("div", { className: "absolute inset-0 bg-white flex flex-col z-10", "data-cp-name": "MemberPermissionPanel", children: [_jsxs("div", { className: "flex w-full items-center border-b border-gray-100 p-2", children: [_jsx("button", { type: "button", className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-[var(--dc-color-nav-icon)]", onClick: onClose, children: _jsx(Icon, { icon: "arrow-left-o", size: 24 }) }), _jsx("p", { className: "min-w-0 flex-1 truncate px-2 text-[18px] font-bold leading-[1.6] text-gray-900", children: t("member_permission_settings") })] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx("div", { className: "flex flex-col py-2", children: SWITCHES.map((name) => (_jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t(SWITCH_LABELS[name]) }), _jsx(Switch, { checked: pending[name], disabled: isLoading, onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))) })] }, name))) }) }) }), _jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-100", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full border border-gray-300 text-gray-700 text-sm font-medium", onClick: onClose, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full bg-[var(--dc-color-primary)] text-white text-sm font-medium disabled:bg-gray-200 disabled:text-gray-400", disabled: !hasChanges || isSaving || isLoading, onClick: requestSave, children: t("save") })] }), _jsx(DisableSendPermissionConfirm, { open: isConfirmOpen, title: t("disable_send_permission_title_all_members"), loading: isSaving, onConfirm: handleSave, onCancel: () => setIsConfirmOpen(false) })] }));
81
+ return (_jsxs("div", { className: "absolute inset-0 bg-white flex flex-col z-10", "data-cp-name": "MemberPermissionPanel", children: [_jsxs("div", { className: "flex w-full items-center border-b border-gray-100 p-2", children: [_jsx("button", { type: "button", className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-[var(--dc-color-nav-icon)]", onClick: onClose, children: _jsx(Icon, { icon: "arrow-left-o", size: 24 }) }), _jsx("p", { className: "min-w-0 flex-1 truncate px-2 text-[18px] font-bold leading-[1.6] text-gray-900", children: t("member_permission_settings") })] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx("div", { className: "flex flex-col py-2", children: SWITCHES.map((name) => (_jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [_jsx("span", { className: "text-sm", children: t(SWITCH_LABELS[name]) }), _jsx(Switch, { checked: pending[name], disabled: isLoading, onChange: (checked) => setEdits((prev) => (Object.assign(Object.assign({}, prev), { [name]: checked }))) })] }, name))) }) }) }), _jsxs("div", { className: "flex items-center justify-end gap-2 px-4 py-3 border-t border-gray-100", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full border border-gray-300 text-gray-700 text-sm font-medium", onClick: onClose, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-full bg-[var(--dc-color-primary)] text-white text-sm font-medium disabled:bg-gray-200 disabled:text-gray-400", disabled: !hasChanges || isSaving || isLoading, onClick: handleSave, children: t("save") })] })] }));
93
82
  };
94
83
  export default MemberPermissionPanel;
@@ -1,5 +1,12 @@
1
1
  import type { MenuProps } from "antd";
2
2
  import { MessageToolboxAction } from "./types";
3
3
  export declare const MESSAGE_TOOLBOX_OVERLAY_CLASSNAME: string;
4
+ interface ToolboxMenuRowProps {
5
+ icon: string;
6
+ label: string;
7
+ danger?: boolean;
8
+ }
9
+ export declare const ToolboxMenuRow: ({ icon, label, danger }: ToolboxMenuRowProps) => import("react/jsx-runtime").JSX.Element;
4
10
  export declare const buildMessageToolboxMenuItems: (actions: MessageToolboxAction[]) => NonNullable<MenuProps["items"]>;
11
+ export {};
5
12
  //# sourceMappingURL=MessageToolboxMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageToolboxMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/MessageToolboxMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAKtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,iCAAiC,QAS7C,CAAC;AAgDF,eAAO,MAAM,4BAA4B,GACvC,SAAS,oBAAoB,EAAE,KAC9B,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAO5B,CAAC"}
1
+ {"version":3,"file":"MessageToolboxMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/MessageToolboxMenu.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAKtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAE/C,eAAO,MAAM,iCAAiC,QAS7C,CAAC;AAEF,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAYD,eAAO,MAAM,cAAc,GAAI,yBAAiC,mBAAmB,4CA4BlF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,SAAS,oBAAoB,EAAE,KAC9B,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAO5B,CAAC"}
@@ -13,7 +13,7 @@ const CRM_ICONS = {
13
13
  "link-o": "link",
14
14
  "trash-o": "undo",
15
15
  };
16
- const ToolboxMenuRow = ({ icon, label, danger = false }) => {
16
+ export const ToolboxMenuRow = ({ icon, label, danger = false }) => {
17
17
  const isCrm = useAuthStore((state) => state.isCrm);
18
18
  const crmIcon = isCrm ? CRM_ICONS[icon] : undefined;
19
19
  return (_jsxs("div", { "data-cp-name": "ToolboxMenuRow", className: "flex items-center gap-3", children: [crmIcon ? (_jsx(StrokeIcon, { name: crmIcon, size: 18, className: danger ? "text-danger-600" : "text-gray-600" })) : (_jsx(Icon, { icon: icon, size: 24, className: danger ? "text-danger-400" : "text-gray-900" })), _jsx("span", { className: clsx("text-base font-medium", danger ? "text-danger-400" : "text-gray-900"), children: label })] }));
@@ -11,6 +11,7 @@ export interface MoreOptionConfig {
11
11
  key: string;
12
12
  icon: string;
13
13
  labelKey: string;
14
+ /** false = still in development: the composer "+" panel doesn't show it. */
14
15
  enabled: boolean;
15
16
  onSelect?: () => void;
16
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"moreOption.d.ts","sourceRoot":"","sources":["../../src/constants/moreOption.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;;;;CAOhB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAAgB,EAmBlD,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,YAAY,GAAE,gBAAgB,EAAO,GACpC,gBAAgB,EAAE,CAEpB;AAED,eAAO,MAAM,YAAY,EAAE,gBAAgB,EAmBzC,CAAC"}
1
+ {"version":3,"file":"moreOption.d.ts","sourceRoot":"","sources":["../../src/constants/moreOption.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa;;;;;;;CAOhB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAAgB,EAmBlD,CAAC;AAEF,wBAAgB,iBAAiB,CAC/B,YAAY,GAAE,gBAAgB,EAAO,GACpC,gBAAgB,EAAE,CAEpB;AAED,eAAO,MAAM,YAAY,EAAE,gBAAgB,EAmBzC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"useBotFlow.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useBotFlow.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAgB9B,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAMtD,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAsBD,eAAO,MAAM,UAAU,GAAI,kCAGxB,gBAAgB;;;;;;;;;;;;6BAuJqB,MAAM;;6BA0DN,eAAe;;;CAmDtD,CAAC"}
1
+ {"version":3,"file":"useBotFlow.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useBotFlow.ts"],"names":[],"mappings":"AAMA,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAgB9B,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,aAAa,CAAC;AAMtD,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAsBD,eAAO,MAAM,UAAU,GAAI,kCAGxB,gBAAgB;;;;;;;;;;;;6BAoKqB,MAAM;;6BA+DN,eAAe;;;CAmDtD,CAAC"}
@@ -104,9 +104,22 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
104
104
  buttons: inputButtonsBlocks.flatMap((block) => block.data.buttons),
105
105
  } }) : null;
106
106
  const isInActiveCsSession = isInCsSession({ flowCurrentStep: activeStep });
107
- const isProcessing = runMutation.isPending ||
107
+ // Also true while a chosen label waits in the send queue behind earlier uploads,
108
+ // so a second click cannot run the same step twice.
109
+ const [isChoosing, setIsChoosing] = useState(false);
110
+ const isProcessing = isChoosing ||
111
+ runMutation.isPending ||
108
112
  resetMutation.isPending ||
109
113
  closeSessionMutation.isPending;
114
+ const whileChoosing = async (action) => {
115
+ setIsChoosing(true);
116
+ try {
117
+ await action();
118
+ }
119
+ finally {
120
+ setIsChoosing(false);
121
+ }
122
+ };
110
123
  const shouldShowButtons = !!currentButtonsBlock && !isProcessing && !pendingGroupId;
111
124
  const status = (activeStep === null || activeStep === void 0 ? void 0 : activeStep.type) === BotFlowNodeType.Start ? "not_started" : "running";
112
125
  const runStep = async (step, actionId = null) => {
@@ -144,15 +157,17 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
144
157
  actionId,
145
158
  };
146
159
  const selectedButton = currentButtonsBlock === null || currentButtonsBlock === void 0 ? void 0 : currentButtonsBlock.data.buttons.find((button) => button.id === actionId);
147
- if (selectedButton) {
148
- await sendMergeMessage({
149
- plainText: selectedButton.label,
150
- richText: "",
151
- isInternal: false,
152
- flow,
153
- });
154
- }
155
- await runStep(activeStep, actionId);
160
+ await whileChoosing(async () => {
161
+ if (selectedButton) {
162
+ await sendMergeMessage({
163
+ plainText: selectedButton.label,
164
+ richText: "",
165
+ isInternal: false,
166
+ flow,
167
+ });
168
+ }
169
+ await runStep(activeStep, actionId);
170
+ });
156
171
  };
157
172
  const performRestart = async () => {
158
173
  var _a;
@@ -161,14 +176,16 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
161
176
  childId: null,
162
177
  actionId: BOT_DEFAULT_BUTTON_ID,
163
178
  };
164
- await sendMergeMessage({
165
- plainText: t("thread_detail.bot.restart"),
166
- richText: "",
167
- isInternal: false,
168
- flow,
179
+ await whileChoosing(async () => {
180
+ await sendMergeMessage({
181
+ plainText: t("thread_detail.bot.restart"),
182
+ richText: "",
183
+ isInternal: false,
184
+ flow,
185
+ });
186
+ const resetDetail = await resetMutation.mutateAsync(conversationId);
187
+ await runStep(resetDetail.flowCurrentStep);
169
188
  });
170
- const resetDetail = await resetMutation.mutateAsync(conversationId);
171
- await runStep(resetDetail.flowCurrentStep);
172
189
  };
173
190
  const restart = async () => {
174
191
  if (isInActiveCsSession) {
@@ -180,13 +197,15 @@ export const useBotFlow = ({ conversationId, isBotThread, }) => {
180
197
  const runMenuOption = async (item) => {
181
198
  if (!activeStep)
182
199
  return;
183
- await sendMergeMessage({
184
- plainText: item.label,
185
- richText: "",
186
- isInternal: false,
187
- flow: { groupId: activeStep.id, childId: null, actionId: item.id },
200
+ await whileChoosing(async () => {
201
+ await sendMergeMessage({
202
+ plainText: item.label,
203
+ richText: "",
204
+ isInternal: false,
205
+ flow: { groupId: activeStep.id, childId: null, actionId: item.id },
206
+ });
207
+ await runStep(activeStep, item.id);
188
208
  });
189
- await runStep(activeStep, item.id);
190
209
  };
191
210
  const selectMenuOption = async (item) => {
192
211
  if (item.id === BOT_DEFAULT_BUTTON_ID) {
@@ -0,0 +1,4 @@
1
+ export declare const useConversationTarget: (conversationId: string, enabled: boolean) => {
2
+ isTargetActive: boolean;
3
+ };
4
+ //# sourceMappingURL=useConversationTarget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConversationTarget.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/useConversationTarget.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,qBAAqB,GAChC,gBAAgB,MAAM,EACtB,SAAS,OAAO;;CAkBjB,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use client";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { apiInstance } from "../../services/api";
4
+ import { ENDPOINTS } from "../../services/routes";
5
+ import { QUERY_KEYS } from "../../services/query";
6
+ // Whether the peer account is still active. Fails open like biz-mobile:
7
+ // a pending or failed lookup counts as active.
8
+ export const useConversationTarget = (conversationId, enabled) => {
9
+ var _a;
10
+ const query = useQuery({
11
+ queryKey: [QUERY_KEYS.GET_CONVERSATION_TARGET, conversationId],
12
+ queryFn: async () => {
13
+ const res = await apiInstance.get(ENDPOINTS.chatService.getConversationTarget(conversationId));
14
+ return res.data.data;
15
+ },
16
+ enabled: enabled && !!conversationId,
17
+ staleTime: 0,
18
+ retry: false,
19
+ refetchOnWindowFocus: false,
20
+ });
21
+ // isError first: a failed refetch keeps the previous data in react-query.
22
+ return { isTargetActive: query.isError || ((_a = query.data) === null || _a === void 0 ? void 0 : _a.isActive) !== false };
23
+ };
@@ -0,0 +1,10 @@
1
+ import type { ConversationItem } from "@droppii-org/wasm-client-sdk";
2
+ /** Thread pin cap, checked client-side like biz-mobile. */
3
+ export declare const MAX_PINNED_THREADS = 5;
4
+ export declare const usePinConversation: () => {
5
+ togglePin: (conversation: ConversationItem) => Promise<void>;
6
+ isPending: boolean;
7
+ isLimitOpen: boolean;
8
+ closeLimit: () => void;
9
+ };
10
+ //# sourceMappingURL=usePinConversation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePinConversation.d.ts","sourceRoot":"","sources":["../../../src/hooks/conversation/usePinConversation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAKrE,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAMpC,eAAO,MAAM,kBAAkB;8BAUU,gBAAgB;;;;CAkCxD,CAAC"}
@@ -0,0 +1,52 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ import { message } from "antd";
4
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
5
+ import { useTranslation } from "react-i18next";
6
+ import { DChatSDK } from "../../constants/sdk";
7
+ import { QUERY_KEYS } from "../../services/query";
8
+ import useConversationStore from "../../store/conversation";
9
+ /** Thread pin cap, checked client-side like biz-mobile. */
10
+ export const MAX_PINNED_THREADS = 5;
11
+ const PIN_MUTATION_KEY = [QUERY_KEYS.PIN_CONVERSATION];
12
+ // Callers render from conversation.isPinned, which OnConversationChanged
13
+ // refreshes, so the label settles on the server state even if the call rejects.
14
+ export const usePinConversation = () => {
15
+ const { t } = useTranslation("conversation-inbox");
16
+ const [isLimitOpen, setIsLimitOpen] = useState(false);
17
+ const queryClient = useQueryClient();
18
+ const { mutateAsync, isPending } = useMutation({
19
+ mutationKey: PIN_MUTATION_KEY,
20
+ mutationFn: (params) => DChatSDK.pinConversation(params),
21
+ });
22
+ const togglePin = async (conversation) => {
23
+ // One pin at a time across rows: the cap is only checked here, against a
24
+ // store that catches up on OnConversationChanged after the call resolves.
25
+ const isAnyPinPending = queryClient.isMutating({ mutationKey: PIN_MUTATION_KEY }) > 0;
26
+ if (isAnyPinPending || !conversation.conversationID)
27
+ return;
28
+ const isPinned = !conversation.isPinned;
29
+ if (isPinned) {
30
+ const pinnedCount = useConversationStore
31
+ .getState()
32
+ .conversationList.filter((item) => item.isPinned).length;
33
+ if (pinnedCount >= MAX_PINNED_THREADS) {
34
+ setIsLimitOpen(true);
35
+ return;
36
+ }
37
+ }
38
+ try {
39
+ await mutateAsync({ conversationID: conversation.conversationID, isPinned });
40
+ message.success(t(isPinned ? "thread_detail.profile.pin_success" : "thread_detail.profile.unpin_success"));
41
+ }
42
+ catch (error) {
43
+ message.error(t("thread_detail.profile.pin_failed"));
44
+ }
45
+ };
46
+ return {
47
+ togglePin,
48
+ isPending,
49
+ isLimitOpen,
50
+ closeLimit: () => setIsLimitOpen(false),
51
+ };
52
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAiDA,eAAO,MAAM,cAAc,YAqnB1B,CAAC"}
1
+ {"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAiDA,eAAO,MAAM,cAAc,YAwnB1B,CAAC"}
@@ -431,6 +431,9 @@ export const useGlobalEvent = () => {
431
431
  });
432
432
  void refreshCurrentGroupState();
433
433
  }
434
+ void queryClient.invalidateQueries({
435
+ queryKey: [QUERY_KEYS.IS_JOINED_GROUP, data.groupID],
436
+ });
434
437
  };
435
438
  const setIMListener = () => {
436
439
  //account
@@ -1 +1 @@
1
- {"version":3,"file":"useJoinPublicGroup.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useJoinPublicGroup.ts"],"names":[],"mappings":"AAOA,UAAU,oBAAoB;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,eAAO,MAAM,kBAAkB,wGAa9B,CAAC"}
1
+ {"version":3,"file":"useJoinPublicGroup.d.ts","sourceRoot":"","sources":["../../../src/hooks/group/useJoinPublicGroup.ts"],"names":[],"mappings":"AAQA,UAAU,oBAAoB;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,eAAO,MAAM,kBAAkB,wGAoB9B,CAAC"}
@@ -1,11 +1,13 @@
1
1
  "use client";
2
- import { useMutation } from "@tanstack/react-query";
2
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
3
3
  import { GroupJoinSource } from "@droppii-org/wasm-client-sdk";
4
4
  import { DChatSDK } from "../../constants/sdk";
5
+ import { QUERY_KEYS } from "../../services/query";
5
6
  import emitter from "../../utils/events";
6
7
  // Joining straight from search results, matching chat-mobile's GroupItem —
7
8
  // GroupJoinSource.Search is what BE uses to attribute the join.
8
9
  export const useJoinPublicGroup = () => {
10
+ const queryClient = useQueryClient();
9
11
  return useMutation({
10
12
  mutationFn: async ({ groupID }) => {
11
13
  const { data } = await DChatSDK.joinGroup({
@@ -16,5 +18,11 @@ export const useJoinPublicGroup = () => {
16
18
  emitter.emit("GROUP_MEMBERS_CHANGED");
17
19
  return data;
18
20
  },
21
+ // Joining a group already cached locally fires no join event, so member-only UI reads this.
22
+ onSuccess: async (_data, { groupID }) => {
23
+ const queryKey = [QUERY_KEYS.IS_JOINED_GROUP, groupID];
24
+ await queryClient.cancelQueries({ queryKey });
25
+ queryClient.setQueryData(queryKey, true);
26
+ },
19
27
  });
20
28
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * In-thread search keeps the border 5 s and fades it in 0.8 s (CSS keyframes).
3
+ * Restarting the class lets a repeated jump start the timer over.
4
+ */
5
+ export declare const holdMessageHighlight: (element: HTMLElement, onDone: () => void) => void;
6
+ //# sourceMappingURL=holdMessageHighlight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"holdMessageHighlight.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/holdMessageHighlight.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,WAAW,EACpB,QAAQ,MAAM,IAAI,SAYnB,CAAC"}
@@ -0,0 +1,22 @@
1
+ const HELD_HIGHLIGHT_CLASSES = [
2
+ "border-blue-500",
3
+ "border",
4
+ "search-hit-highlight",
5
+ ];
6
+ /**
7
+ * In-thread search keeps the border 5 s and fades it in 0.8 s (CSS keyframes).
8
+ * Restarting the class lets a repeated jump start the timer over.
9
+ */
10
+ export const holdMessageHighlight = (element, onDone) => {
11
+ element.classList.remove(...HELD_HIGHLIGHT_CLASSES);
12
+ void element.offsetWidth;
13
+ element.classList.add(...HELD_HIGHLIGHT_CLASSES);
14
+ const onEnd = (event) => {
15
+ if (event.animationName !== "search-hit-fade")
16
+ return;
17
+ element.classList.remove(...HELD_HIGHLIGHT_CLASSES);
18
+ element.removeEventListener("animationend", onEnd);
19
+ onDone();
20
+ };
21
+ element.addEventListener("animationend", onEnd);
22
+ };
@@ -23,6 +23,7 @@ export declare const useMessage: (conversationId?: string, searchClientMsgID?: s
23
23
  moreNewLoading: boolean;
24
24
  getMoreNewMessages: (loadMore?: any) => Promise<void>;
25
25
  refreshLatestMessages: () => Promise<void>;
26
+ notifyRevokedTarget: (clientMsgID: string) => void;
26
27
  };
27
28
  export declare const pushNewMessage: (message: MessageItem) => void;
28
29
  export declare const updateOneMessage: (message: MessageItem) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessage.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EACX,WAAW,EAEZ,MAAM,8BAA8B,CAAC;AAkBtC,eAAO,MAAM,kBAAkB,eAkB9B,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,WAAW,EACpB,OAAO,OAAO,EACd,eAAe;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5E,eAAe,MAAM,KACpB,OA4BF,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,iBAAiB,MAAM,EACvB,oBAAoB,MAAM,EAC1B,iBAAc,EACd,uBAAqB,EAGrB,yBAAyB,MAAM,IAAI;;;;;qBAed,WAAW,EAAE;;;;;;qBAAb,WAAW,EAAE;;;;;;;CAoZnC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,SACpB,CAAC;AAChC,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,SACpB,CAAC;AAClC,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,SACf,CAAC;AAEtC,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,EAC1B,YAAY,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,KAC9C,WAAW,GAAG,SAqBhB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,KACzB,WAAW,GAAG,SAQhB,CAAC;AAmBF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,WAAW,EAAE,EACtB,SAAS,WAAW,EAAE,kBAgBvB,CAAC"}
1
+ {"version":3,"file":"useMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessage.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EACX,WAAW,EAEZ,MAAM,8BAA8B,CAAC;AAkBtC,eAAO,MAAM,kBAAkB,eAkB9B,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,WAAW,EACpB,OAAO,OAAO,EACd,eAAe;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5E,eAAe,MAAM,KACpB,OA4BF,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,iBAAiB,MAAM,EACvB,oBAAoB,MAAM,EAC1B,iBAAc,EACd,uBAAqB,EAGrB,yBAAyB,MAAM,IAAI;;;;;qBAed,WAAW,EAAE;;;;;;qBAAb,WAAW,EAAE;;;;;;;uCAwBlB,MAAM;CAmYvB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,SAAS,WAAW,SACpB,CAAC;AAChC,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,SACpB,CAAC;AAClC,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,SACf,CAAC;AAEtC,eAAO,MAAM,kBAAkB,GAC7B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,EAC1B,YAAY,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,KAC9C,WAAW,GAAG,SAqBhB,CAAC;AAMF,eAAO,MAAM,oBAAoB,GAC/B,aAAa,WAAW,EAAE,EAC1B,SAAS,WAAW,EACpB,WAAW,MAAM,GAAG,MAAM,KACzB,WAAW,GAAG,SAQhB,CAAC;AAmBF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,WAAW,EAAE,EACtB,SAAS,WAAW,EAAE,kBAgBvB,CAAC"}