@droppii-org/chat-sdk 0.2.1 → 0.3.1

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 (67) hide show
  1. package/dist/components/conversation/ConversationByGroupItem.d.ts +5 -3
  2. package/dist/components/conversation/ConversationByGroupItem.d.ts.map +1 -1
  3. package/dist/components/conversation/ConversationByGroupItem.js +24 -35
  4. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  5. package/dist/components/conversation/DeskConversationList.js +4 -3
  6. package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
  7. package/dist/components/conversation-detail/ConversationMessageList.js +4 -1
  8. package/dist/components/group/AddMemberModal.d.ts +1 -2
  9. package/dist/components/group/AddMemberModal.d.ts.map +1 -1
  10. package/dist/components/group/AddMemberModal.js +2 -2
  11. package/dist/components/group/GroupCompositeAvatar.d.ts +11 -0
  12. package/dist/components/group/GroupCompositeAvatar.d.ts.map +1 -0
  13. package/dist/components/group/GroupCompositeAvatar.js +40 -0
  14. package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
  15. package/dist/components/message/GroupMembersDrawer.js +22 -15
  16. package/dist/components/message/MessageHeader.d.ts.map +1 -1
  17. package/dist/components/message/MessageHeader.js +2 -1
  18. package/dist/components/message/MessageList.d.ts.map +1 -1
  19. package/dist/components/message/MessageList.js +31 -2
  20. package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
  21. package/dist/components/thread/GroupThreadInfo.js +4 -3
  22. package/dist/constants/im.d.ts +13 -0
  23. package/dist/constants/im.d.ts.map +1 -1
  24. package/dist/constants/im.js +16 -0
  25. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  26. package/dist/hooks/global/useGlobalEvent.js +10 -2
  27. package/dist/hooks/group/useConversationGroups.d.ts +20 -0
  28. package/dist/hooks/group/useConversationGroups.d.ts.map +1 -0
  29. package/dist/hooks/group/useConversationGroups.js +116 -0
  30. package/dist/hooks/group/useCreateGroup.d.ts.map +1 -1
  31. package/dist/hooks/group/useCreateGroup.js +38 -7
  32. package/dist/hooks/group/useEligibleAccountsForGroup.d.ts +1 -1
  33. package/dist/hooks/group/useEligibleAccountsForGroup.d.ts.map +1 -1
  34. package/dist/hooks/group/useEligibleAccountsForGroup.js +6 -9
  35. package/dist/hooks/group/useGetGroupCandidates.d.ts +2 -1
  36. package/dist/hooks/group/useGetGroupCandidates.d.ts.map +1 -1
  37. package/dist/hooks/group/useGetGroupCandidates.js +3 -2
  38. package/dist/hooks/group/useGroupMemberActions.d.ts +6 -0
  39. package/dist/hooks/group/useGroupMemberActions.d.ts.map +1 -1
  40. package/dist/hooks/group/useGroupMemberActions.js +12 -0
  41. package/dist/hooks/group/useGroupPermission.d.ts +3 -1
  42. package/dist/hooks/group/useGroupPermission.d.ts.map +1 -1
  43. package/dist/hooks/group/useGroupPermission.js +33 -37
  44. package/dist/hooks/session/useSessionLatestPreview.d.ts +6 -2
  45. package/dist/hooks/session/useSessionLatestPreview.d.ts.map +1 -1
  46. package/dist/locales/vi/common.json +14 -0
  47. package/dist/services/query.d.ts +1 -1
  48. package/dist/services/query.js +1 -1
  49. package/dist/services/routes.d.ts +1 -0
  50. package/dist/services/routes.d.ts.map +1 -1
  51. package/dist/services/routes.js +1 -0
  52. package/dist/store/conversation.d.ts.map +1 -1
  53. package/dist/store/conversation.js +25 -1
  54. package/dist/store/type.d.ts +1 -0
  55. package/dist/store/type.d.ts.map +1 -1
  56. package/dist/styles/global.css +1 -1
  57. package/dist/types/dto.d.ts +8 -0
  58. package/dist/types/dto.d.ts.map +1 -1
  59. package/dist/utils/common.d.ts.map +1 -1
  60. package/dist/utils/common.js +20 -2
  61. package/dist/utils/messageLog.d.ts +4 -0
  62. package/dist/utils/messageLog.d.ts.map +1 -1
  63. package/dist/utils/messageLog.js +113 -0
  64. package/package.json +2 -2
  65. package/dist/hooks/group/useJoinedGroups.d.ts +0 -13
  66. package/dist/hooks/group/useJoinedGroups.d.ts.map +0 -1
  67. package/dist/hooks/group/useJoinedGroups.js +0 -33
@@ -1,7 +1,9 @@
1
- import { GroupItem } from "@droppii-org/wasm-client-sdk";
1
+ import type { LatestPreview } from "../../hooks/session/useSessionLatestPreview";
2
+ import type { ConversationGroupDisplayItem } from "../../hooks/group/useConversationGroups";
2
3
  interface ConversationByGroupItemProps {
3
- group: GroupItem;
4
+ group: ConversationGroupDisplayItem;
5
+ latestPreview?: LatestPreview;
4
6
  }
5
- declare const ConversationByGroupItem: ({ group }: ConversationByGroupItemProps) => import("react/jsx-runtime").JSX.Element;
7
+ declare const ConversationByGroupItem: ({ group, latestPreview, }: ConversationByGroupItemProps) => import("react/jsx-runtime").JSX.Element;
6
8
  export default ConversationByGroupItem;
