@droppii-org/chat-sdk 0.3.5 → 0.3.7

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 (37) hide show
  1. package/dist/components/common/ConfirmModal.d.ts +18 -0
  2. package/dist/components/common/ConfirmModal.d.ts.map +1 -0
  3. package/dist/components/common/ConfirmModal.js +15 -0
  4. package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
  5. package/dist/components/conversation/DeskConversationList.js +20 -14
  6. package/dist/components/group/AdminPermissionModal.d.ts.map +1 -1
  7. package/dist/components/group/AdminPermissionModal.js +37 -13
  8. package/dist/components/message/GroupMembersDrawer.d.ts.map +1 -1
  9. package/dist/components/message/GroupMembersDrawer.js +27 -14
  10. package/dist/components/message/MessageList.d.ts.map +1 -1
  11. package/dist/components/message/MessageList.js +17 -3
  12. package/dist/components/message/item/MessageBubble.d.ts +3 -2
  13. package/dist/components/message/item/MessageBubble.d.ts.map +1 -1
  14. package/dist/components/message/item/MessageBubble.js +8 -2
  15. package/dist/components/message/item/index.d.ts.map +1 -1
  16. package/dist/components/message/item/index.js +22 -7
  17. package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
  18. package/dist/components/thread/GroupThreadInfo.js +43 -14
  19. package/dist/components/thread/SingleThreadInfo.d.ts.map +1 -1
  20. package/dist/components/thread/SingleThreadInfo.js +3 -2
  21. package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
  22. package/dist/hooks/global/useGlobalEvent.js +32 -3
  23. package/dist/hooks/group/useGroupPermission.js +1 -1
  24. package/dist/hooks/session/useConversationSessionState.d.ts.map +1 -1
  25. package/dist/hooks/session/useConversationSessionState.js +1 -0
  26. package/dist/layout/index.d.ts.map +1 -1
  27. package/dist/layout/index.js +5 -0
  28. package/dist/locales/vi/common.json +14 -8
  29. package/dist/store/conversation.d.ts.map +1 -1
  30. package/dist/store/conversation.js +16 -4
  31. package/dist/styles/global.css +1 -1
  32. package/dist/utils/common.d.ts +1 -0
  33. package/dist/utils/common.d.ts.map +1 -1
  34. package/dist/utils/common.js +18 -0
  35. package/dist/utils/fileValidation.d.ts.map +1 -1
  36. package/dist/utils/fileValidation.js +1 -1
  37. package/package.json +1 -1
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ConfirmModalProps {
3
+ open: boolean;
4
+ title: ReactNode;
5
+ okText: ReactNode;
6
+ cancelText: ReactNode;
7
+ onOk: () => void;
8
+ onCancel: () => void;
9
+ okType?: "danger" | "primary";
10
+ confirmLoading?: boolean;
11
+ okButtonProps?: {
12
+ disabled?: boolean;
13
+ };
14
+ children: ReactNode;
15
+ }
16
+ declare const ConfirmModal: ({ open, title, okText, cancelText, onOk, onCancel, okType, confirmLoading, okButtonProps, children, }: ConfirmModalProps) => import("react/jsx-runtime").JSX.Element;
17
+ export default ConfirmModal;
18
+ //# sourceMappingURL=ConfirmModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmModal.d.ts","sourceRoot":"","sources":["../../../src/components/common/ConfirmModal.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,UAAU,EAAE,SAAS,CAAC;IACtB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACvC,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,QAAA,MAAM,YAAY,GAAI,uGAWnB,iBAAiB,4CAiCnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Modal } from "antd";
4
+ const ConfirmModal = ({ open, title, okText, cancelText, onOk, onCancel, okType = "primary", confirmLoading, okButtonProps, children, }) => {
5
+ return (_jsx(Modal, { centered: true, open: open, onOk: onOk, onCancel: onCancel, okText: okText, cancelText: cancelText, confirmLoading: confirmLoading, okButtonProps: Object.assign(Object.assign({}, okButtonProps), { className: okType === "danger"
6
+ ? "!h-10 !rounded-lg !border-danger-400 !bg-danger-400 !text-white hover:!border-danger-300 hover:!bg-danger-300"
7
+ : "!h-10 !rounded-lg !border-primary-400 !bg-primary-400 !text-white hover:!border-primary-300 hover:!bg-primary-300" }), cancelButtonProps: {
8
+ className: "!h-10 !rounded-lg !border-gray-25 !bg-gray-25 !text-gray-900",
9
+ }, styles: {
10
+ header: { textAlign: "center", marginBottom: 0 },
11
+ footer: { marginTop: 16 },
12
+ body: { paddingTop: 4 },
13
+ }, classNames: { footer: "border-t border-gray-25 pt-4" }, title: _jsx("span", { className: "text-[18px] font-bold text-gray-900", children: title }), getContainer: false, forceRender: true, children: children }));
14
+ };
15
+ export default ConfirmModal;
@@ -1 +1 @@
1
- {"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAgSzB,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,+CAySzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -68,20 +68,26 @@ const DeskConversationList = () => {
68
68
  }, [showSearch]);
