@droppii-org/chat-sdk 0.7.8 → 0.7.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/conversation-detail/ThreadProfilePanel.d.ts +3 -1
- package/dist/components/conversation-detail/ThreadProfilePanel.d.ts.map +1 -1
- package/dist/components/conversation-detail/ThreadProfilePanel.js +2 -2
- package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts +3 -1
- package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts.map +1 -1
- package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.js +2 -2
- package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
- package/dist/hooks/global/useGlobalEvent.js +3 -0
- package/dist/hooks/group/useJoinPublicGroup.d.ts.map +1 -1
- package/dist/hooks/group/useJoinPublicGroup.js +9 -1
- package/dist/hooks/message/useMessage.d.ts +1 -0
- package/dist/hooks/message/useMessage.d.ts.map +1 -1
- package/dist/hooks/message/useMessage.js +14 -9
- package/dist/hooks/message/useMessageListScroll.d.ts +3 -1
- package/dist/hooks/message/useMessageListScroll.d.ts.map +1 -1
- package/dist/hooks/message/useMessageListScroll.js +22 -3
- package/dist/screens/conversation-detail/index.d.ts.map +1 -1
- package/dist/screens/conversation-detail/index.js +18 -7
- package/package.json +1 -1
|
@@ -9,7 +9,9 @@ interface ThreadProfilePanelProps {
|
|
|
9
9
|
showActions?: boolean;
|
|
10
10
|
/** Wires the "Tìm tin nhắn" quick action. */
|
|
11
11
|
onSearch?: () => void;
|
|
12
|
+
/** Drops "Tìm tin nhắn" where the host has search but this viewer can't use it. */
|
|
13
|
+
hideSearch?: boolean;
|
|
12
14
|
}
|
|
13
|
-
declare const ThreadProfilePanel: ({ open, conversation, onClose, onSelectResource, showActions, onSearch, }: ThreadProfilePanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
declare const ThreadProfilePanel: ({ open, conversation, onClose, onSelectResource, showActions, onSearch, hideSearch, }: ThreadProfilePanelProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
16
|
export default ThreadProfilePanel;
|
|
15
17
|
//# sourceMappingURL=ThreadProfilePanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadProfilePanel.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ThreadProfilePanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAc7E,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ThreadProfilePanel.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ThreadProfilePanel.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAc7E,UAAU,uBAAuB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gBAAgB,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,mFAAmF;IACnF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,QAAA,MAAM,kBAAkB,GAAI,uFAQzB,uBAAuB,4CAoJzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -17,7 +17,7 @@ import ThreadProfileQuickActions from "./profile/ThreadProfileQuickActions";
|
|
|
17
17
|
import ThreadProfileActionRows from "./profile/ThreadProfileActionRows";
|
|
18
18
|
const PROFILE_AVATAR_SIZE = 96;
|
|
19
19
|
const PERSON_PEER_TYPES = [PeerType.USER, PeerType.CUSTOMER];
|
|
20
|
-
const ThreadProfilePanel = ({ open, conversation, onClose, onSelectResource, showActions = false, onSearch, }) => {
|
|
20
|
+
const ThreadProfilePanel = ({ open, conversation, onClose, onSelectResource, showActions = false, onSearch, hideSearch = false, }) => {
|
|
21
21
|
var _a;
|
|
22
22
|
const { t } = useTranslation("conversation-inbox");
|
|
23
23
|
const { displayName, avatarUrl, peerType, badgeLabel } = useConversationPeerIdentity(conversation);
|
|
@@ -33,6 +33,6 @@ const ThreadProfilePanel = ({ open, conversation, onClose, onSelectResource, sho
|
|
|
33
33
|
});
|
|
34
34
|
return (_jsxs(Drawer, { open: open, onClose: onClose, placement: "right", closeIcon: false, width: "100%", "data-cp-name": "ThreadProfilePanel", styles: {
|
|
35
35
|
body: { padding: 0 },
|
|
36
|
-
}, getContainer: false, forceRender: true, destroyOnHidden: true, children: [_jsxs("div", { className: clsx("flex h-full flex-col bg-gray-25", showActions && "overflow-y-auto"), children: [_jsxs("div", { className: clsx("flex w-full items-center bg-white p-2", showActions && "sticky top-0 z-10 shrink-0"), children: [_jsx("button", { type: "button", "data-testid": "thread-profile-back", 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("thread_detail.options_title") })] }), _jsxs("div", { className: "flex flex-col items-center gap-2 bg-white px-0 py-2", children: [_jsxs("div", { className: "relative h-24 w-24 shrink-0", children: [_jsx(Avatar, { size: PROFILE_AVATAR_SIZE, src: avatarUrl || undefined, alt: displayName, className: "uppercase", children: (displayName === null || displayName === void 0 ? void 0 : displayName.charAt(0)) || "A" }), badgeLabel && (_jsx("span", { className: clsx("absolute bottom-1 right-0 flex h-[17px] items-center justify-center rounded-md border border-white px-1 text-[10px] font-semibold leading-3 text-white shadow-badge-inset", resolvePeerBadgeColor(peerType)), children: badgeLabel }))] }), _jsx("p", { className: "w-full truncate text-center text-[18px] font-bold leading-[1.6] text-gray-900", children: displayName }), showActions && (_jsx(ThreadProfileQuickActions, { conversation: conversation, isViewOnly: isViewOnly, onSearch: onSearch }, conversation.conversationID))] }), _jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx(MediaEntryCard, { onSelect: onSelectResource }) }), showActions && (_jsx(ThreadProfileActionRows, { conversation: conversation, displayName: displayName, showPersonRows: showPersonRows, isTargetActive: isTargetActive, isViewOnly: isViewOnly, canLeaveGroup: canLeaveGroup, onLeaveGroup: openLeaveGroup }, conversation.conversationID)), !showActions && canLeaveGroup && (_jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx("div", { className: "overflow-hidden rounded-xl bg-white", children: _jsxs("button", { type: "button", "data-testid": "thread-profile-leave-group", className: "flex w-full cursor-pointer items-center gap-2 p-4 text-danger-400", onClick: openLeaveGroup, children: [_jsx(Icon, { icon: "logout-o", size: 24 }), _jsx("span", { className: "text-[16px] font-medium leading-[1.6]", children: t("thread_detail.leave_group.action") })] }) }) }))] }), _jsx(LeaveGroupConfirmModal, { open: isConfirmOpen, loading: isLeaving, isPublicGroup: isPublicGroup, onCancel: closeConfirm, onConfirm: confirmLeaveGroup }), _jsx(LeaveGroupBlockedModal, { open: isBlockedOpen, onClose: closeBlocked })] }));
|
|
36
|
+
}, getContainer: false, forceRender: true, destroyOnHidden: true, children: [_jsxs("div", { className: clsx("flex h-full flex-col bg-gray-25", showActions && "overflow-y-auto"), children: [_jsxs("div", { className: clsx("flex w-full items-center bg-white p-2", showActions && "sticky top-0 z-10 shrink-0"), children: [_jsx("button", { type: "button", "data-testid": "thread-profile-back", 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("thread_detail.options_title") })] }), _jsxs("div", { className: "flex flex-col items-center gap-2 bg-white px-0 py-2", children: [_jsxs("div", { className: "relative h-24 w-24 shrink-0", children: [_jsx(Avatar, { size: PROFILE_AVATAR_SIZE, src: avatarUrl || undefined, alt: displayName, className: "uppercase", children: (displayName === null || displayName === void 0 ? void 0 : displayName.charAt(0)) || "A" }), badgeLabel && (_jsx("span", { className: clsx("absolute bottom-1 right-0 flex h-[17px] items-center justify-center rounded-md border border-white px-1 text-[10px] font-semibold leading-3 text-white shadow-badge-inset", resolvePeerBadgeColor(peerType)), children: badgeLabel }))] }), _jsx("p", { className: "w-full truncate text-center text-[18px] font-bold leading-[1.6] text-gray-900", children: displayName }), showActions && (_jsx(ThreadProfileQuickActions, { conversation: conversation, isViewOnly: isViewOnly, onSearch: onSearch, hideSearch: hideSearch }, conversation.conversationID))] }), _jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx(MediaEntryCard, { onSelect: onSelectResource }) }), showActions && (_jsx(ThreadProfileActionRows, { conversation: conversation, displayName: displayName, showPersonRows: showPersonRows, isTargetActive: isTargetActive, isViewOnly: isViewOnly, canLeaveGroup: canLeaveGroup, onLeaveGroup: openLeaveGroup }, conversation.conversationID)), !showActions && canLeaveGroup && (_jsx("div", { className: "flex flex-col px-3 py-1.5", children: _jsx("div", { className: "overflow-hidden rounded-xl bg-white", children: _jsxs("button", { type: "button", "data-testid": "thread-profile-leave-group", className: "flex w-full cursor-pointer items-center gap-2 p-4 text-danger-400", onClick: openLeaveGroup, children: [_jsx(Icon, { icon: "logout-o", size: 24 }), _jsx("span", { className: "text-[16px] font-medium leading-[1.6]", children: t("thread_detail.leave_group.action") })] }) }) }))] }), _jsx(LeaveGroupConfirmModal, { open: isConfirmOpen, loading: isLeaving, isPublicGroup: isPublicGroup, onCancel: closeConfirm, onConfirm: confirmLeaveGroup }), _jsx(LeaveGroupBlockedModal, { open: isBlockedOpen, onClose: closeBlocked })] }));
|
|
37
37
|
};
|
|
38
38
|
export default ThreadProfilePanel;
|
|
@@ -5,7 +5,9 @@ interface ThreadProfileQuickActionsProps {
|
|
|
5
5
|
isViewOnly: boolean;
|
|
6
6
|
/** Opens search in this thread; without it the entry stays UI-only. */
|
|
7
7
|
onSearch?: () => void;
|
|
8
|
+
/** The host has thread search but this viewer can't use it here: drop the entry. */
|
|
9
|
+
hideSearch?: boolean;
|
|
8
10
|
}
|
|
9
|
-
declare const ThreadProfileQuickActions: ({ conversation, isViewOnly, onSearch, }: ThreadProfileQuickActionsProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
11
|
+
declare const ThreadProfileQuickActions: ({ conversation, isViewOnly, onSearch, hideSearch, }: ThreadProfileQuickActionsProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
10
12
|
export default ThreadProfileQuickActions;
|
|
11
13
|
//# sourceMappingURL=ThreadProfileQuickActions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThreadProfileQuickActions.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/profile/ThreadProfileQuickActions.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AA6CrE,UAAU,8BAA8B;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,2FAA2F;IAC3F,UAAU,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"ThreadProfileQuickActions.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/profile/ThreadProfileQuickActions.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AA6CrE,UAAU,8BAA8B;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,2FAA2F;IAC3F,UAAU,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,oFAAoF;IACpF,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,QAAA,MAAM,yBAAyB,GAAI,qDAKhC,8BAA8B,mDAoDhC,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
|
|
@@ -9,7 +9,7 @@ import { isConversationMuted, useMuteConversation, } from "../../../hooks/sessio
|
|
|
9
9
|
const QuickAction = ({ icon, label, active, testId, onClick, }) => (_jsxs("button", { type: "button", "data-testid": testId, className: "flex w-[104px] cursor-pointer flex-col items-center gap-1 px-3 py-2", onClick: onClick, children: [_jsx("span", { className: clsx("flex h-10 w-10 items-center justify-center rounded-full", active
|
|
10
10
|
? "bg-[var(--dc-color-primary)] text-white"
|
|
11
11
|
: "bg-gray-25 text-gray-900"), children: _jsx(Icon, { icon: icon, size: 18 }) }), _jsx("span", { className: "whitespace-nowrap text-center text-[13px] font-medium leading-[1.6] text-gray-900", children: label })] }));
|
|
12
|
-
const ThreadProfileQuickActions = ({ conversation, isViewOnly, onSearch, }) => {
|
|
12
|
+
const ThreadProfileQuickActions = ({ conversation, isViewOnly, onSearch, hideSearch = false, }) => {
|
|
13
13
|
const { t } = useTranslation("conversation-inbox");
|
|
14
14
|
const { muteConversation, unmuteConversation, isPending } = useMuteConversation();
|
|
15
15
|
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
|
|
@@ -29,7 +29,7 @@ const ThreadProfileQuickActions = ({ conversation, isViewOnly, onSearch, }) => {
|
|
|
29
29
|
};
|
|
30
30
|
if (isViewOnly)
|
|
31
31
|
return null;
|
|
32
|
-
return (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx(QuickAction, { icon: "search-o", label: t("thread_detail.profile.search"), testId: "thread-profile-search", onClick: onSearch }), _jsx(QuickAction, { icon: isMuted ? "bell-b" : "bell-off-o", label: t(isMuted
|
|
32
|
+
return (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [!hideSearch && (_jsx(QuickAction, { icon: "search-o", label: t("thread_detail.profile.search"), testId: "thread-profile-search", onClick: onSearch })), _jsx(QuickAction, { icon: isMuted ? "bell-b" : "bell-off-o", label: t(isMuted
|
|
33
33
|
? "thread_card.mute.unmute_action"
|
|
34
34
|
: "thread_card.mute.mute_action"), active: isMuted, testId: "thread-profile-mute", onClick: handleMuteClick }), _jsx(MuteConversationConfirmModal, { open: isConfirmOpen, loading: isPending, onCancel: () => setIsConfirmOpen(false), onConfirm: handleConfirmMute })] }));
|
|
35
35
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AAiDA,eAAO,MAAM,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":"
|
|
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
|
};
|
|
@@ -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;;;;;;;
|
|
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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { ViewType, MessageType, } from "@droppii-org/wasm-client-sdk";
|
|
3
3
|
import { DChatSDK } from "../../constants/sdk";
|
|
4
4
|
import { PAGE_SIZE } from "../../constants";
|
|
@@ -87,6 +87,17 @@ onSearchMessageRevoked) => {
|
|
|
87
87
|
// without this the user sees it stacked. Keyed by conversation + target so
|
|
88
88
|
// jumping to a different revoked message still notifies.
|
|
89
89
|
const toastedRevokedTargetRef = useRef(null);
|
|
90
|
+
// Also used by the scroll guard for a search hit recalled since the results came back.
|
|
91
|
+
const notifyRevokedTarget = useCallback((clientMsgID) => {
|
|
92
|
+
const revokedTargetKey = `${conversationIdRef.current}:${clientMsgID}`;
|
|
93
|
+
if (toastedRevokedTargetRef.current === revokedTargetKey)
|
|
94
|
+
return;
|
|
95
|
+
toastedRevokedTargetRef.current = revokedTargetKey;
|
|
96
|
+
if (onSearchMessageRevoked)
|
|
97
|
+
onSearchMessageRevoked();
|
|
98
|
+
else
|
|
99
|
+
antdMessage.info(t("message_revoked_or_deleted"));
|
|
100
|
+
}, [conversationIdRef, onSearchMessageRevoked, t]);
|
|
90
101
|
const { loading: moreOldLoading, runAsync: getMoreOldMessages } = useRequest(async (loadMore = true) => {
|
|
91
102
|
var _a, _b, _c, _d, _e;
|
|
92
103
|
const reqConversationID = conversationId;
|
|
@@ -208,14 +219,7 @@ onSearchMessageRevoked) => {
|
|
|
208
219
|
// list with a window centred on a tombstone is worse than not moving.
|
|
209
220
|
const targetMessage = currentMessages.find((item) => item.clientMsgID === searchClientMsgID);
|
|
210
221
|
if ((targetMessage === null || targetMessage === void 0 ? void 0 : targetMessage.contentType) === MessageType.RevokeMessage) {
|
|
211
|
-
|
|
212
|
-
if (toastedRevokedTargetRef.current !== revokedTargetKey) {
|
|
213
|
-
toastedRevokedTargetRef.current = revokedTargetKey;
|
|
214
|
-
if (onSearchMessageRevoked)
|
|
215
|
-
onSearchMessageRevoked();
|
|
216
|
-
else
|
|
217
|
-
antdMessage.info(t("message_revoked_or_deleted"));
|
|
218
|
-
}
|
|
222
|
+
notifyRevokedTarget(searchClientMsgID);
|
|
219
223
|
// Deliberately outside the guard: a repeat run still needs to leave
|
|
220
224
|
// the user with a usable list. The fallback is a no-op once the first
|
|
221
225
|
// one has populated it, and skipping it here would strand an empty
|
|
@@ -397,6 +401,7 @@ onSearchMessageRevoked) => {
|
|
|
397
401
|
moreNewLoading,
|
|
398
402
|
getMoreNewMessages,
|
|
399
403
|
refreshLatestMessages,
|
|
404
|
+
notifyRevokedTarget,
|
|
400
405
|
};
|
|
401
406
|
};
|
|
402
407
|
export const pushNewMessage = (message) => emit("PUSH_NEW_MSG", message);
|
|
@@ -19,8 +19,10 @@ interface UseMessageListScrollParams {
|
|
|
19
19
|
getMoreNewMessages: () => void | Promise<unknown>;
|
|
20
20
|
/** Holds mark-as-read back while the first-unread lookup is still pending. */
|
|
21
21
|
markAsReadBlocked?: boolean;
|
|
22
|
+
/** Tells the user a search jump target was recalled (deduped with the search load). */
|
|
23
|
+
notifyRevokedTarget?: (clientMsgID: string) => void;
|
|
22
24
|
}
|
|
23
|
-
export declare const useMessageListScroll: ({ conversationId, userId, isGroupChat, loadState, latestLoadState, moreOldLoading, moreNewLoading, getMoreOldMessages, getMoreNewMessages, markAsReadBlocked, }: UseMessageListScrollParams) => {
|
|
25
|
+
export declare const useMessageListScroll: ({ conversationId, userId, isGroupChat, loadState, latestLoadState, moreOldLoading, moreNewLoading, getMoreOldMessages, getMoreNewMessages, markAsReadBlocked, notifyRevokedTarget, }: UseMessageListScrollParams) => {
|
|
24
26
|
scrollRef: RefObject<HTMLDivElement>;
|
|
25
27
|
handleMarkConversationMessageAsRead: () => void;
|
|
26
28
|
loadMoreOldMessage: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMessageListScroll.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessageListScroll.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAkC,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,WAAW,
|
|
1
|
+
{"version":3,"file":"useMessageListScroll.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/useMessageListScroll.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAkC,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,WAAW,EAAe,MAAM,8BAA8B,CAAC;AAYxE,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED,UAAU,0BAA0B;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,oBAAoB,CAAC;IAChC,eAAe,EAAE,SAAS,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACxD,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,kBAAkB,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,kBAAkB,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uFAAuF;IACvF,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED,eAAO,MAAM,oBAAoB,GAAI,sLAYlC,0BAA0B;;;;8BAsErB,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;;;mCA0Ce,MAAM;CAyEzD,CAAC"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { MessageType } from "@droppii-org/wasm-client-sdk";
|
|
4
|
+
import { useDebounceFn, useLatest } from "ahooks";
|
|
4
5
|
import { isNumber } from "lodash";
|
|
5
6
|
import { markConversationMessageAsRead } from "../../hooks/conversation/useConversation";
|
|
6
7
|
import { MSG_ITEM_BUBBLE_GROUP_PREFIX, MSG_ITEM_CONTENT_PREFIX, MSG_ITEM_PREFIX, } from "../../constants";
|
|
7
8
|
import useConversationStore from "../../store/conversation";
|
|
8
9
|
import { holdMessageHighlight } from "./holdMessageHighlight";
|
|
9
10
|
export const MESSAGE_LIST_BOTTOM_THRESHOLD = -5;
|
|
10
|
-
export const useMessageListScroll = ({ conversationId, userId, isGroupChat, loadState, latestLoadState, moreOldLoading, moreNewLoading, getMoreOldMessages, getMoreNewMessages, markAsReadBlocked = false, }) => {
|
|
11
|
+
export const useMessageListScroll = ({ conversationId, userId, isGroupChat, loadState, latestLoadState, moreOldLoading, moreNewLoading, getMoreOldMessages, getMoreNewMessages, markAsReadBlocked = false, notifyRevokedTarget, }) => {
|
|
11
12
|
const scrollRef = useRef(null);
|
|
12
13
|
const autoFillAttemptedRef = useRef(false);
|
|
14
|
+
const notifyRevokedTargetRef = useLatest(notifyRevokedTarget);
|
|
13
15
|
useEffect(() => {
|
|
14
16
|
autoFillAttemptedRef.current = false;
|
|
15
17
|
}, [conversationId]);
|
|
@@ -99,7 +101,22 @@ export const useMessageListScroll = ({ conversationId, userId, isGroupChat, load
|
|
|
99
101
|
});
|
|
100
102
|
}, [handleMarkConversationMessageAsRead]);
|
|
101
103
|
const scrollToMessage = useCallback((clientMsgID) => {
|
|
104
|
+
// A search hit recalled since the results came back: say so instead of landing on its placeholder.
|
|
105
|
+
// Checked before the scroll and again before the highlight, since the list reloads in between.
|
|
106
|
+
const dropRevokedSearchTarget = () => {
|
|
107
|
+
var _a, _b, _c, _d;
|
|
108
|
+
const state = useConversationStore.getState();
|
|
109
|
+
const isRevoked = state.heldHighlightClientMsgID === clientMsgID &&
|
|
110
|
+
((_c = (_b = (_a = latestLoadState.current) === null || _a === void 0 ? void 0 : _a.messageList) === null || _b === void 0 ? void 0 : _b.find((message) => message.clientMsgID === clientMsgID)) === null || _c === void 0 ? void 0 : _c.contentType) === MessageType.RevokeMessage;
|
|
111
|
+
if (!isRevoked)
|
|
112
|
+
return false;
|
|
113
|
+
state.setHeldHighlightClientMsgID("");
|
|
114
|
+
(_d = notifyRevokedTargetRef.current) === null || _d === void 0 ? void 0 : _d.call(notifyRevokedTargetRef, clientMsgID);
|
|
115
|
+
return true;
|
|
116
|
+
};
|
|
102
117
|
setTimeout(() => {
|
|
118
|
+
if (dropRevokedSearchTarget())
|
|
119
|
+
return;
|
|
103
120
|
const targetElement = document.getElementById(`${MSG_ITEM_PREFIX}${clientMsgID}`);
|
|
104
121
|
targetElement === null || targetElement === void 0 ? void 0 : targetElement.scrollIntoView({
|
|
105
122
|
behavior: "auto",
|
|
@@ -107,6 +124,8 @@ export const useMessageListScroll = ({ conversationId, userId, isGroupChat, load
|
|
|
107
124
|
});
|
|
108
125
|
setTimeout(() => {
|
|
109
126
|
var _a;
|
|
127
|
+
if (dropRevokedSearchTarget())
|
|
128
|
+
return;
|
|
110
129
|
const targetContentElement = document.getElementById(`${MSG_ITEM_CONTENT_PREFIX}${clientMsgID}`);
|
|
111
130
|
// The border hugs the bubble; the pulse runs on the wrapper that also
|
|
112
131
|
// holds the pin badge, or the badge stays put while the bubble scales.
|
|
@@ -134,7 +153,7 @@ export const useMessageListScroll = ({ conversationId, userId, isGroupChat, load
|
|
|
134
153
|
});
|
|
135
154
|
}, 500);
|
|
136
155
|
}, 200);
|
|
137
|
-
}, []);
|
|
156
|
+
}, [latestLoadState, notifyRevokedTargetRef]);
|
|
138
157
|
return {
|
|
139
158
|
scrollRef,
|
|
140
159
|
handleMarkConversationMessageAsRead,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/screens/conversation-detail/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAarF,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,6CAA6C,CAAC;AASrD,MAAM,WAAW,4BACf,SAAQ,4BAA4B,EAClC,4BAA4B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,IAAI,CAAC;IACpC,oHAAoH;IACpH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,6GAA6G;IAC7G,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,QAAA,MAAM,uBAAuB,GAAI,oLAgB9B,4BAA4B,mDAwT9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
return t;
|
|
12
12
|
};
|
|
13
13
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
14
|
-
import { useEffect, useState } from "react";
|
|
14
|
+
import { useEffect, useRef, useState } from "react";
|
|
15
15
|
import { useTranslation } from "react-i18next";
|
|
16
16
|
import { useChatContext } from "../../context/ChatContext";
|
|
17
17
|
import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
|
|
@@ -30,6 +30,7 @@ import TypingIndicatorBar from "../../components/conversation-detail/TypingIndic
|
|
|
30
30
|
import { BotButtonMessage, BotInactiveNotice, BotMenuSheet, BotStartButton, BotSuggest, CsSessionConfirmModal, } from "../../components/conversation-detail/bot";
|
|
31
31
|
import { useBotFlow } from "../../hooks/conversation/useBotFlow";
|
|
32
32
|
import { useCanPinMessage } from "../../hooks/message/useCanPinMessage";
|
|
33
|
+
import { useIsJoinedGroup } from "../../hooks/session/useIsJoinedGroup";
|
|
33
34
|
import { BOT_TYPE } from "../../constants/botFlow";
|
|
34
35
|
import { useResolveConversation, } from "../../hooks/conversation/useResolveConversation";
|
|
35
36
|
import { nextConsultantReturnTarget, useConsultantChatNavigation, } from "../../hooks/conversation/useConsultantChatNavigation";
|
|
@@ -75,14 +76,24 @@ const DChatConversationDetail = (_a) => {
|
|
|
75
76
|
closeProfile();
|
|
76
77
|
openSearch();
|
|
77
78
|
};
|
|
78
|
-
// The search API only serves members
|
|
79
|
-
//
|
|
80
|
-
const
|
|
81
|
-
const
|
|
79
|
+
// The search API only serves members: hide it while the check says no or is pending.
|
|
80
|
+
// A failed check keeps it (the API still gates); a 1-1 exposes no groupID and is the viewer's own.
|
|
81
|
+
const { isJoined, isError: isJoinCheckFailed, refetch: refetchIsJoined, } = useIsJoinedGroup(showThreadSearch ? conversation === null || conversation === void 0 ? void 0 : conversation.groupID : undefined);
|
|
82
|
+
const isNonMember = (isViewOnlyMember && (conversation === null || conversation === void 0 ? void 0 : conversation.peerType) === PeerType.GROUP) ||
|
|
83
|
+
(!!(conversation === null || conversation === void 0 ? void 0 : conversation.groupID) && !isJoinCheckFailed && isJoined !== true);
|
|
84
|
+
const canSearch = showThreadSearch && !isNonMember;
|
|
85
|
+
// Being added to a group already open view-only syncs as an update, not a join event: re-check.
|
|
86
|
+
const wasViewOnlyRef = useRef(isViewOnlyMember);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (wasViewOnlyRef.current && !isViewOnlyMember && showThreadSearch) {
|
|
89
|
+
void refetchIsJoined();
|
|
90
|
+
}
|
|
91
|
+
wasViewOnlyRef.current = isViewOnlyMember;
|
|
92
|
+
}, [isViewOnlyMember, showThreadSearch, refetchIsJoined]);
|
|
82
93
|
// Search belongs to one thread; another thread opening in place (or losing membership) closes it.
|
|
83
94
|
useEffect(() => {
|
|
84
95
|
closeSearch();
|
|
85
|
-
}, [conversation === null || conversation === void 0 ? void 0 : conversation.conversationID,
|
|
96
|
+
}, [conversation === null || conversation === void 0 ? void 0 : conversation.conversationID, isNonMember, closeSearch]);
|
|
86
97
|
const { returnTarget: consultantReturnTarget, returnToBotThread, clearReturnTarget, setReturnTarget, } = useConsultantChatNavigation();
|
|
87
98
|
useEffect(() => {
|
|
88
99
|
const next = nextConsultantReturnTarget(consultantReturnTarget, conversation);
|
|
@@ -156,6 +167,6 @@ const DChatConversationDetail = (_a) => {
|
|
|
156
167
|
!botFlow.flowIsEnd &&
|
|
157
168
|
botFlow.shouldShowButtons &&
|
|
158
169
|
botFlow.currentButtonsBlock ? (_jsx(BotButtonMessage, { buttons: botFlow.currentButtonsBlock.data.buttons, disabled: botFlow.isProcessing, onSelect: botFlow.selectButton, onRestart: botFlow.restart })) : null;
|
|
159
|
-
return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile, onProfileIconClick: showProfileActions ? openProfile : undefined, onSearchClick: canSearch ? openSearch : undefined }), _jsx(PinnedMessageBar, { canUnpin: canPinMessage }), _jsxs("div", { [COMPOSER_DISMISS_ZONE_ATTR]: "", className: "relative flex min-h-0 flex-1 flex-col", children: [children !== null && children !== void 0 ? children : (_jsx(ConversationMessageList, { bottomSlot: botButtonsSlot, onCopyMessageLink: onCopyMessageLink, onSearchMessageRevoked: onSearchMessageRevoked })), _jsx(TypingIndicatorBar, { conversationId: conversation.conversationID })] }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab, showActions: showProfileActions, onSearch: canSearch ? openSearchFromProfile : undefined }), showThreadSearch && (_jsx(ThreadSearchPanel, { open: isSearchOpen, conversation: conversation, onClose: closeSearch, onSearchMessageRevoked: onSearchMessageRevoked }, conversation.conversationID)), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
|
|
170
|
+
return (_jsxs("div", { "data-testid": "conversation-detail-root", className: clsx("relative flex h-full w-full flex-col overflow-hidden bg-white", className), children: [_jsx(ConversationDetailHeader, { conversation: conversation, onBack: handleBack, onProfileClick: openProfile, onProfileIconClick: showProfileActions ? openProfile : undefined, onSearchClick: canSearch ? openSearch : undefined }), _jsx(PinnedMessageBar, { canUnpin: canPinMessage }), _jsxs("div", { [COMPOSER_DISMISS_ZONE_ATTR]: "", className: "relative flex min-h-0 flex-1 flex-col", children: [children !== null && children !== void 0 ? children : (_jsx(ConversationMessageList, { bottomSlot: botButtonsSlot, onCopyMessageLink: onCopyMessageLink, onSearchMessageRevoked: onSearchMessageRevoked })), _jsx(TypingIndicatorBar, { conversationId: conversation.conversationID })] }), footer, isBotThread && (_jsxs(_Fragment, { children: [_jsx(BotMenuSheet, { open: isMenuOpen, items: botFlow.menuItems, disabled: botFlow.isProcessing, onClose: closeMenu, onSelect: botFlow.selectMenuOption }), _jsx(CsSessionConfirmModal, { open: botFlow.isCsConfirmOpen, loading: botFlow.isProcessing, onCancel: botFlow.cancelMenuOption, onConfirm: botFlow.confirmEndCsSession })] })), _jsx(ThreadProfilePanel, { open: isProfileOpen, conversation: conversation, onClose: closeProfile, onSelectResource: setResourceTab, showActions: showProfileActions, onSearch: canSearch ? openSearchFromProfile : undefined, hideSearch: showThreadSearch && !canSearch }), showThreadSearch && (_jsx(ThreadSearchPanel, { open: isSearchOpen && canSearch, conversation: conversation, onClose: closeSearch, onSearchMessageRevoked: onSearchMessageRevoked }, conversation.conversationID)), _jsx(MediaResourcePanel, { open: resourceTab !== null, initialTab: resourceTab !== null && resourceTab !== void 0 ? resourceTab : MediaResourceTabKey.Image, onClose: () => setResourceTab(null) })] }));
|
|
160
171
|
};
|
|
161
172
|
export default DChatConversationDetail;
|