7
9
  //# sourceMappingURL=ConversationByGroupItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationByGroupItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationByGroupItem.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,SAAS,EAAe,MAAM,8BAA8B,CAAC;AAMtE,UAAU,4BAA4B;IACpC,KAAK,EAAE,SAAS,CAAC;CAClB;AAED,QAAA,MAAM,uBAAuB,GAAI,WAAW,4BAA4B,4CAiEvE,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationByGroupItem.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/ConversationByGroupItem.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AAOxF,UAAU,4BAA4B;IACpC,KAAK,EAAE,4BAA4B,CAAC;IACpC,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,QAAA,MAAM,uBAAuB,GAAI,2BAG9B,4BAA4B,4CA8D9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -1,50 +1,39 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useState } from "react";
4
- import { Avatar } from "antd";
5
3
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
6
4
  import { useTranslation } from "react-i18next";
7
- import { SessionType } from "@droppii-org/wasm-client-sdk";
8
5
  import useConversationStore from "../../store/conversation";
9
- import { DChatSDK } from "../../constants/sdk";
10
- import { formatTimestamp } from "../../utils/common";
6
+ import { useChatContext } from "../../context/ChatContext";
7
+ import { formatTimestamp, parseLatestMessage } from "../../utils/common";
8
+ import GroupCompositeAvatar from "../../components/group/GroupCompositeAvatar";
11
9
  import ThreadCard from "./ThreadCard";