69
69
  useEffect(() => {
70
70
  const threadId = searchParams.get("threadId");
71
- if (threadId) {
72
- setSelectedConversationId(threadId);
73
- const selectedConversation = conversationList.find((conv) => conv.conversationID === threadId);
74
- if (selectedConversation) {
75
- setConversationData(selectedConversation);
76
- }
77
- else {
78
- DChatSDK.getMultipleConversation([threadId]).then((res) => {
79
- if (res.data.length > 0) {
80
- setConversationData(res.data[0]);
81
- updateConversationList(res.data, "filter");
82
- }
83
- });
84
- }
71
+ if (!threadId) {
72
+ useConversationStore.getState().clearSelectedConversation();
73
+ return;
74
+ }
75
+ setSelectedConversationId(threadId);
76
+ const selectedConversation = conversationList.find((conv) => conv.conversationID === threadId);
77
+ if (selectedConversation) {
78
+ setConversationData(selectedConversation);
79
+ }
80
+ else {
81
+ DChatSDK.getMultipleConversation([threadId]).then((res) => {
82
+ // threadId can go stale between the fetch starting and resolving
83
+ // (e.g. the user was removed from that group mid-flight) — only
84
+ // apply the result if the URL still points at this conversation.
85
+ if (res.data.length > 0 &&
86
+ searchParams.get("threadId") === threadId) {
87
+ setConversationData(res.data[0]);
88
+ updateConversationList(res.data, "filter");
89
+ }
90
+ });
85
91
  }
86
92
  }, [searchParams, conversationList]);
87
93
  useEffect(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"AdminPermissionModal.d.ts","sourceRoot":"","sources":["../../../src/components/group/AdminPermissionModal.tsx"],"names":[],"mappings":"AAcA,UAAU,0BAA0B;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,UAAU,yBAAyB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IAIpB,MAAM,CAAC,EAAE,OAAO,CAAC;IAKjB,MAAM,CAAC,EAAE,0BAA0B,CAAC;IAKpC,wBAAwB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC5E;AA8CD,QAAA,MAAM,oBAAoB,GAAI,uEAO3B,yBAAyB,mDA0L3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"AdminPermissionModal.d.ts","sourceRoot":"","sources":["../../../src/components/group/AdminPermissionModal.tsx"],"names":[],"mappings":"AAcA,UAAU,0BAA0B;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,UAAU,yBAAyB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IAIpB,MAAM,CAAC,EAAE,OAAO,CAAC;IAKjB,MAAM,CAAC,EAAE,0BAA0B,CAAC;IAKpC,wBAAwB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC5E;AA6DD,QAAA,MAAM,oBAAoB,GAAI,uEAO3B,yBAAyB,mDA2M3B,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
@@ -3,19 +3,29 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import { ConfigProvider, Modal, Switch, message } from "antd";
5
5
  import { useTranslation } from "react-i18next";
6
+ import { GroupMemberRole } from "@droppii-org/wasm-client-sdk";
6
7
  import { useGroupPermission } from "../../hooks/group/useGroupPermission";
8
+ import { useGroupMembers } from "../../hooks/group/useGroupMembers";
7
9
  import { useSetGroupMemberPermissions } from "../../hooks/group/useGroupMemberActions";
8
10
  import { Icon } from "../../components/icon";
9
11
  import { PERMISSION_SWITCH_COLOR } from "../../constants";
10
12
  import { GROUP_PERMISSION_KEY } from "../../constants/im";
11
- import useGroupPermissionOverridesStore from "../../store/groupPermissionOverrides";
13
+ // "Cập nhật thông tin nhóm" is one switch in the UI but bundles 3 BE
14
+ // permission keys (name/avatar/description) — DROPPII-30533 fix: reading
15
+ // only checked UPDATE_GROUP_NAME, so turning the switch off only ever
16
+ // stripped that one key, leaving avatar/description at the role default.
17
+ const UPDATE_GROUP_INFO_KEYS = [
18
+ GROUP_PERMISSION_KEY.updateGroupName,
19
+ GROUP_PERMISSION_KEY.updateGroupAvatar,
20
+ GROUP_PERMISSION_KEY.updateGroupDescription,
21
+ ];
12
22
  const permissionListToOverrides = (permissionList) => ({
13
23
  sendMessage: permissionList.includes(GROUP_PERMISSION_KEY.sendMessage),
14
24
  pinMessage: permissionList.includes(GROUP_PERMISSION_KEY.pinMessage),
15
25
  viewMemberInformation: permissionList.includes(GROUP_PERMISSION_KEY.viewMemberInformation),
16
26
  addMember: permissionList.includes(GROUP_PERMISSION_KEY.addMember),
17
27
  removeMember: permissionList.includes(GROUP_PERMISSION_KEY.removeMember),
18
- updateGroupInfo: permissionList.includes(GROUP_PERMISSION_KEY.updateGroupName),
28
+ updateGroupInfo: UPDATE_GROUP_INFO_KEYS.every((key) => permissionList.includes(key)),
19
29
  });
20
30
  const overridesToPermissionList = (overrides, currentPermissionList) => {
21
31
  const desired = {
@@ -25,6 +35,8 @@ const overridesToPermissionList = (overrides, currentPermissionList) => {
25
35
  [GROUP_PERMISSION_KEY.addMember]: overrides.addMember,
26
36
  [GROUP_PERMISSION_KEY.removeMember]: overrides.removeMember,
27
37
  [GROUP_PERMISSION_KEY.updateGroupName]: overrides.updateGroupInfo,
38
+ [GROUP_PERMISSION_KEY.updateGroupAvatar]: overrides.updateGroupInfo,
39
+ [GROUP_PERMISSION_KEY.updateGroupDescription]: overrides.updateGroupInfo,
28
40
  };
29
41
  const managedKeys = new Set(Object.keys(desired));
30
42
  const untouched = currentPermissionList.filter((permission) => !managedKeys.has(permission));
@@ -33,19 +45,23 @@ const overridesToPermissionList = (overrides, currentPermissionList) => {
33
45
  .map(([permission]) => permission);
34
46
  return [...untouched, ...enabled];
35
47
  };
36
- // Mocked config UI (DROPPII-30248 follow-up, "Configure Admin Permissions")
37
- // when used group-wide (no `member`, e.g. from GroupThreadInfo) applies
38
- // to the whole Admin role in the group, no BE persistence yet, committed to
39
- // the in-memory override store on "Lưu" only. When `member` is passed
40
- // (from the member list's per-row menu), it instead patches that member's
41
- // own permission list via the same setGroupMemberInfo call used to mute a
42
- // member real BE persistence, scoped to that user.
48
+ // "Configure Admin Permissions" (DROPPII-30248 follow-up). When `member` is
49
+ // passed (from the member list's per-row menu), it patches that one
50
+ // member's permission list via setGroupMemberInfo. When used group-wide (no
51
+ // `member`, e.g. from GroupThreadInfo's group settings), "Lưu" applies the
52
+ // same permission set to every current Admin in the group (DROPPII-30529/
53
+ // 30531 fix) there is no separate BE endpoint for a role-wide default, so
54
+ // this fans out the existing per-member call to each current Admin instead
55
+ // of only writing to a local override store.
43
56
  const AdminPermissionModal = ({ open, groupID, onClose, inline = false, member, onMemberPermissionsSaved, }) => {
44
57
  const { t } = useTranslation();
45
58
  const groupPermissions = useGroupPermission(groupID);
46
- const setAdminOverrides = useGroupPermissionOverridesStore((state) => state.setAdminOverrides);
59
+ const { members: groupMembers } = useGroupMembers(member ? undefined : groupID);
47
60
  const { mutateAsync: setMemberPermissions } = useSetGroupMemberPermissions();
48
61
  const [isSaving, setIsSaving] = useState(false);
62
+ const isGroupInfoEditable = groupPermissions.updateGroupName &&
63
+ groupPermissions.updateGroupAvatar &&
64
+ groupPermissions.updateGroupDescription;
49
65
  const initialOverrides = () => member
50
66
  ? permissionListToOverrides(member.permissions)
51
67
  : {
@@ -54,7 +70,7 @@ const AdminPermissionModal = ({ open, groupID, onClose, inline = false, member,
54
70
  viewMemberInformation: groupPermissions.viewMemberInformation,
55
71
  addMember: groupPermissions.addMember,
56
72
  removeMember: groupPermissions.removeMember,
57
- updateGroupInfo: groupPermissions.updateGroupName,
73
+ updateGroupInfo: isGroupInfoEditable,
58
74
  };
59
75
  const [pending, setPending] = useState(initialOverrides);
60
76
  useEffect(() => {
@@ -71,7 +87,7 @@ const AdminPermissionModal = ({ open, groupID, onClose, inline = false, member,
71
87
  viewMemberInformation: groupPermissions.viewMemberInformation,
72
88
  addMember: groupPermissions.addMember,
73
89
  removeMember: groupPermissions.removeMember,
74
- updateGroupInfo: groupPermissions.updateGroupName,
90
+ updateGroupInfo: isGroupInfoEditable,
75
91
  };
76
92
  const hasChanges = pending.sendMessage !== baseline.sendMessage ||
77
93
  pending.pinMessage !== baseline.pinMessage ||
@@ -95,7 +111,15 @@ const AdminPermissionModal = ({ open, groupID, onClose, inline = false, member,
95
111
  onMemberPermissionsSaved === null || onMemberPermissionsSaved === void 0 ? void 0 : onMemberPermissionsSaved(member.userID, nextPermissions);
96
112
  }
97
113
  else {
98
- setAdminOverrides(groupID, pending);
114
+ const admins = groupMembers.filter((groupMember) => groupMember.roleLevel === GroupMemberRole.Admin);
115
+ await Promise.all(admins.map((admin) => {
116
+ var _a;
117
+ return setMemberPermissions({
118
+ groupID,
119
+ userID: admin.userID,
120
+ permissions: overridesToPermissionList(pending, (_a = admin.permissions) !== null && _a !== void 0 ? _a : []),
121
+ });
122
+ }));
99
123
  }
100
124
  message.success(t("update_admin_permissions_success"));
101
125
  onClose();
@@ -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;AAiB/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,4CA6hBzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"GroupMembersDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/message/GroupMembersDrawer.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiB/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,4CA4iBzB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { GroupMemberRole } from "@droppii-org/wasm-client-sdk";
3
- import { Avatar, Button, Drawer, Dropdown, Empty, Input, message, Modal, Spin } from "antd";
3
+ import { Avatar, Button, Drawer, Dropdown, Empty, Input, message, Spin } from "antd";
4
+ import ConfirmModal from "../../components/common/ConfirmModal";
4
5
  import { useEffect, useMemo, useState } from "react";
5
6
  import { useDebounce } from "ahooks";
6
7
  import { useTranslation } from "react-i18next";
@@ -202,6 +203,16 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
202
203
  message.error(((_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.message) || t("update_group_info_failed"));
203
204
  }
204
205
  };
206
+ const handleConfirmAssignAdmin = async () => {
207
+ if ((confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) !== "assign-admin")
208
+ return;
209
+ try {
210
+ await handleToggleAdminRole(confirmMemberAction.member);
211
+ }
212
+ finally {
213
+ setConfirmMemberAction(null);
214
+ }
215
+ };
205
216
  const handleConfirmTransferOwner = async () => {
206
217
  var _a, _b;
207
218
  if (!groupID || (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) !== "transfer-owner")
@@ -256,7 +267,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
256
267
  const leaveGroupItem = {
257
268
  key: "leave-group",
258
269
  label: t("leave_group"),
259
- icon: _jsx(Icon, { icon: "logout-b", size: 16 }),
270
+ icon: _jsx(Icon, { icon: "logout-o", size: 16 }),
260
271
  onClick: handleLeaveGroup,
261
272
  danger: true,
262
273
  };
@@ -266,7 +277,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
266
277
  {
267
278
  key: "mute",
268
279
  label: t("mute_member"),
269
- icon: _jsx(Icon, { icon: "bell-off-b", size: 16 }),
280
+ icon: _jsx(Icon, { icon: "bell-off-o", size: 16 }),
270
281
  disabled: true,
271
282
  },
272
283
  leaveGroupItem,
@@ -276,7 +287,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
276
287
  {
277
288
  key: "send-message",
278
289
  label: t("send_message"),
279
- icon: _jsx(Icon, { icon: "send-b", size: 16 }),
290
+ icon: _jsx(Icon, { icon: "send-o", size: 16 }),
280
291
  disabled: true,
281
292
  },
282
293
  {
@@ -284,14 +295,14 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
284
295
  label: member.isSendMessageDisabled
285
296
  ? t("unmute_member")
286
297
  : t("mute_member"),
287
- icon: _jsx(Icon, { icon: "bell-off-b", size: 16 }),
298
+ icon: _jsx(Icon, { icon: "bell-off-o", size: 16 }),
288
299
  disabled: !permissions.removeMember,
289
300
  onClick: () => void handleToggleMemberSendMessage(member),
290
301
  },
291
302
  {
292
303
  key: "transfer-owner",
293
304
  label: t("transfer_owner"),
294
- icon: _jsx(Icon, { icon: "key-b", size: 16 }),
305
+ icon: _jsx(Icon, { icon: "key-o", size: 16 }),
295
306
  disabled: !permissions.transferOwnership,
296
307
  onClick: () => setConfirmMemberAction({ type: "transfer-owner", member }),
297
308
  },
@@ -307,7 +318,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
307
318
  {
308
319
  key: "remove-admin",
309
320
  label: t("remove_from_admin"),
310
- icon: _jsx(Icon, { icon: "shield-fail-b", size: 16 }),
321
+ icon: _jsx(Icon, { icon: "shield-fail-o", size: 16 }),
311
322
  disabled: !permissions.addAdmin,
312
323
  onClick: () => void handleToggleAdminRole(member),
313
324
  },
@@ -316,15 +327,15 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
316
327
  {
317
328
  key: "assign-admin",
318
329
  label: t("assign_as_admin"),
319
- icon: _jsx(Icon, { icon: "shield-b", size: 16 }),
330
+ icon: _jsx(Icon, { icon: "shield-o", size: 16 }),
320
331
  disabled: !permissions.addAdmin,
321
- onClick: () => void handleToggleAdminRole(member),
332
+ onClick: () => setConfirmMemberAction({ type: "assign-admin", member }),
322
333
  },
323
334
  ]),
324
335
  {
325
336
  key: "remove-member",
326
337
  label: t("remove_from_group"),
327
- icon: _jsx(Icon, { icon: "user-del-b", size: 16 }),
338
+ icon: _jsx(Icon, { icon: "user-del-o", size: 16 }),
328
339
  danger: true,
329
340
  disabled: !permissions.removeMember,
330
341
  onClick: () => setConfirmMemberAction({ type: "remove", member }),
@@ -332,7 +343,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
332
343
  {
333
344
  key: "remove-and-block",
334
345
  label: t("remove_and_block"),
335
- icon: _jsx(Icon, { icon: "user-octagon-b", size: 16 }),
346
+ icon: _jsx(Icon, { icon: "user-octagon-o", size: 16 }),
336
347
  danger: true,
337
348
  disabled: true,
338
349
  },
@@ -340,7 +351,7 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
340
351
  return (_jsxs("div", { className: "flex items-center gap-3 px-4 py-2", children: [_jsx(Avatar, { src: member.faceURL, size: 40, className: "shrink-0", children: member.nickname.charAt(0) || "A" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("p", { className: "text-sm font-semibold text-gray-900 truncate", children: member.nickname }), member.isCurrentUser && (_jsx("span", { className: "text-xs text-blue-500 shrink-0", children: t("current_account") }))] }), member.roleLabel && (_jsx("p", { className: "text-sm text-gray-500 truncate", children: member.roleLabel }))] }), (isRealGroup || member.isCurrentUser) && (_jsx(Dropdown, { menu: { items: menuItems }, trigger: ["click"], placement: "bottomRight", children: _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: (event) => event.stopPropagation(), children: _jsx(Icon, { icon: "more-horizontal-b", size: 18 }) }) }))] }, member.userID));
341
352
  }) }));
