@droppii-org/chat-sdk 0.6.8 → 0.6.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/chat-bubble/ChatBubble.d.ts +2 -1
- package/dist/components/chat-bubble/ChatBubble.d.ts.map +1 -1
- package/dist/components/chat-bubble/ChatBubble.js +3 -3
- package/dist/components/conversation/DeskConversationList.d.ts.map +1 -1
- package/dist/components/conversation/DeskConversationList.js +3 -2
- package/dist/components/conversation-detail/ConversationMessageList.d.ts +7 -1
- package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationMessageList.js +17 -3
- package/dist/components/group/InviteLinkPanel.d.ts +14 -0
- package/dist/components/group/InviteLinkPanel.d.ts.map +1 -0
- package/dist/components/group/InviteLinkPanel.js +61 -0
- package/dist/components/media-resources/MediaResourceTabs.d.ts.map +1 -1
- package/dist/components/media-resources/MediaResourceTabs.js +11 -1
- package/dist/components/message/MessageList.d.ts +1 -0
- package/dist/components/message/MessageList.d.ts.map +1 -1
- package/dist/components/message/MessageList.js +14 -5
- package/dist/components/message/item/index.d.ts +2 -1
- package/dist/components/message/item/index.d.ts.map +1 -1
- package/dist/components/message/item/index.js +19 -7
- package/dist/components/message/toolbox/types.d.ts +1 -0
- package/dist/components/message/toolbox/types.d.ts.map +1 -1
- package/dist/components/message/toolbox/types.js +1 -0
- package/dist/components/message/toolbox/useMessageToolboxVisibility.d.ts +1 -0
- package/dist/components/message/toolbox/useMessageToolboxVisibility.d.ts.map +1 -1
- package/dist/components/message/toolbox/useMessageToolboxVisibility.js +7 -3
- package/dist/components/thread/GroupThreadInfo.d.ts.map +1 -1
- package/dist/components/thread/GroupThreadInfo.js +17 -1
- package/dist/hooks/conversation/useActiveConversation.d.ts.map +1 -1
- package/dist/hooks/conversation/useActiveConversation.js +34 -7
- package/dist/hooks/group/useInviteLinkAction.d.ts +23 -0
- package/dist/hooks/group/useInviteLinkAction.d.ts.map +1 -0
- package/dist/hooks/group/useInviteLinkAction.js +83 -0
- package/dist/locales/vi/common.json +18 -1
- package/dist/screens/chat-bubble/index.d.ts +5 -0
- package/dist/screens/chat-bubble/index.d.ts.map +1 -1
- package/dist/screens/chat-bubble/index.js +2 -2
- package/dist/screens/conversation-detail/index.d.ts +6 -1
- package/dist/screens/conversation-detail/index.d.ts.map +1 -1
- package/dist/screens/conversation-detail/index.js +5 -2
- package/dist/screens/conversation-inbox/index.d.ts +6 -0
- package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
- package/dist/screens/conversation-inbox/index.js +2 -2
- package/dist/screens/desk-message/index.d.ts +2 -1
- package/dist/screens/desk-message/index.d.ts.map +1 -1
- package/dist/screens/desk-message/index.js +3 -3
- package/dist/services/joinLink.d.ts +18 -0
- package/dist/services/joinLink.d.ts.map +1 -0
- package/dist/services/joinLink.js +30 -0
- package/dist/services/query.d.ts +1 -0
- package/dist/services/query.d.ts.map +1 -1
- package/dist/services/query.js +1 -0
- package/dist/services/routes.d.ts +2 -0
- package/dist/services/routes.d.ts.map +1 -1
- package/dist/services/routes.js +2 -0
- package/dist/styles/global.css +1 -1
- package/dist/types/dto.d.ts +6 -0
- package/dist/types/dto.d.ts.map +1 -1
- package/dist/utils/inviteLink.d.ts +14 -0
- package/dist/utils/inviteLink.d.ts.map +1 -0
- package/dist/utils/inviteLink.js +20 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
interface ChatBubbleProps {
|
|
2
2
|
className?: string;
|
|
3
|
+
onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
|
|
3
4
|
}
|
|
4
|
-
declare const ChatBubble: ({ className }: ChatBubbleProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
declare const ChatBubble: ({ className, onCopyMessageLink }: ChatBubbleProps) => import("react/jsx-runtime").JSX.Element;
|
|
5
6
|
export default ChatBubble;
|
|
6
7
|
//# sourceMappingURL=ChatBubble.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatBubble.d.ts","sourceRoot":"","sources":["../../../src/components/chat-bubble/ChatBubble.tsx"],"names":[],"mappings":"AASA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatBubble.d.ts","sourceRoot":"","sources":["../../../src/components/chat-bubble/ChatBubble.tsx"],"names":[],"mappings":"AASA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,UAAU,GAAI,kCAAkC,eAAe,4CA8GpE,CAAC;AAEF,eAAe,UAAU,CAAC"}
|
|
@@ -7,7 +7,7 @@ import useConversationStore from "../../store/conversation";
|
|
|
7
7
|
import { useIsMobile } from "../../hooks/common/useIsMobile";
|
|
8
8
|
import { useBoolean } from "ahooks";
|
|
9
9
|
import { ANTD_PRIMARY_COLOR_VAR } from "../../constants";
|
|
10
|
-
const ChatBubble = ({ className }) => {
|
|
10
|
+
const ChatBubble = ({ className, onCopyMessageLink }) => {
|
|
11
11
|
const isMobile = useIsMobile();
|
|
12
12
|
const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
|
|
13
13
|
const searchClientMsgID = useConversationStore((state) => state.searchClientMsgID);
|
|
@@ -24,9 +24,9 @@ const ChatBubble = ({ className }) => {
|
|
|
24
24
|
body: { padding: 0 },
|
|
25
25
|
}, classNames: {
|
|
26
26
|
wrapper: "!z-[9999]",
|
|
27
|
-
}, width: "100%", push: false, children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID }) })] }));
|
|
27
|
+
}, width: "100%", push: false, children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID, onCopyMessageLink: onCopyMessageLink }) })] }));
|
|
28
28
|
}
|
|
29
|
-
return (_jsx(Popover, { placement: "topLeft", trigger: "click", open: isOpen, destroyOnHidden: true, content: () => (_jsx("div", { className: "w-[400px] h-[600px]", children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID }) })), styles: { body: { padding: 0 } }, children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx(FloatButton, { icon: _jsx("span", { className: "text-white", children: isOpen ? _jsx(CloseOutlined, {}) : _jsx(MessageOutlined, {}) }), type: "primary", style: {
|
|
29
|
+
return (_jsx(Popover, { placement: "topLeft", trigger: "click", open: isOpen, destroyOnHidden: true, content: () => (_jsx("div", { className: "w-[400px] h-[600px]", children: _jsx(MessageList, { conversationId: selectedConversationId, className: "flex-1", onClose: setOpenFalse, searchClientMsgID: searchClientMsgID, onCopyMessageLink: onCopyMessageLink }) })), styles: { body: { padding: 0 } }, children: _jsx(ConfigProvider, { theme: { token: { colorPrimary: ANTD_PRIMARY_COLOR_VAR } }, children: _jsx(FloatButton, { icon: _jsx("span", { className: "text-white", children: isOpen ? _jsx(CloseOutlined, {}) : _jsx(MessageOutlined, {}) }), type: "primary", style: {
|
|
30
30
|
right: 24,
|
|
31
31
|
bottom: 24,
|
|
32
32
|
width: 60,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+
|
|
1
|
+
{"version":3,"file":"DeskConversationList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation/DeskConversationList.tsx"],"names":[],"mappings":"AA4BA,QAAA,MAAM,oBAAoB,+CAwVzB,CAAC;AAEF,eAAe,oBAAoB,CAAC"}
|
|
@@ -83,6 +83,7 @@ const DeskConversationList = () => {
|
|
|
83
83
|
}, [filterSummary, isAllGroupsView]);
|
|
84
84
|
useEffect(() => {
|
|
85
85
|
const threadId = searchParams.get("threadId");
|
|
86
|
+
const startClientMsgID = searchParams.get("messageId") || undefined;
|
|
86
87
|
if (!threadId) {
|
|
87
88
|
useConversationStore.getState().clearSelectedConversation();
|
|
88
89
|
return;
|
|
@@ -98,7 +99,7 @@ const DeskConversationList = () => {
|
|
|
98
99
|
// threadId can come from a notification/stale search click — force a
|
|
99
100
|
// fresh membership check instead of trusting the groupID cache.
|
|
100
101
|
if (selectedConversation) {
|
|
101
|
-
setConversationData(selectedConversation,
|
|
102
|
+
setConversationData(selectedConversation, startClientMsgID, {
|
|
102
103
|
forceRevalidate: true,
|
|
103
104
|
});
|
|
104
105
|
}
|
|
@@ -108,7 +109,7 @@ const DeskConversationList = () => {
|
|
|
108
109
|
// (e.g. the user was removed from that group mid-flight) — only
|
|
109
110
|
// apply the result if the URL still points at this conversation.
|
|
110
111
|
if (res.data.length > 0 && searchParams.get("threadId") === threadId) {
|
|
111
|
-
setConversationData(res.data[0],
|
|
112
|
+
setConversationData(res.data[0], startClientMsgID, {
|
|
112
113
|
forceRevalidate: true,
|
|
113
114
|
});
|
|
114
115
|
updateConversationList(res.data, "filter");
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
export interface ConversationMessageListProps {
|
|
3
3
|
bottomSlot?: ReactNode;
|
|
4
|
+
/**
|
|
5
|
+
* Supplied by the host app — the SDK has no route table, so building the
|
|
6
|
+
* deep link (and copying it) is the host's job. Omit it and the "copy
|
|
7
|
+
* message link" context-menu action is hidden entirely.
|
|
8
|
+
*/
|
|
9
|
+
onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
|
|
4
10
|
}
|
|
5
|
-
declare const ConversationMessageList: ({ bottomSlot }: ConversationMessageListProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
declare const ConversationMessageList: ({ bottomSlot, onCopyMessageLink, }: ConversationMessageListProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
12
|
export default ConversationMessageList;
|
|
7
13
|
//# sourceMappingURL=ConversationMessageList.d.ts.map
|
|
@@ -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;AAmBzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,SAAS,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;IACvB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,uBAAuB,GAAI,oCAG9B,4BAA4B,4CA0N9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -17,7 +17,7 @@ import { isGroupChat as resolveIsGroupChat } from "../../utils/conversation";
|
|
|
17
17
|
import emitter from "../../utils/events";
|
|
18
18
|
import MessageItem from "../../components/message/item";
|
|
19
19
|
import PinLimitModal from "./PinLimitModal";
|
|
20
|
-
const ConversationMessageList = ({ bottomSlot }) => {
|
|
20
|
+
const ConversationMessageList = ({ bottomSlot, onCopyMessageLink, }) => {
|
|
21
21
|
const { t } = useTranslation("conversation-inbox");
|
|
22
22
|
const { t: tCommon } = useTranslation();
|
|
23
23
|
const { user } = useChatContext();
|
|
@@ -57,7 +57,13 @@ const ConversationMessageList = ({ bottomSlot }) => {
|
|
|
57
57
|
catch (_a) {
|
|
58
58
|
toastMessage.error(tCommon("pin_message_failed"));
|
|
59
59
|
}
|
|
60
|
-
}, [
|
|
60
|
+
}, [
|
|
61
|
+
conversationId,
|
|
62
|
+
pinMessage,
|
|
63
|
+
pinnedRemainingCount,
|
|
64
|
+
pinnedTotalCount,
|
|
65
|
+
tCommon,
|
|
66
|
+
]);
|
|
61
67
|
const handleUnpinMessage = useCallback(async (message) => {
|
|
62
68
|
try {
|
|
63
69
|
await unpinMessage(conversationId, message.clientMsgID);
|
|
@@ -87,6 +93,14 @@ const ConversationMessageList = ({ bottomSlot }) => {
|
|
|
87
93
|
setSearchClientMsgID(clientMsgID);
|
|
88
94
|
}
|
|
89
95
|
}, [scrollToMessage, loadState.messageList, setSearchClientMsgID]);
|
|
96
|
+
// MessageItem only knows its own clientMsgID — bind the conversation here,
|
|
97
|
+
// and keep the callback undefined when the host gave us none so MessageItem
|
|
98
|
+
// can drop the menu entry instead of rendering a no-op.
|
|
99
|
+
const handleCopyMessageLink = useCallback((clientMsgID) => {
|
|
100
|
+
if (!conversationId || !clientMsgID)
|
|
101
|
+
return;
|
|
102
|
+
onCopyMessageLink === null || onCopyMessageLink === void 0 ? void 0 : onCopyMessageLink(conversationId, clientMsgID);
|
|
103
|
+
}, [onCopyMessageLink, conversationId]);
|
|
90
104
|
useEffect(() => {
|
|
91
105
|
emitter.on("CONVERSATION_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM", scrollToBottomIfAtBottom);
|
|
92
106
|
emitter.on("CHAT_LIST_SCROLL_TO_MESSAGE", scrollToMessage);
|
|
@@ -99,6 +113,6 @@ const ConversationMessageList = ({ bottomSlot }) => {
|
|
|
99
113
|
target: scrollRef,
|
|
100
114
|
enable: !!openToolboxMessageId,
|
|
101
115
|
});
|
|
102
|
-
return (_jsxs("div", { "data-testid": "conversation-message-list", "data-cp-name": "ConversationMessageList", 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, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, enableLongPress: true }, message.clientMsgID)))] })), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
|
|
116
|
+
return (_jsxs("div", { "data-testid": "conversation-message-list", "data-cp-name": "ConversationMessageList", 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, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, onCopyMessageLink: handleCopyMessageLink, enableLongPress: true }, message.clientMsgID)))] })), _jsx(PinLimitModal, { open: pinLimitModalOpen, max: pinnedTotalCount, onClose: () => setPinLimitModalOpen(false) })] }));
|
|
103
117
|
};
|
|
104
118
|
export default ConversationMessageList;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface InviteLinkPanelProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
groupID: string;
|
|
4
|
+
conversationID: string;
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Invite-link management for a group (DROPPII-30551). Renders inline within the
|
|
9
|
+
* "Thông tin" sidebar (back arrow, no gray background), same as
|
|
10
|
+
* MemberPermissionPanel — not as a Modal/Drawer.
|
|
11
|
+
*/
|
|
12
|
+
declare const InviteLinkPanel: ({ open, groupID, conversationID, onClose }: InviteLinkPanelProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
13
|
+
export default InviteLinkPanel;
|
|
14
|
+
//# sourceMappingURL=InviteLinkPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InviteLinkPanel.d.ts","sourceRoot":"","sources":["../../../src/components/group/InviteLinkPanel.tsx"],"names":[],"mappings":"AASA,UAAU,oBAAoB;IAC5B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;GAIG;AACH,QAAA,MAAM,eAAe,GAAI,4CAA4C,oBAAoB,mDAuLxF,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Dropdown, message } from "antd";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import ConfirmModal from "../../components/common/ConfirmModal";
|
|
6
|
+
import { Icon } from "../../components/icon";
|
|
7
|
+
import { useInviteLinkAction } from "../../hooks/group/useInviteLinkAction";
|
|
8
|
+
import { buildInviteLink } from "../../utils/inviteLink";
|
|
9
|
+
/**
|
|
10
|
+
* Invite-link management for a group (DROPPII-30551). Renders inline within the
|
|
11
|
+
* "Thông tin" sidebar (back arrow, no gray background), same as
|
|
12
|
+
* MemberPermissionPanel — not as a Modal/Drawer.
|
|
13
|
+
*/
|
|
14
|
+
const InviteLinkPanel = ({ open, groupID, conversationID, onClose }) => {
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
const { link, isOwner, isLoading, isError, refetch, isMutating, confirmAction, createLink, openRegenerateConfirm, openRevokeConfirm, closeConfirm, confirmRegenerate, confirmRevoke, } = useInviteLinkAction({ groupID, conversationID, enabled: open });
|
|
17
|
+
if (!open)
|
|
18
|
+
return null;
|
|
19
|
+
const inviteUrl = (link === null || link === void 0 ? void 0 : link.code) ? buildInviteLink(link.code) : "";
|
|
20
|
+
// isError must short-circuit before the empty states — a failed fetch is
|
|
21
|
+
// NOT "no link exists" (see useInviteLinkAction.ts). Showing "Tạo link mới"
|
|
22
|
+
// on a transient network error would invite the owner to replace a link
|
|
23
|
+
// that may still be active.
|
|
24
|
+
const showOwnerEmptyState = !isLoading && !isError && !link && isOwner;
|
|
25
|
+
const showMemberEmptyState = !isLoading && !isError && !link && !isOwner;
|
|
26
|
+
const handleCopy = () => {
|
|
27
|
+
if (!inviteUrl)
|
|
28
|
+
return;
|
|
29
|
+
navigator.clipboard.writeText(inviteUrl);
|
|
30
|
+
message.success(t("copied_to_clipboard"));
|
|
31
|
+
};
|
|
32
|
+
// Icons are brand-blue in the design (not currentColor/gray) — matches the
|
|
33
|
+
// back-arrow header icon convention already used in MemberPermissionPanel.tsx.
|
|
34
|
+
const ownerMenuItems = [
|
|
35
|
+
{
|
|
36
|
+
key: "copy",
|
|
37
|
+
label: t("invite_link_action_copy"),
|
|
38
|
+
icon: _jsx(Icon, { icon: "copy-o", size: 16, color: "var(--dc-color-primary)" }),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "regenerate",
|
|
42
|
+
label: t("invite_link_action_regenerate"),
|
|
43
|
+
icon: _jsx(Icon, { icon: "refresh-o", size: 16, color: "var(--dc-color-primary)" }),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
key: "revoke",
|
|
47
|
+
label: t("invite_link_action_revoke"),
|
|
48
|
+
icon: _jsx(Icon, { icon: "unlink-alt-o", size: 16, color: "var(--dc-color-primary)" }),
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
const handleMenuClick = ({ key }) => {
|
|
52
|
+
if (key === "copy")
|
|
53
|
+
handleCopy();
|
|
54
|
+
if (key === "regenerate")
|
|
55
|
+
openRegenerateConfirm();
|
|
56
|
+
if (key === "revoke")
|
|
57
|
+
openRevokeConfirm();
|
|
58
|
+
};
|
|
59
|
+
return (_jsxs("div", { className: "absolute inset-0 bg-white flex flex-col z-10", "data-cp-name": "InviteLinkPanel", children: [_jsxs("div", { className: "flex w-full items-center border-b border-gray-100 p-2", children: [_jsx("button", { type: "button", className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-[var(--dc-color-primary)]", 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("invite_via_link") })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: [showOwnerEmptyState && (_jsxs("div", { className: "flex flex-col items-center gap-4 text-center", children: [_jsx("div", { className: "flex h-24 w-24 items-center justify-center rounded-full bg-gray-100", children: _jsx(Icon, { icon: "link-alt-o", size: 40, className: "text-gray-300" }) }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-base font-bold text-gray-900", children: t("invite_link_empty_owner_title") }), _jsx("p", { className: "text-sm text-gray-400", children: t("invite_link_empty_owner_subtitle") })] }), _jsx("button", { type: "button", className: "w-full rounded-full bg-[var(--dc-color-primary)] px-4 py-2.5 text-sm font-medium text-white disabled:bg-gray-200 disabled:text-gray-400", disabled: isMutating, onClick: createLink, children: t("invite_link_create_button") })] })), showMemberEmptyState && (_jsxs("div", { className: "flex flex-col items-center gap-4 text-center", children: [_jsx("div", { className: "flex h-24 w-24 items-center justify-center rounded-full bg-gray-100", children: _jsx(Icon, { icon: "link-alt-o", size: 40, className: "text-gray-300" }) }), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("p", { className: "text-base font-bold text-gray-900", children: t("invite_link_empty_member_title") }), _jsx("p", { className: "text-sm text-gray-400", children: t("invite_link_empty_member_subtitle") })] })] })), isError && (_jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [_jsx("p", { className: "text-sm text-gray-400", children: t("invite_link_load_failed") }), _jsx("button", { type: "button", className: "rounded-full border border-gray-300 px-4 py-1.5 text-sm font-medium text-gray-700", onClick: () => refetch(), children: t("retry") })] })), !isLoading && !isError && link && (_jsxs("div", { className: "flex flex-col gap-2 rounded-lg bg-gray-25 p-3", children: [_jsx("p", { className: "text-sm font-semibold text-gray-900", children: t("invite_link_box_label") }), _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("p", { className: "min-w-0 flex-1 break-all rounded-lg bg-white px-3 py-2 text-sm text-gray-700", children: inviteUrl }), isOwner ? (_jsx(Dropdown, { menu: { items: ownerMenuItems, onClick: handleMenuClick }, trigger: ["click"], children: _jsx("button", { type: "button", className: "flex shrink-0 items-center justify-center p-2 text-gray-500", children: _jsx(Icon, { icon: "more-horizontal-o", size: 18 }) }) })) : (_jsx("button", { type: "button", className: "flex shrink-0 items-center justify-center p-2 text-gray-500", onClick: handleCopy, "aria-label": t("invite_link_action_copy"), children: _jsx(Icon, { icon: "copy-o", size: 18, color: "var(--dc-color-primary)" }) }))] })] }))] }), _jsx(ConfirmModal, { open: confirmAction === "regenerate", onOk: confirmRegenerate, onCancel: closeConfirm, title: t("invite_link_confirm_title"), okText: t("agree"), cancelText: t("cancel"), confirmLoading: isMutating, children: _jsx("p", { children: t("invite_link_regenerate_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "revoke", onOk: confirmRevoke, onCancel: closeConfirm, title: t("invite_link_confirm_title"), okText: t("agree"), cancelText: t("cancel"), confirmLoading: isMutating, children: _jsx("p", { children: t("invite_link_revoke_confirm_message") }) })] }));
|
|
60
|
+
};
|
|
61
|
+
export default InviteLinkPanel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaResourceTabs.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/MediaResourceTabs.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAED,QAAA,MAAM,iBAAiB,GAAI,iBAExB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"MediaResourceTabs.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/MediaResourceTabs.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOlD,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAED,QAAA,MAAM,iBAAiB,GAAI,iBAExB,sBAAsB,4CAyBxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
4
|
import { MediaResourceTabKey } from "./constants";
|
|
5
5
|
import MediaResourceTabBar from "./MediaResourceTabBar";
|
|
6
6
|
import ImageResourceGrid from "./ImageResourceGrid";
|
|
@@ -9,6 +9,16 @@ import FileResourceList from "./FileResourceList";
|
|
|
9
9
|
import LinkResourceList from "./LinkResourceList";
|
|
10
10
|
const MediaResourceTabs = ({ initialTab = MediaResourceTabKey.Image, }) => {
|
|
11
11
|
const [activeKey, setActiveKey] = useState(initialTab);
|
|
12
|
+
// MediaResourcePanel's Drawer uses forceRender, so this component stays
|
|
13
|
+
// mounted across close/reopen cycles instead of remounting fresh each
|
|
14
|
+
// time — useState(initialTab) alone only applies on the very first
|
|
15
|
+
// mount, so every entry point after the first (e.g. clicking "Video" in
|
|
16
|
+
// MediaEntryCard after "Ảnh" was already opened once) was silently
|
|
17
|
+
// ignored and the panel kept showing whichever tab happened to be active
|
|
18
|
+
// when it first mounted. Re-sync whenever the caller passes a new target.
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setActiveKey(initialTab);
|
|
21
|
+
}, [initialTab]);
|
|
12
22
|
return (_jsxs("div", { "data-cp-name": "MediaResourceTabs", className: "flex h-full flex-col bg-white", children: [_jsx(MediaResourceTabBar, { activeKey: activeKey, onChange: setActiveKey }), _jsxs("div", { className: "min-h-0 flex-1", children: [activeKey === MediaResourceTabKey.Image && _jsx(ImageResourceGrid, {}), activeKey === MediaResourceTabKey.Video && _jsx(VideoResourceGrid, {}), activeKey === MediaResourceTabKey.File && _jsx(FileResourceList, {}), activeKey === MediaResourceTabKey.Link && _jsx(LinkResourceList, {})] })] }));
|
|
13
23
|
};
|
|
14
24
|
export default MediaResourceTabs;
|
|
@@ -5,6 +5,7 @@ interface MessageListProps {
|
|
|
5
5
|
onClose?: () => void;
|
|
6
6
|
openCreateCannedModal?: () => void;
|
|
7
7
|
onOpenThreadInfo?: () => void;
|
|
8
|
+
onCopyMessageLink?: (conversationId: string, clientMsgID: string) => void;
|
|
8
9
|
}
|
|
9
10
|
declare const MessageList: (props: MessageListProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
11
|
export default MessageList;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAiCA,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;
|
|
1
|
+
{"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../../src/components/message/MessageList.tsx"],"names":[],"mappings":"AAiCA,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;IAC9B,iBAAiB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED,QAAA,MAAM,WAAW,GAAI,OAAO,gBAAgB,4CAsW3C,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -31,7 +31,7 @@ dayjs.extend(isToday);
|
|
|
31
31
|
const MessageList = (props) => {
|
|
32
32
|
const { t } = useTranslation();
|
|
33
33
|
const { user, connectStatus } = useChatContext();
|
|
34
|
-
const { onClose, conversationId, searchClientMsgID, openCreateCannedModal, onOpenThreadInfo, } = props;
|
|
34
|
+
const { onClose, conversationId, searchClientMsgID, openCreateCannedModal, onOpenThreadInfo, onCopyMessageLink, } = props;
|
|
35
35
|
const conversationData = useConversationStore((state) => state.conversationData);
|
|
36
36
|
const permissions = useGroupPermission(conversationData === null || conversationData === void 0 ? void 0 : conversationData.groupID);
|
|
37
37
|
const isGroupChat = isGroupConversation(conversationData !== null && conversationData !== void 0 ? conversationData : undefined);
|
|
@@ -63,7 +63,9 @@ const MessageList = (props) => {
|
|
|
63
63
|
// by design, not because it's a removed member.
|
|
64
64
|
const isConfirmedNonMember = isConfirmedGroup && isGroupInfoFresh && !currentMemberInGroup;
|
|
65
65
|
const canSendMessage = !isConfirmedGroup ||
|
|
66
|
-
(!isConfirmedNonMember &&
|
|
66
|
+
(!isConfirmedNonMember &&
|
|
67
|
+
permissions.sendMessage &&
|
|
68
|
+
!isRestrictedByGroupMute);
|
|
67
69
|
const canPinMessage = !isConfirmedGroup || (!isConfirmedNonMember && permissions.pinMessage);
|
|
68
70
|
const setQuotedMessage = useConversationStore((state) => state.setQuotedMessage);
|
|
69
71
|
const setSearchClientMsgID = useConversationStore((state) => state.setSearchClientMsgID);
|
|
@@ -79,7 +81,7 @@ const MessageList = (props) => {
|
|
|
79
81
|
const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
|
|
80
82
|
const pinnedRemainingCount = useConversationStore((state) => state.pinnedRemainingCount);
|
|
81
83
|
const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
|
|
82
|
-
const { rawGroupId, latestConversationSession, refetchConversationSessions
|
|
84
|
+
const { rawGroupId, latestConversationSession, refetchConversationSessions } = useConversationSessionState({
|
|
83
85
|
conversationId,
|
|
84
86
|
conversationType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType,
|
|
85
87
|
peerType: conversationData === null || conversationData === void 0 ? void 0 : conversationData.peerType,
|
|
@@ -148,6 +150,11 @@ const MessageList = (props) => {
|
|
|
148
150
|
toastMessage.error(t("unpin_message_failed"));
|
|
149
151
|
}
|
|
150
152
|
}, [conversationId, unpinMessage, t]);
|
|
153
|
+
const handleCopyMessageLink = useCallback((clientMsgID) => {
|
|
154
|
+
if (!conversationId || !clientMsgID)
|
|
155
|
+
return;
|
|
156
|
+
onCopyMessageLink === null || onCopyMessageLink === void 0 ? void 0 : onCopyMessageLink(conversationId, clientMsgID);
|
|
157
|
+
}, [onCopyMessageLink, conversationId]);
|
|
151
158
|
useEffect(() => {
|
|
152
159
|
emitter.on("CHAT_LIST_SCROLL_TO_BOTTOM", scrollToBottom);
|
|
153
160
|
emitter.on("CHAT_LIST_SCROLL_TO_MESSAGE", scrollToMessage);
|
|
@@ -180,8 +187,10 @@ const MessageList = (props) => {
|
|
|
180
187
|
if (!conversationData) {
|
|
181
188
|
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") })) }));
|
|
182
189
|
}
|
|
183
|
-
return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (Boolean(rawGroupId) && 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 })) : !isConfirmedGroup && shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", "data-cp-name": "JoinGroupRequired", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
|
|
190
|
+
return (_jsxs("div", { className: "flex flex-col flex-1 relative h-full min-w-0 overflow-hidden bg-white", "data-cp-name": "MessageList", children: [_jsx(MessageHeader, { onClose: onClose, currentSession: latestConversationSession, isJoined: isJoined, onOpenThreadInfo: onOpenThreadInfo }), _jsx(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, children: loadState.messageList.map((message, _, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, contextMenuOpen: openMenuId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenMenuId(open ? message.clientMsgID : null), onRevokeMessage: handleOpenRevoke, onQuoteMessage: setQuotedMessage, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: canPinMessage ? handlePinMessage : undefined, onUnpinMessage: canPinMessage ? handleUnpinMessage : undefined, timeRevealed: revealedTimeMsgId === message.clientMsgID, onToggleTimeReveal: handleToggleTimeReveal, onCopyMessageLink: handleCopyMessageLink }, message.clientMsgID))) }), moreNewLoading && (_jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) })), (Boolean(rawGroupId) && 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 })) : !isConfirmedGroup && shouldRestrictToLastSession ? (_jsxs("div", { className: "border-t bg-white py-4 flex flex-col items-center gap-2", "data-cp-name": "JoinGroupRequired", children: [_jsx("p", { className: "text-sm text-gray-500", children: t("join_group_required") }), _jsx(Button, { type: "primary", onClick: joinGroup, loading: isJoining, children: t("join_group") })] })) : isViewOnlyMember ? (
|
|
184
191
|
// Brief fallback while isJoined catches up after the kick event.
|
|
185
|
-
_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute
|
|
192
|
+
_jsx(MessageFooter, { currentSession: latestConversationSession, openCreateCannedModal: openCreateCannedModal })) : !canSendMessage ? (_jsx("div", { className: "border-t bg-[#f5f5f5] py-3 flex items-center justify-center", children: _jsx("p", { className: "text-sm text-gray-500", children: t(isRestrictedByGroupMute
|
|
193
|
+
? "send_message_disabled_group_mute"
|
|
194
|
+
: "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) })] }));
|
|
186
195
|
};
|
|
187
196
|
export default MessageList;
|
|
@@ -16,7 +16,8 @@ interface MessageItemProps {
|
|
|
16
16
|
timeRevealed?: boolean;
|
|
17
17
|
/** Called on bubble tap to toggle this message as the revealed one. */
|
|
18
18
|
onToggleTimeReveal?: (clientMsgID: string) => void;
|
|
19
|
+
onCopyMessageLink?: (clientMsgID: string) => void;
|
|
19
20
|
}
|
|
20
|
-
declare const MessageItem: ({ message, allMessages, isGroupChat, forceHideTip, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress, timeRevealed, onToggleTimeReveal, }: MessageItemProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
declare const MessageItem: ({ message, allMessages, isGroupChat, forceHideTip, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress, timeRevealed, onToggleTimeReveal, onCopyMessageLink, }: MessageItemProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
21
22
|
export default MessageItem;
|
|
22
23
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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;AAyCtC,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;IAC1B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,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;AAyCtC,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;IAC1B,2EAA2E;IAC3E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACnD;AAkCD,QAAA,MAAM,WAAW,GAAI,0PAgBlB,gBAAgB,mDA2WlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -37,14 +37,14 @@ const MessageAvatar = ({ avatarUrl, senderNickname, }) => {
|
|
|
37
37
|
var _a;
|
|
38
38
|
return (_jsx(Avatar, { size: MESSAGE_AVATAR_SIZE, src: avatarUrl, className: "shrink-0 self-end", "data-cp-name": "MessageAvatar", children: ((_a = senderNickname === null || senderNickname === void 0 ? void 0 : senderNickname.charAt) === null || _a === void 0 ? void 0 : _a.call(senderNickname, 0)) || "A" }));
|
|
39
39
|
};
|
|
40
|
-
const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip = false, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress = false, timeRevealed = false, onToggleTimeReveal, }) => {
|
|
40
|
+
const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip = false, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress = false, timeRevealed = false, onToggleTimeReveal, onCopyMessageLink, }) => {
|
|
41
41
|
var _a;
|
|
42
42
|
const { t } = useTranslation();
|
|
43
43
|
const { resendMessage } = useSendMessage();
|
|
44
44
|
const userID = useAuthStore((state) => state.userID);
|
|
45
45
|
const isCrm = useAuthStore((state) => state.isCrm);
|
|
46
46
|
const conversationData = useConversationStore((state) => state.conversationData);
|
|
47
|
-
const { isDeliveredForUi, isRevocable, isMine } = useMessageToolboxVisibility(message, userID);
|
|
47
|
+
const { isDeliveredForUi, isRevocable, isMine, isMessageLinkCopyable } = useMessageToolboxVisibility(message, userID);
|
|
48
48
|
const contextMenuItems = useMemo(() => {
|
|
49
49
|
if ((message === null || message === void 0 ? void 0 : message.contentType) === MessageType.RevokeMessage ||
|
|
50
50
|
isSystemLogMessageType(message === null || message === void 0 ? void 0 : message.contentType) ||
|
|
@@ -71,7 +71,9 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
|
|
|
71
71
|
key: isMessagePinned
|
|
72
72
|
? MessageToolboxActionKey.UNPIN
|
|
73
73
|
: MessageToolboxActionKey.PIN,
|
|
74
|
-
label: isMessagePinned
|
|
74
|
+
label: isMessagePinned
|
|
75
|
+
? t("context_menu_unpin")
|
|
76
|
+
: t("context_menu_pin"),
|
|
75
77
|
icon: isMessagePinned ? "thumbtack-off-o" : "thumbtack-o",
|
|
76
78
|
onClick: () => {
|
|
77
79
|
if (isMessagePinned) {
|
|
@@ -96,6 +98,16 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
|
|
|
96
98
|
},
|
|
97
99
|
});
|
|
98
100
|
}
|
|
101
|
+
if (isMessageLinkCopyable && onCopyMessageLink) {
|
|
102
|
+
actions.push({
|
|
103
|
+
key: MessageToolboxActionKey.COPY_MESSAGE_LINK,
|
|
104
|
+
label: t("context_menu_copy_message_link"),
|
|
105
|
+
icon: "link-o",
|
|
106
|
+
onClick: () => {
|
|
107
|
+
onCopyMessageLink === null || onCopyMessageLink === void 0 ? void 0 : onCopyMessageLink((message === null || message === void 0 ? void 0 : message.clientMsgID) || "");
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
99
111
|
if (isRevocable && onRevokeMessage) {
|
|
100
112
|
actions.push({
|
|
101
113
|
key: MessageToolboxActionKey.REVOKE,
|
|
@@ -116,6 +128,8 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
|
|
|
116
128
|
onPinMessage,
|
|
117
129
|
onUnpinMessage,
|
|
118
130
|
onContextMenuOpenChange,
|
|
131
|
+
onCopyMessageLink,
|
|
132
|
+
isMessageLinkCopyable,
|
|
119
133
|
]);
|
|
120
134
|
const bubbleWrapperRef = useRef(null);
|
|
121
135
|
useLongPress(() => onContextMenuOpenChange === null || onContextMenuOpenChange === void 0 ? void 0 : onContextMenuOpenChange(true), enableLongPress ? bubbleWrapperRef : undefined, { delay: 500 });
|
|
@@ -163,16 +177,14 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
|
|
|
163
177
|
!isOrderMessage(previousMessage) &&
|
|
164
178
|
previousMessage.sendID === (message === null || message === void 0 ? void 0 : message.sendID) &&
|
|
165
179
|
isSameCalendarDay(message.sendTime, previousMessage.sendTime) &&
|
|
166
|
-
Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "millisecond")) <=
|
|
167
|
-
GROUPING_GAP_MS;
|
|
180
|
+
Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "millisecond")) <= GROUPING_GAP_MS;
|
|
168
181
|
const nextSameUser = !isOrder &&
|
|
169
182
|
!nextBreaksGroup &&
|
|
170
183
|
!!nextMessage &&
|
|
171
184
|
!isOrderMessage(nextMessage) &&
|
|
172
185
|
nextMessage.sendID === (message === null || message === void 0 ? void 0 : message.sendID) &&
|
|
173
186
|
isSameCalendarDay(nextMessage.sendTime, message.sendTime) &&
|
|
174
|
-
Math.abs(dayjs(nextMessage.sendTime).diff(message.sendTime, "millisecond")) <=
|
|
175
|
-
GROUPING_GAP_MS;
|
|
187
|
+
Math.abs(dayjs(nextMessage.sendTime).diff(message.sendTime, "millisecond")) <= GROUPING_GAP_MS;
|
|
176
188
|
const showTimeBreak = !previousMessage ||
|
|
177
189
|
!isSameCalendarDay(message.sendTime, previousMessage.sendTime) ||
|
|
178
190
|
Math.abs(dayjs(message.sendTime).diff(previousMessage.sendTime, "millisecond")) > DIVIDER_GAP_MS;
|
|
@@ -5,6 +5,7 @@ export declare const MessageToolboxActionKey: {
|
|
|
5
5
|
readonly VIEW_ORIGINAL: "VIEW_ORIGINAL";
|
|
6
6
|
readonly COPY_MESSAGE: "COPY_MESSAGE";
|
|
7
7
|
readonly REVOKE: "REVOKE";
|
|
8
|
+
readonly COPY_MESSAGE_LINK: "COPY_MESSAGE_LINK";
|
|
8
9
|
};
|
|
9
10
|
export type MessageToolboxActionKey = (typeof MessageToolboxActionKey)[keyof typeof MessageToolboxActionKey];
|
|
10
11
|
export interface MessageToolboxAction {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB;;;;;;;;CAQ1B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,OAAO,uBAAuB,CAAC,CAAC;AAEzE,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,uBAAuB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
|
|
@@ -3,6 +3,7 @@ interface MessageToolboxVisibility {
|
|
|
3
3
|
isDeliveredForUi: boolean;
|
|
4
4
|
isMine: boolean;
|
|
5
5
|
isRevocable: boolean;
|
|
6
|
+
isMessageLinkCopyable: boolean;
|
|
6
7
|
}
|
|
7
8
|
export declare const useMessageToolboxVisibility: (message: MessageItemType, userID?: string) => MessageToolboxVisibility;
|
|
8
9
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMessageToolboxVisibility.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/useMessageToolboxVisibility.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,IAAI,eAAe,
|
|
1
|
+
{"version":3,"file":"useMessageToolboxVisibility.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/useMessageToolboxVisibility.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,IAAI,eAAe,EAG/B,MAAM,8BAA8B,CAAC;AAItC,UAAU,wBAAwB;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,qBAAqB,EAAE,OAAO,CAAC;CAChC;AAED,eAAO,MAAM,2BAA2B,GACtC,SAAS,eAAe,EACxB,SAAS,MAAM,KACd,wBAqBoB,CAAC"}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
|
-
import { MessageStatus, } from "@droppii-org/wasm-client-sdk";
|
|
3
|
+
import { MessageStatus, MessageType, } from "@droppii-org/wasm-client-sdk";
|
|
4
4
|
const REVOKE_WINDOW_DAYS = 1;
|
|
5
5
|
export const useMessageToolboxVisibility = (message, userID) => useMemo(() => {
|
|
6
6
|
const isMine = (message === null || message === void 0 ? void 0 : message.sendID) === userID;
|
|
7
|
-
const isOlderThanRevokeWindow = dayjs().diff(dayjs(message === null || message === void 0 ? void 0 : message.sendTime), "days", true) >=
|
|
7
|
+
const isOlderThanRevokeWindow = dayjs().diff(dayjs(message === null || message === void 0 ? void 0 : message.sendTime), "days", true) >=
|
|
8
|
+
REVOKE_WINDOW_DAYS;
|
|
8
9
|
const isDeliveredForUi = (message === null || message === void 0 ? void 0 : message.status) !== MessageStatus.Sending &&
|
|
9
10
|
(message === null || message === void 0 ? void 0 : message.status) !== MessageStatus.Failed;
|
|
11
|
+
const isRevocable = isMine && isDeliveredForUi && !isOlderThanRevokeWindow;
|
|
12
|
+
const isMessageLinkCopyable = isDeliveredForUi && (message === null || message === void 0 ? void 0 : message.contentType) !== MessageType.RevokeMessage;
|
|
10
13
|
return {
|
|
11
14
|
isDeliveredForUi,
|
|
12
15
|
isMine,
|
|
13
|
-
isRevocable
|
|
16
|
+
isRevocable,
|
|
17
|
+
isMessageLinkCopyable,
|
|
14
18
|
};
|
|
15
19
|
}, [userID, message]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GroupThreadInfo.d.ts","sourceRoot":"","sources":["../../../src/components/thread/GroupThreadInfo.tsx"],"names":[],"mappings":"AAsBA,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;AASD,QAAA,MAAM,eAAe,GAAI,qBAAqB,oBAAoB,mDA8mBjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -17,6 +17,7 @@ import { useUploadFile } from "../../hooks/common/useUploadFile";
|
|
|
17
17
|
import { ACCEPTED_IMAGE_TYPES, MAX_IMAGE_SIZE_MB } from "../../utils/fileValidation";
|
|
18
18
|
import AdminPermissionModal from "../../components/group/AdminPermissionModal";
|
|
19
19
|
import MemberPermissionPanel from "../../components/group/MemberPermissionPanel";
|
|
20
|
+
import InviteLinkPanel from "../../components/group/InviteLinkPanel";
|
|
20
21
|
import GroupCompositeAvatar from "../../components/group/GroupCompositeAvatar";
|
|
21
22
|
const GroupThreadInfo = ({ onClose, config }) => {
|
|
22
23
|
var _a, _b, _c, _d;
|
|
@@ -39,6 +40,10 @@ const GroupThreadInfo = ({ onClose, config }) => {
|
|
|
39
40
|
// Non-member viewing an existing public group in view-only mode — no
|
|
40
41
|
// longer a member, so "Rời khỏi nhóm" (leave) doesn't apply.
|
|
41
42
|
const isViewOnlyMember = useConversationStore((state) => state.isViewOnlyMember);
|
|
43
|
+
// Feature ships to staging first (DROPPII-30551). Same env var apps/web already
|
|
44
|
+
// reads in src/app/layout.tsx:51 — this repo has no feature-flag service and
|
|
45
|
+
// this ticket is not the place to introduce one.
|
|
46
|
+
const isInviteLinkFeatureEnabled = process.env.NEXT_PUBLIC_ENV === "stg";
|
|
42
47
|
const [editingField, setEditingField] = useState(null);
|
|
43
48
|
const [nameValue, setNameValue] = useState((_a = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _a !== void 0 ? _a : "");
|
|
44
49
|
const [descriptionValue, setDescriptionValue] = useState((_b = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _b !== void 0 ? _b : "");
|
|
@@ -53,6 +58,17 @@ const GroupThreadInfo = ({ onClose, config }) => {
|
|
|
53
58
|
(_a = descriptionInputRef.current) === null || _a === void 0 ? void 0 : _a.focus({ cursor: "end" });
|
|
54
59
|
}
|
|
55
60
|
}, [editingField]);
|
|
61
|
+
// Losing membership (kicked from a public group, staying view-only) can
|
|
62
|
+
// happen while this panel is already open — same transition
|
|
63
|
+
// useLeaveGroupAction.ts guards against for its own modals. The row is
|
|
64
|
+
// already hidden via !isViewOnlyMember, but activeRow isn't reset by that,
|
|
65
|
+
// so without this the invite-link panel would keep rendering/querying for
|
|
66
|
+
// a group the user can no longer act in.
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (isViewOnlyMember && activeRow === "invite-link") {
|
|
69
|
+
setActiveRow(null);
|
|
70
|
+
}
|
|
71
|
+
}, [isViewOnlyMember, activeRow]);
|
|
56
72
|
// changeGroupMute() persists via GroupItem.status (GroupStatus.Muted),
|
|
57
73
|
// refreshed into currentGroupInfo on OnGroupInfoChanged — same pattern as
|
|
58
74
|
// notifyEnabled below, not a locally-tracked flag.
|
|
@@ -229,6 +245,6 @@ const GroupThreadInfo = ({ onClose, config }) => {
|
|
|
229
245
|
return (_jsxs("div", { className: "h-full w-[330px] bg-white shadow-md flex flex-col relative overflow-hidden", "data-cp-name": "GroupThreadInfo", 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("thread_info_title") }), _jsx("button", { type: "button", className: "flex items-center justify-center w-8 h-8 rounded-full text-gray-500 hover:bg-gray-100", onClick: onClose, children: _jsx(Icon, { icon: "close-o", size: 20 }) })] }), _jsxs("div", { className: "flex-1 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-4", children: [_jsxs("div", { className: "relative shrink-0", children: [_jsx(GroupCompositeAvatar, { groupID: groupID, faceURL: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.faceURL, ownerUserID: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.ownerUserID, groupName: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName, size: 56 }), isAvatarUploading && (_jsx("span", { className: "absolute inset-0 flex items-center justify-center rounded-full bg-black/30", children: _jsx("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent" }) })), permissions.updateGroupAvatar && (_jsxs(_Fragment, { children: [_jsx("input", { ref: avatarInputRef, type: "file", accept: ACCEPTED_IMAGE_TYPES.join(", "), className: "hidden", onChange: handleAvatarSelect }), _jsx("button", { type: "button", disabled: isAvatarUploading, onClick: handleAvatarClick, className: "absolute -bottom-1 -right-1 flex items-center justify-center w-5 h-5 rounded-full bg-black text-white", children: _jsx(Icon, { icon: "camera-o", size: 12 }) })] }))] }), _jsx("div", { className: "flex-1 min-w-0", children: editingField !== "name" && (_jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [_jsx("p", { className: "text-base font-medium truncate", children: currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName }), permissions.updateGroupName && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditName }))] })) })] }), editingField === "name" && (_jsx("div", { className: "px-4 pb-4", children: _jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsx(Input, { autoFocus: true, value: nameValue, onChange: (event) => setNameValue(event.target.value), maxLength: 255, showCount: true, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditName, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
|
|
230
246
|
!nameValue.trim() ||
|
|
231
247
|
nameValue.trim() === ((_c = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.groupName) !== null && _c !== void 0 ? _c : ""), onClick: saveName, children: t("save") })] })] }) })), _jsx("div", { className: "px-4 pb-4", children: editingField === "description" ? (_jsxs("div", { className: "flex flex-col gap-3 bg-[#f5f5f5] rounded-lg p-3", children: [_jsxs("span", { className: "flex items-center gap-2 text-sm font-medium text-gray-900", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "text-gray-500" }), t("description")] }), _jsx(Input.TextArea, { ref: descriptionInputRef, value: descriptionValue, onChange: (event) => setDescriptionValue(event.target.value), placeholder: t("enter_group_description"), autoSize: { minRows: 1, maxRows: 4 }, maxLength: 500, disabled: isSavingGroupInfo, className: "bg-white" }), _jsxs("span", { className: "self-end text-xs text-gray-400", children: [descriptionValue.length, " / 500"] }), _jsxs("div", { className: "flex items-center justify-end gap-2", children: [_jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg border border-[var(--dc-color-primary-hover)] text-[var(--dc-color-primary-hover)] text-sm font-medium disabled:border-gray-200 disabled:text-gray-300", disabled: isSavingGroupInfo, onClick: cancelEditDescription, children: t("cancel") }), _jsx("button", { type: "button", className: "px-4 py-1.5 rounded-lg bg-[var(--dc-color-primary-hover)] text-white text-sm font-medium disabled:bg-gray-100 disabled:text-gray-300", disabled: isSavingGroupInfo ||
|
|
232
|
-
descriptionValue.trim() === ((_d = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _d !== void 0 ? _d : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }))] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), _jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center
|
|
248
|
+
descriptionValue.trim() === ((_d = currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) !== null && _d !== void 0 ? _d : ""), onClick: saveDescription, children: t("save") })] })] })) : (_jsxs("div", { className: "flex items-start justify-between gap-2 bg-[#f5f5f5] rounded-lg px-3 py-2.5", children: [_jsxs("div", { className: "flex items-start gap-2 flex-1 min-w-0", children: [_jsx(Icon, { icon: "info-circle-o", size: 18, className: "shrink-0 mt-0.5 text-gray-900" }), _jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: t("description") }), _jsx("p", { className: "text-sm text-gray-400 truncate", children: (currentGroupInfo === null || currentGroupInfo === void 0 ? void 0 : currentGroupInfo.introduction) || t("no_description") })] })] }), permissions.updateGroupDescription && (_jsx(Icon, { icon: "edit-o", size: 16, className: "shrink-0 cursor-pointer text-gray-500", onClick: startEditDescription }))] })) }), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 rounded-lg overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-o", size: 18 }), t("notification")] }), _jsx(Switch, { checked: notifyEnabled, onChange: handleToggleNotification, loading: isTogglingNotification })] }), (config === null || config === void 0 ? void 0 : config.showAllowSendMessage) && isOwner && (_jsxs("div", { className: "flex items-center justify-between px-3 py-3", children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-check-o", size: 18 }), t("allow_send_message")] }), _jsx(Switch, { checked: allowSendMessage, onChange: handleToggleSendMessage, loading: isTogglingSendMessage })] })), isAdminOrOwner && (config === null || config === void 0 ? void 0 : config.showMutedChatList) && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("muted-chat-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "bell-off-o", size: 18 }), t("muted_chat_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })), isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("block-list"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("block_list")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }))] }), permissions.configGroupPermission && (_jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 rounded-lg overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("member-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-o", size: 18 }), t("member_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }), _jsxs("button", { type: "button", className: "flex items-center justify-between px-3 py-3 text-left", onClick: () => setActiveRow("admin-permission"), children: [_jsxs("span", { className: "flex items-center gap-3 text-sm", children: [_jsx(Icon, { icon: "shield-done-o", size: 18 }), t("admin_permission_settings")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] })] })), isInviteLinkFeatureEnabled && !isViewOnlyMember && (_jsx("div", { className: "mx-4 mt-3 rounded-lg overflow-hidden bg-[#f5f5f5]", children: _jsxs("button", { type: "button", className: "w-full flex items-center justify-between px-3 py-3 text-left text-sm", onClick: () => setActiveRow("invite-link"), children: [_jsxs("span", { className: "flex items-center gap-3", children: [_jsx(Icon, { icon: "link-alt-o", size: 18 }), t("invite_via_link")] }), _jsx(Icon, { icon: "angle-right-o", size: 16, className: "text-gray-400" })] }) })), _jsxs("div", { className: "flex flex-col gap-1 bg-[#f5f5f5] mx-4 mt-3 mb-4 rounded-lg overflow-hidden", children: [isAdminOrOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("delete"), children: [_jsx(Icon, { icon: "trash-o", size: 18 }), t("delete_conversation")] })), !isViewOnlyMember && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("leave"), children: [_jsx(Icon, { icon: "logout-o", size: 18 }), t("leave_group")] })), isOwner && (_jsxs("button", { type: "button", className: "flex items-center gap-3 px-3 py-3 text-left text-sm text-red-500", onClick: () => setConfirmAction("disband"), children: [_jsx(Icon, { icon: "user-del-o", size: 18 }), t("disband_group")] }))] })] }), _jsx(ConfirmModal, { open: confirmAction === "leave", onOk: handleConfirmLeave, onCancel: () => setConfirmAction(null), 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: confirmAction === "disband", onOk: handleConfirmDisband, onCancel: () => setConfirmAction(null), title: t("disband_group_confirm_title"), okText: t("disband_group"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDismissing, children: _jsx("p", { children: t("disband_group_confirm_message") }) }), _jsx(ConfirmModal, { open: confirmAction === "delete", onOk: handleConfirmDelete, onCancel: () => setConfirmAction(null), title: t("delete_conversation_confirm_title"), okText: t("delete_conversation"), cancelText: t("cancel"), okType: "danger", confirmLoading: isDeleting, children: _jsx("p", { children: t("delete_conversation_confirm_message") }) }), _jsx(MemberPermissionPanel, { open: activeRow === "member-permission", groupID: groupID, onClose: () => setActiveRow(null) }), _jsx(AdminPermissionModal, { open: activeRow === "admin-permission", groupID: groupID, onClose: () => setActiveRow(null), inline: true }), _jsx(InviteLinkPanel, { open: activeRow === "invite-link", groupID: groupID, conversationID: conversationID, onClose: () => setActiveRow(null) }), (activeRow === "muted-chat-list" || activeRow === "block-list") && (_jsx(Modal, { centered: true, open: true, footer: null, onCancel: () => setActiveRow(null), title: t(activeRow === "muted-chat-list" ? "muted_chat_list" : "block_list"), getContainer: false, forceRender: true, children: _jsx("p", { className: "text-sm text-gray-500", children: t("feature_coming_soon") }) }))] }));
|
|
233
249
|
};
|
|
234
250
|
export default GroupThreadInfo;
|