12
- const ConversationByGroupItem = ({ group }) => {
13
- var _a;
10
+ const ConversationByGroupItem = ({ group, latestPreview, }) => {
14
11
  const { t } = useTranslation();
15
- const [isOpening, setIsOpening] = useState(false);
12
+ const user = useChatContext().user;
16
13
  const router = useRouter();
17
14
  const pathname = usePathname();
18
15
  const searchParams = useSearchParams();
19
16
  const setConversationData = useConversationStore((state) => state.setConversationData);
20
17
  const setSelectedConversationId = useConversationStore((state) => state.setSelectedConversationId);
21
- const updateConversationList = useConversationStore((state) => state.updateConversationList);
22
- const isSelected = useConversationStore((state) => state.selectedSourceId === group.groupID);
23
- const handleClick = async () => {
24
- if (isOpening)
25
- return;
26
- setIsOpening(true);
27
- try {
28
- const { data: conversation } = await DChatSDK.getOneConversation({
29
- sourceID: group.groupID,
30
- sessionType: SessionType.Group,
31
- });
32
- setConversationData(conversation);
33
- updateConversationList([conversation], "filter");
34
- setSelectedConversationId(conversation.conversationID);
35
- const newSearchParams = new URLSearchParams(searchParams);
36
- newSearchParams.set("threadId", conversation.conversationID);
37
- newSearchParams.delete("userId");
38
- newSearchParams.delete("groupId");
39
- newSearchParams.delete("botId");
40
- router.push(`${pathname}?${newSearchParams.toString()}`);
41
- }
42
- finally {
43
- setIsOpening(false);
44
- }
18
+ const isSelected = useConversationStore((state) => state.selectedConversationId === group.conversationId);
19
+ const { conversation } = group;
20
+ const handleClick = () => {
21
+ setConversationData(conversation);
22
+ setSelectedConversationId(conversation.conversationID);
23
+ const newSearchParams = new URLSearchParams(searchParams);
24
+ newSearchParams.set("threadId", conversation.conversationID);
25
+ newSearchParams.delete("userId");
26
+ newSearchParams.delete("groupId");
27
+ newSearchParams.delete("botId");
28
+ router.push(`${pathname}?${newSearchParams.toString()}`);
45
29
  };
46
- return (_jsx(ThreadCard, { onClick: handleClick, isSelected: isSelected, preview: t("member_count", { count: group.memberCount }), time: group.createTime
47
- ? formatTimestamp(group.createTime, { hasTime: true })
48
- : undefined, avatar: _jsx(Avatar, { size: 48, src: group.faceURL, children: ((_a = group.groupName) === null || _a === void 0 ? void 0 : _a.charAt(0)) || "G" }), title: _jsx("h3", { className: "truncate text-sm font-semibold text-gray-900", children: group.groupName }) }));
30
+ const latestMsg = (latestPreview === null || latestPreview === void 0 ? void 0 : latestPreview.latestMsg) || "";
31
+ const latestMsgSendTime = (latestPreview === null || latestPreview === void 0 ? void 0 : latestPreview.latestMsgSendTime) || 0;
32
+ return (_jsx(ThreadCard, { onClick: handleClick, isSelected: isSelected, unreadCount: conversation.unreadCount, preview: parseLatestMessage(latestMsg, user === null || user === void 0 ? void 0 : user.userID, t, {
33
+ peerType: conversation.peerType,
34
+ showName: conversation.showName,
35
+ }), time: latestMsgSendTime > 0
36
+ ? formatTimestamp(latestMsgSendTime, { hasTime: true })
37
+ : undefined, avatar: _jsx(GroupCompositeAvatar, { groupID: group.groupId, faceURL: conversation.faceURL, groupName: conversation.showName, size: 48 }), title: _jsx("h3", { className: "truncate text-sm font-semibold text-gray-900", children: conversation.showName }) }));
49
38
  };
50
39
  export default ConversationByGroupItem;
@@ -1 +1 @@
1
- {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAwRzB,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,+CA+RzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -16,7 +16,7 @@ import { useGetSession } from "../../hooks/session/useGetSession";
16
16
  import { useSortedSessions } from "../../hooks/session/useSortedSessions";
17
17
  import { useSessionLatestPreview } from "../../hooks/session/useSessionLatestPreview";
18
18
  import { useGetTeamSessionSummary } from "../../hooks/session/useGetTeamSessionSummary";
19
- import { useJoinedGroups } from "../../hooks/group/useJoinedGroups";
19
+ import { useConversationGroups } from "../../hooks/group/useConversationGroups";
20
20
  import { DChatSDK } from "../../constants/sdk";
21
21
  import emitter from "../../utils/events";
22
22
  import { ALL_GROUPS_KEY, getFilterConfig, } from "../../components/session/sessionMenuItems";
@@ -38,9 +38,10 @@ const DeskConversationList = () => {
38
38
  const isAllGroupsView = viewMode === "allGroups";
39
39
  const { data: teamSummaries } = useGetTeamSessionSummary();
40
40
  const { dataFlatten: rawSessions, hasNextPage, fetchNextPage, refetch, } = useGetSession({ filter: filterSummary, options: { isEnabled: !isAllGroupsView } });
41
- const { groups: joinedGroups, fetchNextPage: fetchNextGroupsPage, hasNextPage: hasNextGroupsPage, isLoading: isJoinedGroupsLoading, } = useJoinedGroups({ enabled: isAllGroupsView });
41
+ const { groups: conversationGroups, fetchNextPage: fetchNextGroupsPage, hasNextPage: hasNextGroupsPage, isLoading: isConversationGroupsLoading, } = useConversationGroups({ enabled: isAllGroupsView });
42
42
  const latestPreviewMap = useSessionLatestPreview(rawSessions);
43
43
  const sessions = useSortedSessions(rawSessions, latestPreviewMap);
44
+ const groupLatestPreviewMap = useSessionLatestPreview(conversationGroups);
44
45
  const currentFilterKey = isAllGroupsView
45
46
  ? ALL_GROUPS_KEY
46
47
  : filterSummary.status || filterSummary.tag || "";
@@ -104,7 +105,7 @@ const DeskConversationList = () => {
104
105
  height: "100%",
105
106
  overflow: showSearch ? "hidden" : "auto",
106
107
  position: "relative",
107
- }, children: [isAllGroupsView ? (isJoinedGroupsLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : !(joinedGroups === null || joinedGroups === void 0 ? void 0 : joinedGroups.length) ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : (_jsx(InfiniteScroll, { dataLength: joinedGroups.length, next: fetchNextGroupsPage, hasMore: !!hasNextGroupsPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: joinedGroups.map((group) => (_jsx(ConversationByGroupItem, { group: group }, group.groupID))) }))) : (_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: (sessions === null || sessions === void 0 ? void 0 : sessions.length) === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : ((_b = sessions === null || sessions === void 0 ? void 0 : sessions.map) === null || _b === void 0 ? void 0 : _b.call(sessions, (session) => (_jsx(ConversationBySessionItem, { sessionItem: session, latestPreview: latestPreviewMap.get(session.conversationId) }, session.conversationId)))) })), _jsx(Drawer, { open: showSearch, mask: false, closeIcon: false, styles: {
108
+ }, children: [isAllGroupsView ? (isConversationGroupsLoading ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Spin, {}) })) : !(conversationGroups === null || conversationGroups === void 0 ? void 0 : conversationGroups.length) ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : (_jsx(InfiniteScroll, { dataLength: conversationGroups.length, next: fetchNextGroupsPage, hasMore: !!hasNextGroupsPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: conversationGroups.map((group) => (_jsx(ConversationByGroupItem, { group: group, latestPreview: groupLatestPreviewMap.get(group.conversationId) }, group.groupId))) }))) : (_jsx(InfiniteScroll, { dataLength: (sessions === null || sessions === void 0 ? void 0 : sessions.length) || 0, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableConversationsDiv", children: (sessions === null || sessions === void 0 ? void 0 : sessions.length) === 0 ? (_jsx("div", { className: "flex items-center justify-center py-12", children: _jsx(Empty, { image: _jsx(Icon, { icon: "chat-square-b", size: 80, className: "text-gray-300" }), description: t("no_conversation") }) })) : ((_b = sessions === null || sessions === void 0 ? void 0 : sessions.map) === null || _b === void 0 ? void 0 : _b.call(sessions, (session) => (_jsx(ConversationBySessionItem, { sessionItem: session, latestPreview: latestPreviewMap.get(session.conversationId) }, session.conversationId)))) })), _jsx(Drawer, { open: showSearch, mask: false, closeIcon: false, styles: {
108
109
  body: {
109
110
  padding: 0,
110
111
  },
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationMessageList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationMessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAkBzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,QAAA,MAAM,uBAAuB,GAAI,gBAAgB,4BAA4B,4CAuL5E,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationMessageList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationMessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAmBzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,QAAA,MAAM,uBAAuB,GAAI,gBAAgB,4BAA4B,4CAyL5E,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -10,6 +10,7 @@ import { useMessage } from "../../hooks/message/useMessage";
10
10
  import { useMessageListScroll } from "../../hooks/message/useMessageListScroll";
11
11
  import { usePinMessage } from "../../hooks/message/usePinMessage";
12
12
  import { useRevokeMessage } from "../../hooks/message/useRevokeMessage";
13
+ import { useGroupPermission } from "../../hooks/group/useGroupPermission";
13
14
  import useConversationStore from "../../store/conversation";
14
15
  import useAuthStore from "../../store/auth";
15
16
  import { isGroupChat as resolveIsGroupChat } from "../../utils/conversation";
@@ -28,6 +29,8 @@ const ConversationMessageList = ({ bottomSlot }) => {
28
29
  const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
29
30
  const conversationId = selectedConversationId || (conversation === null || conversation === void 0 ? void 0 : conversation.conversationID) || "";
30
31
  const isGroupChat = conversation ? resolveIsGroupChat(conversation) : false;
32
+ const permissions = useGroupPermission(conversation === null || conversation === void 0 ? void 0 : conversation.groupID);
33
+ const canPinMessage = !isGroupChat || permissions.pinMessage;
31
34
  const messageController = useMessage(conversationId, searchClientMsgID, true, true);
32
35
  const { scrollRef, loadMoreOldMessage, handleInfiniteScroll, scrollToBottomIfAtBottom, scrollToMessage, } = useMessageListScroll(Object.assign({ conversationId, userId: (user === null || user === void 0 ? void 0 : user.userID) || authUserID }, messageController));
33
36
  const { loadState, moreOldLoading } = messageController;
@@ -92,6 +95,6 @@ const ConversationMessageList = ({ bottomSlot }) => {
92
95
  target: scrollRef,
93
96
  enable: !!openToolboxMessageId,
94
97
  });
95
- return (_jsxs("div", { "data-testid": "conversation-message-list", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: [isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsxs(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableConversationMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: [bottomSlot, loadState.messageList.map((message, index, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, forceHideTip: index === 0 && !!bottomSlot, contextMenuOpen: openToolboxMessageId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenToolboxMessageId(open ? message.clientMsgID : null), onQuoteMessage: setQuotedMessage, onRevokeMessage: handleRevokeMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: handlePinMessage, onUnpinMessage: handleUnpinMessage, enableLongPress: true }, message.clientMsgID)))] })), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
98
+ return (_jsxs("div", { "data-testid": "conversation-message-list", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: [isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsxs(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableConversationMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: [bottomSlot, loadState.messageList.map((message, index, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, forceHideTip: index === 0 && !!bottomSlot, contextMenuOpen: openToolboxMessageId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenToolboxMessageId(open ? message.clientMsgID : null), onQuoteMessage: setQuotedMessage, onRevokeMessage: handleRevokeMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, enableLongPress: true }, message.clientMsgID)))] })), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
96
99
  };
97
100
  export default ConversationMessageList;
@@ -1,9 +1,8 @@
1
1
  interface AddMemberModalProps {
2
2
  open: boolean;
3
3
  groupID: string;
4
- existingMemberIDs: string[];
5
4
  onClose: () => void;
6
5
  }
7
- declare const AddMemberModal: ({ open, groupID, existingMemberIDs, onClose, }: AddMemberModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const AddMemberModal: ({ open, groupID, onClose, }: AddMemberModalProps) => import("react/jsx-runtime").JSX.Element;
8
7
  export default AddMemberModal;
9
8
  //# sourceMappingURL=AddMemberModal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AddMemberModal.d.ts","sourceRoot":"","sources":["../../../src/components/group/AddMemberModal.tsx"],"names":[],"mappings":"AAYA,UAAU,mBAAmB;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,cAAc,GAAI,gDAKrB,mBAAmB,4CA2LrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"AddMemberModal.d.ts","sourceRoot":"","sources":["../../../src/components/group/AddMemberModal.tsx"],"names":[],"mappings":"AAYA,UAAU,mBAAmB;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,cAAc,GAAI,6BAIrB,mBAAmB,4CA2LrB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -8,13 +8,13 @@ import { useTranslation } from "react-i18next";
8
8
  import { Icon } from "../../components/icon";
9
9
  import { useEligibleAccountsForGroup } from "../../hooks/group/useEligibleAccountsForGroup";
10
10
  import { useAddGroupMember } from "../../hooks/group/useAddGroupMember";
11
- const AddMemberModal = ({ open, groupID, existingMemberIDs, onClose, }) => {
11
+ const AddMemberModal = ({ open, groupID, onClose, }) => {
12
12
  const { t } = useTranslation("conversation-inbox");
13
13
  const { t: tCommon } = useTranslation();
14
14
  const [search, setSearch] = useState("");
15
15
  const [selectedMembers, setSelectedMembers] = useState([]);
16
16
  const debouncedSearch = useDebounce(search, { wait: 300 });
17
- const { accounts, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useEligibleAccountsForGroup(debouncedSearch, existingMemberIDs, open);
17
+ const { accounts, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = useEligibleAccountsForGroup(debouncedSearch, groupID, open);
18
18
  const { mutateAsync: addGroupMember, isPending: isSubmitting } = useAddGroupMember();
19
19
  const selectedIds = new Set(selectedMembers.map((member) => member.userID));
20
20
  const toggleMember = (account) => {
@@ -0,0 +1,11 @@
1
+ interface GroupCompositeAvatarProps {
2
+ groupID?: string;
3
+ faceURL?: string;
4
+ ownerUserID?: string;
5
+ groupName?: string;
6
+ size?: number;
7
+ className?: string;
8
+ }
9
+ declare const GroupCompositeAvatar: ({ groupID, faceURL, ownerUserID, groupName, size, className, }: GroupCompositeAvatarProps) => import("react/jsx-runtime").JSX.Element;
10
+ export default GroupCompositeAvatar;
11
+ //# sourceMappingURL=GroupCompositeAvatar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GroupCompositeAvatar.d.ts","sourceRoot":"","sources":["../../../src/components/group/GroupCompositeAvatar.tsx"],"names":[],"mappings":"AAOA,UAAU,yBAAyB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAOD,QAAA,MAAM,oBAAoB,GAAI,gEAO3B,yBAAyB,4CAyD3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Avatar } from "antd";
4
+ import { GroupMemberRole } from "@droppii-org/wasm-client-sdk";
5
+ import { useMemo } from "react";
6
+ import { useGroupMembers } from "../../hooks/group/useGroupMembers";
7
+ // AC-Default2 (DROPPII-29976): when a group is created without a picked
8
+ // avatar, show two overlapping avatars (owner bottom-left, first-joined
9
+ // non-owner member top-right) instead of a static placeholder image —
10
+ // resolved client-side so it always reflects current member avatars,
11
+ // no image compositing/upload needed.
12
+ const GroupCompositeAvatar = ({ groupID, faceURL, ownerUserID, groupName, size = 56, className, }) => {
13
+ var _a, _b;
14
+ const shouldLoadMembers = !faceURL && Boolean(groupID);
15
+ const { members } = useGroupMembers(shouldLoadMembers ? groupID : undefined);
16
+ const { ownerFaceURL, firstMemberFaceURL } = useMemo(() => {
17
+ var _a;
18
+ if (!members.length)
19
+ return { ownerFaceURL: undefined, firstMemberFaceURL: undefined };
20
+ // Prefer the explicit ownerUserID when the caller has it (e.g. GroupItem);
21
+ // otherwise fall back to roleLevel, which every member list already carries.
22
+ const owner = (_a = members.find((member) => member.userID === ownerUserID)) !== null && _a !== void 0 ? _a : members.find((member) => member.roleLevel === GroupMemberRole.Owner);
23
+ const firstMember = [...members]
24
+ .filter((member) => member.userID !== (owner === null || owner === void 0 ? void 0 : owner.userID))
25
+ .sort((a, b) => a.joinTime - b.joinTime)[0];
26
+ return {
27
+ ownerFaceURL: owner === null || owner === void 0 ? void 0 : owner.faceURL,
28
+ firstMemberFaceURL: firstMember === null || firstMember === void 0 ? void 0 : firstMember.faceURL,
29
+ };
30
+ }, [members, ownerUserID]);
31
+ if (faceURL) {
32
+ return (_jsx(Avatar, { src: faceURL, size: size, className: className, children: ((_a = groupName === null || groupName === void 0 ? void 0 : groupName.charAt) === null || _a === void 0 ? void 0 : _a.call(groupName, 0)) || "G" }));
33
+ }
34
+ if (!shouldLoadMembers || (!ownerFaceURL && !firstMemberFaceURL)) {
35
+ return (_jsx(Avatar, { size: size, className: className, children: ((_b = groupName === null || groupName === void 0 ? void 0 : groupName.charAt) === null || _b === void 0 ? void 0 : _b.call(groupName, 0)) || "G" }));
36
+ }
37
+ const subSize = Math.round(size * 0.62);
38
+ return (_jsxs("div", { className: className, style: { position: "relative", width: size, height: size, flexShrink: 0 }, children: [_jsx(Avatar, { src: ownerFaceURL, size: subSize, style: { position: "absolute", left: 0, bottom: 0, border: "2px solid white" } }), _jsx(Avatar, { src: firstMemberFaceURL, size: subSize, style: { position: "absolute", right: 0, top: 0, border: "2px solid white" } })] }));
39
+ };
40
+ export default GroupCompositeAvatar;
@@ -1 +1 @@
1
- {"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAe/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA4CD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,4CAuezB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAgB/C,UAAU,uBAAuB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AA0CD,QAAA,MAAM,kBAAkB,GAAI,wDAKzB,uBAAuB,4CA6ezB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -11,11 +11,11 @@ import { useGroupMembers } from "../../hooks/group/useGroupMembers";
11
11
  import { useSearchGroupMembers } from "../../hooks/group/useSearchGroupMembers";
12
12
  import { useLeaveConversation } from "../../hooks/session/useLeaveConversation";
13
13
  import { useGroupPermission } from "../../hooks/group/useGroupPermission";
14
- import { useKickGroupMember, useMuteGroupMember, useSetGroupMemberRole, useTransferGroupOwner, } from "../../hooks/group/useGroupMemberActions";
14
+ import { GROUP_PERMISSION_KEY } from "../../constants/im";
15
+ import { useKickGroupMember, useSetGroupMemberPermissions, useSetGroupMemberRole, useTransferGroupOwner, } from "../../hooks/group/useGroupMemberActions";
15
16
  import { Icon } from "../../components/icon";
16
17
  import AddMemberModal from "../../components/group/AddMemberModal";
17
18
  import AdminPermissionModal from "../../components/group/AdminPermissionModal";
18
- const isMemberMuted = (member) => Boolean(member.muteEndTime) && member.muteEndTime * 1000 > Date.now();
19
19
  const getMemberDisplayName = (member) => {
20
20
  return member.nickname || member.userID || "";
21
21
  };
@@ -43,17 +43,15 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
43
43
  const debouncedSearch = useDebounce(search, { wait: 300 });
44
44
  const authUserID = useAuthStore((state) => state.userID);
45
45
  const conversationData = useConversationStore((state) => state.conversationData);
46
- const currentMemberInGroup = useConversationStore((state) => state.currentMemberInGroup);
47
46
  const { members, isLoading, error, refetch } = useGroupMembers(groupID);
48
47
  const { results: searchResults, isSearching } = useSearchGroupMembers(groupID, debouncedSearch);
49
48
  const { mutateAsync: leaveConversation, isPending: isLeaving } = useLeaveConversation();
50
49
  const permissions = useGroupPermission(groupID);
51
- const { mutateAsync: muteMember } = useMuteGroupMember();
50
+ const { mutateAsync: setMemberPermissions } = useSetGroupMemberPermissions();
52
51
  const { mutateAsync: kickMember } = useKickGroupMember();
53
52
  const { mutateAsync: setMemberRole } = useSetGroupMemberRole();
54
53
  const { mutateAsync: transferOwner } = useTransferGroupOwner();
55
- const canAddMember = (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Admin ||
56
- (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Owner;
54
+ const canAddMember = permissions.addMember;
57
55
  let customerUserID = "";
58
56
  try {
59
57
  const exConversationInfo = JSON.parse((conversationData === null || conversationData === void 0 ? void 0 : conversationData.ex) || "{}");
@@ -67,8 +65,10 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
67
65
  const sortedMembers = useMemo(() => {
68
66
  return [...sourceMembers]
69
67
  .map((member) => {
68
+ var _a;
70
69
  const isOwner = member.userID === customerUserID ||
71
70
  member.roleLevel === GroupMemberRole.Owner;
71
+ const permissions = ((_a = member.permissions) !== null && _a !== void 0 ? _a : []);
72
72
  return {
73
73
  userID: member.userID,
74
74
  nickname: getMemberDisplayName(member),
@@ -77,7 +77,8 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
77
77
  isCurrentUser: member.userID === authUserID,
78
78
  isOwner,
79
79
  isAdmin: member.roleLevel === GroupMemberRole.Admin,
80
- isMuted: isMemberMuted(member),
80
+ permissions,
81
+ isSendMessageDisabled: !permissions.includes(GROUP_PERMISSION_KEY.sendMessage),
81
82
  // Mockup only shows a role label under the owner/creator row —
82
83
  // Admin/Member rows show name only.
83
84
  roleLabel: isOwner
@@ -119,17 +120,21 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
119
120
  }
120
121
  setShowLeaveConfirm(true);
121
122
  };
122
- const handleToggleMuteMember = async (member) => {
123
+ const handleToggleMemberSendMessage = async (member) => {
123
124
  var _a, _b;
124
125
  if (!groupID)
125
126
  return;
127
+ const sendMessageKey = GROUP_PERMISSION_KEY.sendMessage;
128
+ const nextPermissions = member.isSendMessageDisabled
129
+ ? [...member.permissions, sendMessageKey]
130
+ : member.permissions.filter((permission) => permission !== sendMessageKey);
126
131
  try {
127
- await muteMember({
132
+ await setMemberPermissions({
128
133
  groupID,
129
134
  userID: member.userID,
130
- mute: !member.isMuted,
135
+ permissions: nextPermissions,
131
136
  });
132
- message.success(member.isMuted
137
+ message.success(member.isSendMessageDisabled
133
138
  ? t("unmute_member_success")
134
139
  : t("mute_member_success"));
135
140
  void refetch();
@@ -249,10 +254,12 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
249
254
  },
250
255
  {
251
256
  key: "mute",
252
- label: member.isMuted ? t("unmute_member") : t("mute_member"),
257
+ label: member.isSendMessageDisabled
258
+ ? t("unmute_member")
259
+ : t("mute_member"),
253
260
  icon: _jsx(Icon, { icon: "bell-off-b", size: 16 }),
254
261
  disabled: !permissions.removeMember,
255
- onClick: () => void handleToggleMuteMember(member),
262
+ onClick: () => void handleToggleMemberSendMessage(member),
256
263
  },
257
264
  {
258
265
  key: "transfer-owner",
@@ -311,10 +318,10 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
311
318
  padding: 0,
312
319
  height: "100%",
313
320
  },
314
- }, getContainer: false, width: 360, children: _jsxs("div", { className: "flex flex-col h-full bg-white py-2", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("members") }), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleCloseDrawer, children: _jsx(Icon, { icon: "close-b", size: 22 }) })] }), _jsxs("div", { className: "flex items-center gap-2 px-4 py-3", children: [_jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: t("search_member_placeholder"), prefix: _jsx(Icon, { icon: "search-o", size: 16, className: "text-gray-400" }), className: "h-10" }), canAddMember && (_jsx(Button, { type: "primary", shape: "default", className: "w-10 h-10 p-0 shrink-0 flex items-center justify-center rounded-lg", onClick: () => setShowAddMember(true), children: _jsx(Icon, { icon: "user-add-b", size: 18 }) }))] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: renderContent() })] }) }), _jsx(Modal, { centered: true, open: showLeaveConfirm, onOk: handleConfirmLeave, onCancel: () => setShowLeaveConfirm(false), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, getContainer: false, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(Modal, { centered: true, open: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "remove", onOk: handleConfirmRemoveMember, onCancel: () => setConfirmMemberAction(null), title: t("remove_member_confirm_title"), okText: t("remove_from_group"), cancelText: t("cancel"), okType: "danger", getContainer: false, children: _jsx("p", { children: t("remove_member_confirm_message", {
321
+ }, getContainer: false, width: 360, children: _jsxs("div", { className: "flex flex-col h-full bg-white", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-4 border-b border-gray-100", children: [_jsx("span", { className: "text-lg font-medium", children: t("members") }), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleCloseDrawer, children: _jsx(Icon, { icon: "close-b", size: 22 }) })] }), _jsxs("div", { className: "flex items-center gap-2 px-4 py-3", children: [_jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: t("search_member_placeholder"), prefix: _jsx(Icon, { icon: "search-o", size: 16, className: "text-gray-400" }), className: "h-10" }), canAddMember && (_jsx(Button, { type: "primary", shape: "default", className: "w-10 h-10 p-0 shrink-0 flex items-center justify-center rounded-lg", onClick: () => setShowAddMember(true), children: _jsx(Icon, { icon: "user-add-b", size: 18 }) }))] }), _jsx("div", { className: "flex-1 overflow-y-auto", children: renderContent() })] }) }), _jsx(Modal, { centered: true, open: showLeaveConfirm, onOk: handleConfirmLeave, onCancel: () => setShowLeaveConfirm(false), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, getContainer: false, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(Modal, { centered: true, open: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "remove", onOk: handleConfirmRemoveMember, onCancel: () => setConfirmMemberAction(null), title: t("remove_member_confirm_title"), okText: t("remove_from_group"), cancelText: t("cancel"), okType: "danger", getContainer: false, children: _jsx("p", { children: t("remove_member_confirm_message", {
315
322
  name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
316
323
  }) }) }), _jsx(Modal, { centered: true, open: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "transfer-owner", onOk: handleConfirmTransferOwner, onCancel: () => setConfirmMemberAction(null), title: t("transfer_owner_confirm_title"), okText: t("transfer_owner"), cancelText: t("cancel"), okType: "danger", getContainer: false, children: _jsx("p", { children: t("transfer_owner_confirm_message", {
317
324
  name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
318
- }) }) }), groupID && (_jsx(AddMemberModal, { open: showAddMember, groupID: groupID, existingMemberIDs: members.map((member) => member.userID), onClose: () => setShowAddMember(false) })), groupID && (_jsx(AdminPermissionModal, { open: showAdminPermission, groupID: groupID, onClose: () => setShowAdminPermission(false) }))] }));
325
+ }) }) }), groupID && (_jsx(AddMemberModal, { open: showAddMember, groupID: groupID, onClose: () => setShowAddMember(false) })), groupID && (_jsx(AdminPermissionModal, { open: showAdminPermission, groupID: groupID, onClose: () => setShowAdminPermission(false) }))] }));
319
326
  };