342
353
  };
343
- return (_jsxs("div", { className: "inline-flex shrink-0", children: [_jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleOpenDrawer, disabled: disabled || !groupID, children: _jsx(Icon, { icon: "user-two-check-b", size: 22 }) }), _jsx(Drawer, { open: isOpen, onClose: handleCloseDrawer, mask: false, closeIcon: false, styles: {
354
+ return (_jsxs("div", { className: "inline-flex shrink-0", children: [_jsx(Button, { type: "text", shape: "default", className: "text-gray-500 w-8 h-8 p-0", onClick: handleOpenDrawer, disabled: disabled || !groupID, children: _jsx(Icon, { icon: "user-two-check-o", size: 22 }) }), _jsx(Drawer, { open: isOpen, onClose: handleCloseDrawer, mask: false, closeIcon: false, styles: {
344
355
  body: {
345
356
  padding: 0,
346
357
  height: "100%",
@@ -350,9 +361,11 @@ const GroupMembersDrawer = ({ groupID, conversationId, currentSession, disabled
350
361
  userID: adminPermissionMember.userID,
351
362
  permissions: adminPermissionMember.permissions,
352
363
  }
353
- : undefined, onMemberPermissionsSaved: patchMemberPermissions })), _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, forceRender: true, 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, forceRender: true, children: _jsx("p", { children: t("remove_member_confirm_message", {
364
+ : undefined, onMemberPermissionsSaved: patchMemberPermissions })), _jsx(ConfirmModal, { open: showLeaveConfirm, onOk: handleConfirmLeave, onCancel: () => setShowLeaveConfirm(false), title: t("leave_group_confirm_title"), okText: t("leave_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isLeaving, children: _jsx("p", { children: t("leave_group_confirm_message") }) }), _jsx(ConfirmModal, { 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", children: _jsx("p", { children: t("remove_member_confirm_message", {
365
+ name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
366
+ }) }) }), _jsx(ConfirmModal, { 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", children: _jsx("p", { children: t("transfer_owner_confirm_message", {
354
367
  name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
355
- }) }) }), _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, forceRender: true, children: _jsx("p", { children: t("transfer_owner_confirm_message", {
368
+ }) }) }), _jsx(ConfirmModal, { open: (confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.type) === "assign-admin", onOk: handleConfirmAssignAdmin, onCancel: () => setConfirmMemberAction(null), title: t("assign_admin_confirm_title"), okText: t("agree"), cancelText: t("cancel"), children: _jsx("p", { children: t("assign_admin_confirm_message", {
356
369
  name: confirmMemberAction === null || confirmMemberAction === void 0 ? void 0 : confirmMemberAction.member.nickname,
357
370
  }) }) })] }));
358
371
  };
@@ -1 +1 @@
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
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAgCA,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAgS3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -1,10 +1,11 @@
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, message as toastMessage } from "antd";
4
+ import { Button, Empty, 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";
8
+ import { GroupMemberRole, GroupStatus } from "@droppii-org/wasm-client-sdk";
8
9
  import emitter from "../../utils/events";
9
10
  import MessageItem from "./item";
10
11
  import MessageInfiniteScroll from "./MessageInfiniteScroll";
@@ -24,6 +25,7 @@ import { useGroupPermission } from "../../hooks/group/useGroupPermission";
24
25
  import { isGroupSession } from "../../utils/imCommon";
25
26
  import { ConnectStatus, SessionStatus } from "../../types/chat";
26
27
  import PinLimitModal from "../../components/conversation-detail/PinLimitModal";
28
+ import ConfirmModal from "../../components/common/ConfirmModal";
27
29
  dayjs.extend(isToday);
28
30
  const MessageList = (props) => {
29
31
  const { t } = useTranslation();
@@ -32,7 +34,19 @@ const MessageList = (props) => {
32
34
  const conversationData = useConversationStore((state) => state.conversationData);
33
35
  const permissions = useGroupPermission(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID);
34
36
  const isGroupChat = isGroupSession(conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType);
35
- const canSendMessage = !isGroupChat || permissions.sendMessage;
37
+ const currentGroupInfo = useConversationStore((state) => state.currentGroupInfo);
38
+ const currentMemberInGroup = useConversationStore((state) => state.currentMemberInGroup);
39
+ // Group/member data refetches async on conversation switch and can briefly
40
+ // hold the previous conversation's groupID or a not-yet-loaded roleLevel.
41
+ const isGroupStateFresh = !isGroupChat ||
42
+ ((currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupID) === (conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID) &&
43
+ (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.groupID) === (conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID) &&
44
+ (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) !== undefined);
45
+ // Group-wide mute (GroupThreadInfo.tsx) restricts Normal members only.
46
+ const isRestrictedByGroupMute = isGroupChat &&
47
+ (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.status) === GroupStatus.Muted &&
48
+ (currentMemberInGroup === null || currentMemberInGroup === void 0 ? void 0 : currentMemberInGroup.roleLevel) === GroupMemberRole.Normal;
49
+ const canSendMessage = !isGroupChat || (permissions.sendMessage && !isRestrictedByGroupMute);
36
50
  const canPinMessage = !isGroupChat || permissions.pinMessage;
37
51
  const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
38
52
  const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
@@ -136,6 +150,6 @@ const MessageList = (props) => {
136
150
  if (!conversationData) {
137
151
  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") })) }));
138
152
  }
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) })] }));
153
+ 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 || !isGroupStateFresh ? (_jsx("div", { className: "border-t bg-white py-4 flex items-center justify-center", children: _jsx(Spin, {}) })) : (latestConversationSession === null || latestConversationSession === void 0 ? void 0 : latestConversationSession.status) === SessionStatus.UNASSIGNED ? (_jsx(UnassignedSessionFooter, { sessionId: latestConversationSession.id, teamId: latestConversationSession.teamId })) : shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute ? "send_message_disabled_group_mute" : "send_message_disabled") }) })) : (_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })), _jsx(ConfirmModal, { open: showConfirmRevoke, onOk: onRevokeMessage, onCancel: handleCloseRevoke, title: t("revoke_message_confirm_title"), okText: t("revoke"), cancelText: t("cancel"), okType: "danger", confirmLoading: isRevoking, children: _jsx("p", { children: t("revoke_message_confirm_message") }) }), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
140
154
  };
141
155
  export default MessageList;
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from "react";
1
+ import { type ReactNode } from "react";
2
2
  export interface MessageBubbleProps {
3
3
  isMine: boolean;
4
4
  content: ReactNode;
@@ -8,7 +8,8 @@ export interface MessageBubbleProps {
8
8
  isInternal?: boolean;
9
9
  id?: string;
10
10
  isTransparent?: boolean;
11
+ timeLabel?: string;
11
12
  }
12
- declare const MessageBubble: ({ isMine, content, senderName, showSenderName, showTip, isInternal, id, isTransparent, }: MessageBubbleProps) => import("react/jsx-runtime").JSX.Element;
13
+ declare const MessageBubble: ({ isMine, content, senderName, showSenderName, showTip, isInternal, id, isTransparent, timeLabel, }: MessageBubbleProps) => import("react/jsx-runtime").JSX.Element;
13
14
  export default MessageBubble;
14
15
  //# sourceMappingURL=MessageBubble.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageBubble.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/MessageBubble.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,SAAS,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AA4CD,QAAA,MAAM,aAAa,GAAI,0FASpB,kBAAkB,4CA6CpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"MessageBubble.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/MessageBubble.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,SAAS,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4CD,QAAA,MAAM,aAAa,GAAI,qGAUpB,kBAAkB,4CAiEpB,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import clsx from "clsx";
4
+ import { useState } from "react";
4
5
  const INCOMING_TAIL_PATH = "M1.3798 18H4V0C3.51762 4.5 3.34269 10.5 0.966518 14C-0.568384 16.2608 -0.159364 18 1.3798 18Z";
5
6
  const OUTGOING_TAIL_PATH = "M2.6202 18H0V0C0.482375 4.5 0.657308 10.5 3.03348 14C4.56838 16.2608 4.15936 18 2.6202 18Z";
6
7
  const MessageBubbleTail = ({ isMine, isInternal, }) => (_jsx("div", { className: clsx("flex shrink-0 items-center justify-center", isInternal
@@ -8,8 +9,10 @@ const MessageBubbleTail = ({ isMine, isInternal, }) => (_jsx("div", { className:
8
9
  : isMine
9
10
  ? "text-chat-bubble-mine"
10
11
  : "text-chat-bubble-neutral"), children: _jsx("div", { className: "flex-none", children: _jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "4", height: "18", viewBox: "0 0 4 18", fill: "none", "aria-hidden": true, children: _jsx("path", { d: isMine ? OUTGOING_TAIL_PATH : INCOMING_TAIL_PATH, fill: "currentColor", fillOpacity: "0.1" }) }) }) }));
11
- const MessageBubble = ({ isMine, content, senderName, showSenderName, showTip = true, isInternal = false, id, isTransparent = false, }) => {
12
+ const MessageBubble = ({ isMine, content, senderName, showSenderName, showTip = true, isInternal = false, id, isTransparent = false, timeLabel, }) => {
12
13
  const showTail = showTip && !isTransparent;
14
+ const [timeRevealed, setTimeRevealed] = useState(false);
15
+ const showTimeLabel = Boolean(timeLabel) && (showTip || timeRevealed);
13
16
  const bubbleClass = clsx("relative max-w-full", isTransparent
14
17
  ? "bg-transparent p-0"
15
18
  : clsx("p-2", showTip
@@ -21,6 +24,9 @@ const MessageBubble = ({ isMine, content, senderName, showSenderName, showTip =
21
24
  : isMine
22
25
  ? "bg-chat-bubble-mine/10"
23
26
  : "bg-chat-bubble-neutral/10"));
24
- return (_jsxs("div", { className: "relative flex items-end", "data-testid": "message-bubble", children: [!isMine && showTail && (_jsx(MessageBubbleTail, { isMine: isMine, isInternal: isInternal })), _jsx("div", { className: bubbleClass, id: id, children: _jsxs("div", { className: clsx("flex flex-col gap-1", !isTransparent && "px-1"), children: [showSenderName && senderName && (_jsx("span", { className: "text-[12px] font-medium leading-[160%] tracking-[0.12px] text-gray-400", children: senderName })), _jsx("div", { className: "flex flex-col gap-2", children: content })] }) }), isMine && showTail && (_jsx(MessageBubbleTail, { isMine: isMine, isInternal: isInternal }))] }));
27
+ return (_jsxs("div", { className: "relative flex items-end", "data-testid": "message-bubble", onClick: () => {
28
+ if (!showTip && timeLabel)
29
+ setTimeRevealed((prev) => !prev);
30
+ }, children: [!isMine && showTail && (_jsx(MessageBubbleTail, { isMine: isMine, isInternal: isInternal })), _jsx("div", { className: bubbleClass, id: id, children: _jsxs("div", { className: clsx("flex flex-col gap-1", !isTransparent && "px-1"), children: [showSenderName && senderName && (_jsx("span", { className: "text-[12px] font-medium leading-[160%] tracking-[0.12px] text-gray-400", children: senderName })), _jsx("div", { className: "flex flex-col gap-2", children: content })] }) }), isMine && showTail && (_jsx(MessageBubbleTail, { isMine: isMine, isInternal: isInternal })), showTimeLabel && (_jsx("span", { "data-testid": "message-time-label", className: clsx("absolute -bottom-2 flex h-[19px] w-[30px] items-center justify-center rounded-full bg-white text-[10px] text-gray-400 shadow-sm", isMine ? "right-1" : "left-1"), children: timeLabel }))] }));
25
31
  };
26
32
  export default MessageBubble;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,WAAW,IAAI,eAAe,EAG/B,MAAM,8BAA8B,CAAC;AAkCtC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAoBD,QAAA,MAAM,WAAW,GAAI,qMAalB,gBAAgB,mDA8RlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,WAAW,IAAI,eAAe,EAG/B,MAAM,8BAA8B,CAAC;AAmCtC,UAAU,gBAAgB;IACxB,OAAO,EAAE,eAAe,CAAC;IACzB,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,eAAe,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAClD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAyBD,QAAA,MAAM,WAAW,GAAI,qMAalB,gBAAgB,mDA6SlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -8,7 +8,8 @@ import MessageBubble from "./MessageBubble";
8
8
  import { renderBubbleContent } from "./renderMessageContent";
9
9
  import { getVisibleNeighbor, isMessageHiddenForViewer, } from "../../../hooks/message/useMessage";
10
10
  import { MSG_ITEM_CONTENT_PREFIX, MSG_ITEM_PREFIX } from "../../../constants";
11
- import { formatTimestamp } from "../../../utils/common";
11
+ import { formatDividerTimestamp } from "../../../utils/common";
12
+ import { CustomMessageType } from "../../../types/customMessage";
12
13
  import useAuthStore from "../../../store/auth";
13
14
  import useConversationStore from "../../../store/conversation";
14
15
  import { useMemo, useRef } from "react";
@@ -22,6 +23,9 @@ import GroupNotificationMessageItem from "./GroupNotificationMessage";
22
23
  import { isGroupNotificationMessageType, isSystemLogMessageType } from "../../../utils/messageLog";
23
24
  import { buildMessageToolboxMenuItems, getCopyableMessageText, isCopyableMessageType, isPinnableMessageType, MessageToolboxActionKey, MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, useMessageToolboxVisibility, } from "../../../components/message/toolbox";
24
25
  const MESSAGE_AVATAR_SIZE = 24;
26
+ const DIVIDER_GAP_MINUTES = 30;
27
+ const GROUPING_GAP_MINUTES = 5;
28
+ const isOrderMessage = (message) => { var _a; return ((_a = message === null || message === void 0 ? void 0 : message.customElem) === null || _a === void 0 ? void 0 : _a.extension) === CustomMessageType.Order; };
25
29
  const MessageAvatar = ({ avatarUrl, senderNickname, }) => {
26
30
  var _a;
27
31
  return (_jsx(Avatar, { size: MESSAGE_AVATAR_SIZE, src: avatarUrl, className: "shrink-0 self-end", children: ((_a = senderNickname === null || senderNickname === void 0 ? void 0 : senderNickname.charAt) === null || _a === void 0 ? void 0 : _a.call(senderNickname, 0)) || "A" }));
@@ -127,10 +131,21 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
127
131
  message.clientMsgID;
128
132
  const previousMessage = getVisibleNeighbor(allMessages, message, "prev");
129
133
  const nextMessage = getVisibleNeighbor(allMessages, message, "next");
130
- const prevSameUser = (previousMessage === null || previousMessage === void 0 ? void 0 : previousMessage.sendID) === (message === null || message === void 0 ? void 0 : message.sendID);
131
- const nextSameUser = (nextMessage === null || nextMessage === void 0 ? void 0 : nextMessage.sendID) === (message === null || message === void 0 ? void 0 : message.sendID);
134
+ const isOrder = isOrderMessage(message);
135
+ const prevSameUser = !isOrder &&
136
+ !isOrderMessage(previousMessage) &&
137
+ (previousMessage === null || previousMessage === void 0 ? void 0 : previousMessage.sendID) === (message === null || message === void 0 ? void 0 : message.sendID) &&
138
+ Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "minute")) <=
139
+ GROUPING_GAP_MINUTES;
140
+ const nextSameUser = !isOrder &&
141
+ !isOrderMessage(nextMessage) &&
142
+ (nextMessage === null || nextMessage === void 0 ? void 0 : nextMessage.sendID) === (message === null || message === void 0 ? void 0 : message.sendID) &&
143
+ Math.abs(dayjs(nextMessage.sendTime).diff(message.sendTime, "minute")) <=
144
+ GROUPING_GAP_MINUTES;
132
145
  const showTimeBreak = !previousMessage ||
133
- !dayjs(message.sendTime).isSame(previousMessage.sendTime, "days");
146
+ !dayjs(message.sendTime).isSame(previousMessage.sendTime, "days") ||
147
+ Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "minute")) >
148
+ DIVIDER_GAP_MINUTES;
134
149
  const showPeerInfo = isGroupChat || showSenderInfo;
135
150
  const showAvatar = showPeerInfo && !isMine && !nextSameUser;
136
151
  const showSenderName = showPeerInfo && !isMine && !prevSameUser;
@@ -142,7 +157,7 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
142
157
  className: "flex flex-col gap-2 py-1 px-3 sm:px-4 min-w-0",
143
158
  id: `${MSG_ITEM_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`,
144
159
  };
145
- const timeBreakNode = showTimeBreak ? (_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "rounded-full bg-neutral-100 px-2 py-1 text-center text-xs text-gray-600", children: formatTimestamp(message.sendTime, { hasTime: true }) }) })) : null;
160
+ const timeBreakNode = showTimeBreak ? (_jsx("div", { className: "flex justify-center", children: _jsx("span", { className: "rounded-full bg-neutral-100 px-2 py-1 text-center text-xs text-gray-600", children: formatDividerTimestamp(message.sendTime) }) })) : null;
146
161
  if (isSystemLogMessage) {
147
162
  return (_createElement("div", Object.assign({}, messageContainerProps, { key: message === null || message === void 0 ? void 0 : message.clientMsgID }),
148
163
  timeBreakNode,
@@ -153,8 +168,8 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
153
168
  timeBreakNode,
154
169
  _jsx(GroupNotificationMessageItem, { message: message })));
155
170
  }