320
327
  export default GroupMembersDrawer;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageHeader.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageHeader.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAO/C,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,KAAK,sBAAsB,GAAG,aAAa,GAAG,UAAU,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,sBAAsB,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,QAAA,MAAM,aAAa,GAAI,0DAKpB,kBAAkB,4CAkNpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"MessageHeader.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageHeader.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAQ/C,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,KAAK,sBAAsB,GAAG,aAAa,GAAG,UAAU,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,sBAAsB,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,QAAA,MAAM,aAAa,GAAI,0DAKpB,kBAAkB,4CA6NpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -14,6 +14,7 @@ import useAuthStore from "../../store/auth";
14
14
  import SearchDrawer from "../../components/search-conversation/SearchDrawer";
15
15
  import { isGroupSession } from "../../utils/imCommon";
16
16
  import { useGroupPermission } from "../../hooks/group/useGroupPermission";
17
+ import GroupCompositeAvatar from "../../components/group/GroupCompositeAvatar";
17
18
  import GroupMembersDrawer from "./GroupMembersDrawer";
18
19
  const MessageHeader = ({ onClose, currentSession, isJoined, onOpenThreadInfo, }) => {
19
20
  var _a, _b;
@@ -129,7 +130,7 @@ const MessageHeader = ({ onClose, currentSession, isJoined, onOpenThreadInfo, })
129
130
  setCurrentSessionStatus(currentSession.status);
130
131
  }