156
- const bubbleNode = (_jsx(MessageBubble, { isMine: isMine, isInternal: isInternalMessage, senderName: message === null || message === void 0 ? void 0 : message.senderNickname, showSenderName: showSenderName, showTip: showTip, id: `${MSG_ITEM_CONTENT_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`, content: renderBubbleContent(message, onPressQuoteMessage), isTransparent: isStickerMessage }));
157
- const showPinnedBadge = Boolean(message === null || message === void 0 ? void 0 : message.isPinned) && Boolean(onPinMessage);
171
+ const bubbleNode = (_jsx(MessageBubble, { isMine: isMine, isInternal: isInternalMessage, senderName: message === null || message === void 0 ? void 0 : message.senderNickname, showSenderName: showSenderName, showTip: showTip, id: `${MSG_ITEM_CONTENT_PREFIX}${message === null || message === void 0 ? void 0 : message.clientMsgID}`, content: renderBubbleContent(message, onPressQuoteMessage), isTransparent: isStickerMessage, timeLabel: dayjs.tz(message.sendTime).format("HH:mm") }));
172
+ const showPinnedBadge = Boolean(message === null || message === void 0 ? void 0 : message.isPinned);
158
173
  const pinnedBadgeNode = showPinnedBadge ? (_jsx("span", { "data-testid": "message-pinned-badge", className: clsx("absolute -top-2.5 z-[1] flex h-[22px] w-[26px] items-center justify-center rounded-full border border-white bg-gray-25", isMine ? "left-0" : "right-0"), children: _jsx(Icon, { icon: "thumbtack-b", size: 12, className: "text-primary-400" }) })) : null;
159
174
  const wrappedBubble = hasContextMenu ? (_jsx(Dropdown, { menu: { items: contextMenuItems }, trigger: ["contextMenu"], open: contextMenuOpen, onOpenChange: onContextMenuOpenChange, overlayClassName: MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, children: _jsxs("div", { ref: bubbleWrapperRef, className: "relative min-w-0", children: [bubbleNode, pinnedBadgeNode] }) })) : (_jsxs("div", { className: "relative min-w-0", children: [bubbleNode, pinnedBadgeNode] }));
160
175
  return (_createElement("div", Object.assign({}, messageContainerProps, { key: message === null || message === void 0 ? void 0 : message.clientMsgID }),
@@ -1 +1 @@
1
- {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AAmBA,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,mDAigBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AAoBA,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,mDAyjBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}