131
132
  }, [currentSession]);
132
- return (_jsxs("div", { className: "px-4 py-3 flex items-center border-b gap-3 bg-white flex-wrap", children: [_jsx(Avatar, { src: avatar, size: "large", className: "min-w-10 min-h-10", children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" }), _jsxs("div", { className: "flex flex-col overflow-hidden flex-1", children: [_jsx("p", { className: "text-base truncate", children: displayName || "" }), shouldShowMemberCount && (_jsx("p", { className: "text-xs text-gray-500 truncate", children: t("member_count", { count: (_b = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.memberCount) !== null && _b !== void 0 ? _b : 0 }) }))] }), _jsxs("div", { className: "flex items-center gap-2 justify-end overflow-hidden", children: [isCx && (_jsx(SelectSession, { placeholder: t("select_tag"), options: tagOptions, value: currentSessionTag, onChange: (value) => handleUpdateSession(value, "tag", value === currentSessionTag), excludeOptions: [SessionTag.SLOW_PROCESSING, SessionTag.NONE] })), isCx && (_jsx(SelectSession, { placeholder: t("select_status"), options: statusOptions, value: currentSessionStatus, onChange: (value) => handleUpdateSession(value, "status", value === currentSessionStatus) })), _jsx(SearchDrawer, {}), shouldShowMemberCount &&
133
+ return (_jsxs("div", { className: "px-4 py-3 flex items-center border-b gap-3 bg-white flex-wrap", children: [shouldShowMemberCount ? (_jsx(GroupCompositeAvatar, { groupID: conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID, faceURL: avatar, ownerUserID: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.ownerUserID, groupName: displayName, size: 40, className: "min-w-10 min-h-10" })) : (_jsx(Avatar, { src: avatar, size: "large", className: "min-w-10 min-h-10", children: ((_a = displayName === null || displayName === void 0 ? void 0 : displayName.charAt) === null || _a === void 0 ? void 0 : _a.call(displayName, 0)) || "A" })), _jsxs("div", { className: "flex flex-col overflow-hidden flex-1", children: [_jsx("p", { className: "text-base truncate", children: displayName || "" }), shouldShowMemberCount && (_jsx("p", { className: "text-xs text-gray-500 truncate", children: t("member_count", { count: (_b = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.memberCount) !== null && _b !== void 0 ? _b : 0 }) }))] }), _jsxs("div", { className: "flex items-center gap-2 justify-end overflow-hidden", children: [isCx && (_jsx(SelectSession, { placeholder: t("select_tag"), options: tagOptions, value: currentSessionTag, onChange: (value) => handleUpdateSession(value, "tag", value === currentSessionTag), excludeOptions: [SessionTag.SLOW_PROCESSING, SessionTag.NONE] })), isCx && (_jsx(SelectSession, { placeholder: t("select_status"), options: statusOptions, value: currentSessionStatus, onChange: (value) => handleUpdateSession(value, "status", value === currentSessionStatus) })), _jsx(SearchDrawer, {}), shouldShowMemberCount &&
133
134
  isJoined === true &&
134
135
  isCrm &&
135
136
  permissions.viewMemberInformation && (_jsx(GroupMembersDrawer, { groupID: conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID, conversationId: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID, currentSession: currentSession })), _jsx(MediaCollection, {}), _jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: onOpenThreadInfo, children: _jsx(Icon, { icon: "align-justify-o", size: 22 }) }), !!onClose && (_jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: onClose, children: _jsx(Icon, { icon: "close-b", size: 22 }) }))] })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AA2BA,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,4CAuO3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AA8BA,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,4CAmR3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useMessage } from "../../hooks/message/useMessage";
4
- import { Button, Empty, Modal, Spin } from "antd";
4
+ import { Button, Empty, Modal, Spin, message as toastMessage } from "antd";
5
5
  import { useCallback, useEffect, useState } from "react";
6
6
  import dayjs from "dayjs";
7
7
  import isToday from "dayjs/plugin/isToday";
@@ -17,11 +17,13 @@ import { useBoolean } from "ahooks";
17
17
  import { useMessageListScroll } from "../../hooks/message/useMessageListScroll";
18
18
  import { useChatContext } from "../../context/ChatContext";
19
19
  import { useRevokeMessage } from "../../hooks/message/useRevokeMessage";
20
+ import { usePinMessage } from "../../hooks/message/usePinMessage";
20
21
  import { useConversationSessionState } from "../../hooks/session/useConversationSessionState";
21
22
  import { useJoinGroupFlow } from "../../hooks/session/useJoinGroupFlow";
22
23
  import { useGroupPermission } from "../../hooks/group/useGroupPermission";
23
24
  import { isGroupSession } from "../../utils/imCommon";
24
25
  import { ConnectStatus, SessionStatus } from "../../types/chat";
26
+ import PinLimitModal from "../../components/conversation-detail/PinLimitModal";
25
27
  dayjs.extend(isToday);
26
28
  const MessageList = (props) => {
27
29
  const { t } = useTranslation();
@@ -31,12 +33,17 @@ const MessageList = (props) => {
31
33
  const permissions = useGroupPermission(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID);
32
34
  const isGroupChat = isGroupSession(conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType);
33
35
  const canSendMessage = !isGroupChat || permissions.sendMessage;
36
+ const canPinMessage = !isGroupChat || permissions.pinMessage;
34
37
  const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
35
38
  const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
36
39
  const [openMenuId, setOpenMenuId] = useState(null);
37
40
  const [selectedItem, setSelectedItem] = useState(null);
38
41
  const [showConfirmRevoke, { setTrue: openConfirmRevoke, setFalse: closeConfirmRevoke },] = useBoolean(false);
39
42
  const { revokeMessage, loading: isRevoking } = useRevokeMessage();
43
+ const { pinMessage, unpinMessage } = usePinMessage();
44
+ const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
45
+ const pinnedRemainingCount = useConversationStore((state) => state.pinnedRemainingCount);
46
+ const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
40
47
  const { groupId, latestConversationSession, refetchConversationSessions, } = useConversationSessionState({
41
48
  conversationId,
42
49
  conversationType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType,
@@ -75,6 +82,28 @@ const MessageList = (props) => {
75
82
  setSearchClientMsgID(clientMsgID);
76
83
  }
77
84
  }, [scrollToMessage, loadState.messageList, setSearchClientMsgID]);
85
+ const handlePinMessage = useCallback(async (message) => {
86
+ if (pinnedRemainingCount <= 0 && pinnedTotalCount > 0) {
87
+ setPinLimitModalOpen(true);
88
+ return;
89
+ }
90
+ try {
91
+ await pinMessage(conversationId, message.clientMsgID);
92
+ toastMessage.success(t("pin_message_success"));
93
+ }
94
+ catch (_a) {
95
+ toastMessage.error(t("pin_message_failed"));
96
+ }
97
+ }, [conversationId, pinMessage, pinnedRemainingCount, pinnedTotalCount, t]);
98
+ const handleUnpinMessage = useCallback(async (message) => {
99
+ try {
100
+ await unpinMessage(conversationId, message.clientMsgID);
101
+ toastMessage.success(t("unpin_message_success"));
102
+ }
103
+ catch (_a) {
104
+ toastMessage.error(t("unpin_message_failed"));
105
+ }
106
+ }, [conversationId, unpinMessage, t]);
78
107
  useEffect(() => {
79
108
  emitter.on("CHAT_LIST_SCROLL_TO_BOTTOM", scrollToBottom);
80
109
  emitter.on("CHAT_LIST_SCROLL_TO_MESSAGE", scrollToMessage);
@@ -107,6 +136,6 @@ const MessageList = (props) => {
107
136
  if (!conversationData) {
108
137
  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") })) }));
109
138
  }
110
- return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 bg-white", style: { overflowX: "hidden" }, 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 }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), isCheckingMembership ? (_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") })] })) : !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("send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(Modal, { centered: true, open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, getContainer: false, forceRender: true, children: _jsx("p", { children: t("revoke_message_confirm_message") }) })] }));
139
+ return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 bg-white", style: { overflowX: "hidden" }, 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, {}) })), isCheckingMembership ? (_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") })] })) : !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("send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(Modal, { centered: true, open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, getContainer: false, forceRender: true, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
111
140
  };
112
141
  export default MessageList;
@@ -1 +1 @@
1
- {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAID,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDAmcjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,oBAAoB;IAC5B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAChC;AAID,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDAucjE,CAAC;AAEF,eAAe,eAAe,CAAC"}