@droppii-org/chat-sdk 0.1.58 → 0.1.60

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 (92) hide show
  1. package/dist/assets/droppiiFontSelection.json +13195 -3192
  2. package/dist/components/conversation-detail/ConversationDetailHeader.js +3 -2
  3. package/dist/components/conversation-detail/ConversationDetailInput.js +1 -1
  4. package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
  5. package/dist/components/conversation-detail/ConversationMessageList.js +33 -2
  6. package/dist/components/conversation-detail/ConversationMoreOptionItem.js +1 -1
  7. package/dist/components/conversation-detail/PinLimitModal.d.ts +7 -0
  8. package/dist/components/conversation-detail/PinLimitModal.d.ts.map +1 -0
  9. package/dist/components/conversation-detail/PinLimitModal.js +11 -0
  10. package/dist/components/conversation-detail/PinnedMessageBar.d.ts +3 -0
  11. package/dist/components/conversation-detail/PinnedMessageBar.d.ts.map +1 -0
  12. package/dist/components/conversation-detail/PinnedMessageBar.js +95 -0
  13. package/dist/components/conversation-detail/bot/BotInactiveNotice.d.ts +3 -0
  14. package/dist/components/conversation-detail/bot/BotInactiveNotice.d.ts.map +1 -0
  15. package/dist/components/conversation-detail/bot/BotInactiveNotice.js +10 -0
  16. package/dist/components/conversation-detail/bot/index.d.ts +1 -0
  17. package/dist/components/conversation-detail/bot/index.d.ts.map +1 -1
  18. package/dist/components/conversation-detail/bot/index.js +1 -0
  19. package/dist/components/conversation-detail/index.d.ts +2 -0
  20. package/dist/components/conversation-detail/index.d.ts.map +1 -1
  21. package/dist/components/conversation-detail/index.js +2 -0
  22. package/dist/components/conversation-inbox/ConversationInboxItem.js +3 -2
  23. package/dist/components/conversation-inbox/ConversationInboxTitle.js +1 -1
  24. package/dist/components/message/item/QuoteMessage.js +1 -1
  25. package/dist/components/message/item/TextMessage.d.ts.map +1 -1
  26. package/dist/components/message/item/TextMessage.js +2 -2
  27. package/dist/components/message/item/index.d.ts +3 -1
  28. package/dist/components/message/item/index.d.ts.map +1 -1
  29. package/dist/components/message/item/index.js +39 -12
  30. package/dist/components/message/reply/ReplyPreviewMedia.d.ts.map +1 -1
  31. package/dist/components/message/reply/ReplyPreviewMedia.js +3 -14
  32. package/dist/components/message/reply/ReplyQuoteContent.d.ts +2 -2
  33. package/dist/components/message/reply/ReplyQuoteContent.d.ts.map +1 -1
  34. package/dist/components/message/reply/ReplyQuoteContent.js +1 -1
  35. package/dist/components/message/toolbox/index.d.ts +1 -0
  36. package/dist/components/message/toolbox/index.d.ts.map +1 -1
  37. package/dist/components/message/toolbox/index.js +1 -0
  38. package/dist/components/message/toolbox/isPinnableMessageType.d.ts +3 -0
  39. package/dist/components/message/toolbox/isPinnableMessageType.d.ts.map +1 -0
  40. package/dist/components/message/toolbox/isPinnableMessageType.js +11 -0
  41. package/dist/components/message/toolbox/types.d.ts +1 -0
  42. package/dist/components/message/toolbox/types.d.ts.map +1 -1
  43. package/dist/components/message/toolbox/types.js +1 -0
  44. package/dist/constants/index.d.ts +1 -0
  45. package/dist/constants/index.d.ts.map +1 -1
  46. package/dist/constants/index.js +1 -0
  47. package/dist/hooks/conversation/useBotConversationDetail.js +2 -2
  48. package/dist/hooks/conversation/useBotDetail.d.ts +3 -0
  49. package/dist/hooks/conversation/useBotDetail.d.ts.map +1 -0
  50. package/dist/hooks/conversation/useBotDetail.js +18 -0
  51. package/dist/hooks/conversation/useBotFlow.d.ts +1 -0
  52. package/dist/hooks/conversation/useBotFlow.d.ts.map +1 -1
  53. package/dist/hooks/conversation/useBotFlow.js +13 -3
  54. package/dist/hooks/conversation/useConversationAvatarUrl.d.ts +3 -0
  55. package/dist/hooks/conversation/useConversationAvatarUrl.d.ts.map +1 -0
  56. package/dist/hooks/conversation/useConversationAvatarUrl.js +11 -0
  57. package/dist/hooks/conversation/useCreateConversation.js +2 -2
  58. package/dist/hooks/message/usePinMessage.d.ts +7 -0
  59. package/dist/hooks/message/usePinMessage.d.ts.map +1 -0
  60. package/dist/hooks/message/usePinMessage.js +42 -0
  61. package/dist/hooks/message/usePinnedMessages.d.ts +6 -0
  62. package/dist/hooks/message/usePinnedMessages.d.ts.map +1 -0
  63. package/dist/hooks/message/usePinnedMessages.js +33 -0
  64. package/dist/locales/vi/common.json +13 -0
  65. package/dist/locales/vi/conversation-inbox.json +2 -1
  66. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  67. package/dist/screens/conversation-detail/index.js +6 -4
  68. package/dist/services/query.d.ts +3 -2
  69. package/dist/services/query.d.ts.map +1 -1
  70. package/dist/services/query.js +3 -2
  71. package/dist/services/routes.d.ts +3 -2
  72. package/dist/services/routes.d.ts.map +1 -1
  73. package/dist/services/routes.js +3 -2
  74. package/dist/store/conversation.d.ts.map +1 -1
  75. package/dist/store/conversation.js +21 -1
  76. package/dist/store/pinnedMessage.d.ts +4 -0
  77. package/dist/store/pinnedMessage.d.ts.map +1 -0
  78. package/dist/store/pinnedMessage.js +17 -0
  79. package/dist/store/type.d.ts +4 -0
  80. package/dist/store/type.d.ts.map +1 -1
  81. package/dist/styles/global.css +1 -1
  82. package/dist/tsconfig.tsbuildinfo +1 -1
  83. package/dist/types/conversation.d.ts +20 -0
  84. package/dist/types/conversation.d.ts.map +1 -1
  85. package/dist/types/conversation.js +5 -0
  86. package/dist/utils/common.d.ts +2 -0
  87. package/dist/utils/common.d.ts.map +1 -1
  88. package/dist/utils/common.js +11 -0
  89. package/dist/utils/conversation.d.ts +1 -0
  90. package/dist/utils/conversation.d.ts.map +1 -1
  91. package/dist/utils/conversation.js +11 -1
  92. package/package.json +127 -126
@@ -4,12 +4,13 @@ import { useTranslation } from "react-i18next";
4
4
  import { Icon } from "../../components/icon";
5
5
  import ConversationPeerAvatar from "../../components/conversation-inbox/ConversationPeerAvatar";
6
6
  import ConversationInboxTitle from "../../components/conversation-inbox/ConversationInboxTitle";
7
- import { resolveConversationAvatarUrl, resolveConversationDisplayName, resolvePeerBadgeLabel, } from "../../utils/conversation";
7
+ import { resolveConversationDisplayName, resolvePeerBadgeLabel, } from "../../utils/conversation";
8
+ import { useConversationAvatarUrl } from "../../hooks/conversation/useConversationAvatarUrl";
8
9
  const HeaderIconButton = ({ icon, onClick, testId, }) => (_jsx("button", { type: "button", onClick: onClick, "data-testid": testId, className: "flex shrink-0 cursor-pointer items-center justify-center p-2 text-primary-400", children: _jsx(Icon, { icon: icon, size: 24 }) }));
9
10
  const ConversationDetailHeader = ({ conversation, onBack, }) => {
10
11
  const { t } = useTranslation("conversation-inbox");
11
12
  const displayName = resolveConversationDisplayName(conversation, t);
12
- const avatarUrl = resolveConversationAvatarUrl(conversation);
13
+ const avatarUrl = useConversationAvatarUrl(conversation);
13
14
  const peerType = conversation.peerType;
14
15
  const badgeLabel = resolvePeerBadgeLabel(peerType, t);
15
16
  const subtitle = t("thread_detail.profile_hint");
@@ -252,7 +252,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, })
252
252
  event.preventDefault();
253
253
  void handleSend();
254
254
  };
255
- return (_jsxs("div", { ref: containerRef, "data-testid": "conversation-detail-input", className: "flex w-full shrink-0 flex-col bg-chat-bubble-neutral/[0.05]", children: [_jsx("input", { ref: mediaInputRef, type: "file", accept: "image/jpeg,image/png,image/jpg,video/*", multiple: true, className: "hidden", onChange: handleMediaInputChange }), _jsx("input", { ref: documentInputRef, type: "file", accept: ".pdf,.doc,.docx", multiple: true, className: "hidden", onChange: handleDocumentInputChange }), _jsx(ReplyPreviewBar, {}), listUploadFiles.length > 0 && (_jsx(FilePreviewList, { files: listUploadFiles, onRemove: (file) => handleRemoveFile(file.uid), onVideoReady: () => setIsVideoLoading(false) })), showLinkPreview && previewUrl && (_jsx(LinkInputPreview, { url: previewUrl, onRemove: () => setPreviewDismissed(true) })), _jsxs("div", { className: "flex items-end gap-3 px-3 py-2", children: [_jsx("div", { className: "flex shrink-0 items-center", children: _jsx("button", { type: "button", onClick: handleToggleMoreOption, "aria-label": t("thread_detail.more_option.toggle"), "aria-expanded": isMoreOptionOpen, "data-testid": "conversation-detail-more-option-toggle", className: "flex py-3 items-center justify-center rounded-full", children: isMoreOptionOpen ? (_jsx(Icon, { icon: "close-circle-o", size: 24, className: "text-primary-400" })) : (_jsx(Icon, { icon: "plus-circle-o", size: 24, className: "text-gray-900" })) }) }), _jsx("div", { className: clsx("flex min-w-0 flex-1 overflow-hidden rounded-xl border bg-white transition-shadow", isFocused
255
+ return (_jsxs("div", { ref: containerRef, "data-testid": "conversation-detail-input", className: "flex w-full shrink-0 flex-col bg-chat-bubble-neutral/[0.05]", children: [_jsx("input", { ref: mediaInputRef, type: "file", accept: "image/jpeg,image/png,image/jpg,video/*", multiple: true, className: "hidden", onChange: handleMediaInputChange }), _jsx("input", { ref: documentInputRef, type: "file", accept: ".pdf,.doc,.docx", multiple: true, className: "hidden", onChange: handleDocumentInputChange }), _jsx(ReplyPreviewBar, {}), listUploadFiles.length > 0 && (_jsx(FilePreviewList, { files: listUploadFiles, onRemove: (file) => handleRemoveFile(file.uid), onVideoReady: () => setIsVideoLoading(false) })), showLinkPreview && previewUrl && (_jsx(LinkInputPreview, { url: previewUrl, onRemove: () => setPreviewDismissed(true) })), _jsxs("div", { className: "flex items-end gap-3 px-3 py-2", children: [_jsx("div", { className: "flex shrink-0 items-center", children: _jsx("button", { type: "button", onClick: handleToggleMoreOption, "aria-label": t("thread_detail.more_option.toggle"), "aria-expanded": isMoreOptionOpen, "data-testid": "conversation-detail-more-option-toggle", className: "flex py-3 items-center justify-center rounded-full", children: isMoreOptionOpen ? (_jsx(Icon, { icon: "close-circle-b", size: 24, className: "text-primary-400" })) : (_jsx(Icon, { icon: "plus-circle-o", size: 24, className: "text-gray-900" })) }) }), _jsx("div", { className: clsx("flex min-w-0 flex-1 overflow-hidden rounded-xl border bg-white transition-shadow", isFocused
256
256
  ? "border-primary-200 shadow-primary-focus"
257
257
  : "border-gray-50"), style: { minHeight: CONTAINER_MIN_HEIGHT }, children: _jsx("div", { className: "flex min-w-0 flex-[1_0_0] items-center gap-2 self-stretch px-3 py-1", children: _jsx("textarea", { ref: textareaRef, value: value, rows: 1, onChange: (event) => handleChange(event.target.value), onFocus: handleFocus, onBlur: handleBlur, onKeyDown: handleKeyDown, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : t("thread_detail.input_placeholder"), "data-testid": "conversation-detail-message-input", className: "conversation-message-input-scrollbar min-h-0 min-w-0 flex-1 resize-none appearance-none border-0 bg-transparent p-0 text-[16px] leading-[1.6] tracking-[0.16px] text-black outline-none ring-0 placeholder:text-gray-400 focus:border-0 focus:outline-none focus:ring-0" }) }) }), _jsx("div", { className: "flex shrink-0 items-center pb-1", children: _jsx("button", { type: "button", onClick: () => void handleSend(), disabled: !canSend, "data-testid": "conversation-detail-send", className: clsx("flex h-10 w-10 items-center justify-center rounded-full", canSend ? "bg-chat-send" : "bg-chat-send opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(ConversationMoreOptionPanel, { disabled: !isConversationReady, onTriggerMediaInput: () => { var _a; return (_a = mediaInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, onTriggerDocumentInput: () => { var _a; return (_a = documentInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); } }))] }));
258
258
  };
@@ -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;AAczE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,QAAA,MAAM,uBAAuB,GAAI,gBAAgB,4BAA4B,4CAsH5E,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"ConversationMessageList.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationMessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAkBzE,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,QAAA,MAAM,uBAAuB,GAAI,gBAAgB,4BAA4B,4CAiK5E,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
@@ -1,20 +1,24 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useState } from "react";
4
- import { Empty, Spin } from "antd";
4
+ import { Empty, Spin, message as toastMessage } from "antd";
5
5
  import { useEventListener } from "ahooks";
6
6
  import { useTranslation } from "react-i18next";
7
7
  import { useChatContext } from "../../context/ChatContext";
8
8
  import MessageInfiniteScroll from "../../components/message/MessageInfiniteScroll";
9
9
  import { useMessage } from "../../hooks/message/useMessage";
10
10
  import { useMessageListScroll } from "../../hooks/message/useMessageListScroll";
11
+ import { usePinMessage } from "../../hooks/message/usePinMessage";
11
12
  import useConversationStore from "../../store/conversation";
12
13
  import useAuthStore from "../../store/auth";
13
14
  import { isGroupChat as resolveIsGroupChat } from "../../utils/conversation";
14
15
  import emitter from "../../utils/events";
15
16
  import MessageItem from "../../components/message/item";
17
+ import PinLimitModal from "./PinLimitModal";
18
+ import { MAX_PINNED_MESSAGES_PER_CONVERSATION } from "../../constants";
16
19
  const ConversationMessageList = ({ bottomSlot }) => {
17
20
  const { t } = useTranslation("conversation-inbox");
21
+ const { t: tCommon } = useTranslation();
18
22
  const { user } = useChatContext();
19
23
  const authUserID = useAuthStore((state) => state.userID);
20
24
  const conversation = useConversationStore((state) => state.conversationData);
@@ -30,6 +34,33 @@ const ConversationMessageList = ({ bottomSlot }) => {
30
34
  const isMessageLoading = !conversationId || loadState.initLoading;
31
35
  const [openToolboxMessageId, setOpenToolboxMessageId] = useState(null);
32
36
  const closeToolbox = useCallback(() => setOpenToolboxMessageId(null), []);
37
+ const { pinMessage, unpinMessage } = usePinMessage();
38
+ const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
39
+ const [pinLimitModalOpen, setPinLimitModalOpen] = useState(false);
40
+ const handlePinMessage = useCallback(async (message) => {
41
+ if (pinnedTotalCount >= MAX_PINNED_MESSAGES_PER_CONVERSATION) {
42
+ setPinLimitModalOpen(true);
43
+ return;
44
+ }
45
+ try {
46
+ await pinMessage(conversationId, message.clientMsgID);
47
+ toastMessage.success(tCommon("pin_message_success"));
48
+ }
49
+ catch (error) {
50
+ console.error("pinMessage failed", error);
51
+ toastMessage.error(tCommon("pin_message_failed"));
52
+ }
53
+ }, [conversationId, pinMessage, pinnedTotalCount, tCommon]);
54
+ const handleUnpinMessage = useCallback(async (message) => {
55
+ try {
56
+ await unpinMessage(conversationId, message.clientMsgID);
57
+ toastMessage.success(tCommon("unpin_message_success"));
58
+ }
59
+ catch (error) {
60
+ console.error("unpinMessage failed", error);
61
+ toastMessage.error(tCommon("unpin_message_failed"));
62
+ }
63
+ }, [conversationId, unpinMessage, tCommon]);
33
64
  const onPressQuoteMessage = useCallback((clientMsgID) => {
34
65
  const hasMessage = loadState.messageList.some((msg) => msg.clientMsgID === clientMsgID);
35
66
  if (hasMessage) {
@@ -51,6 +82,6 @@ const ConversationMessageList = ({ bottomSlot }) => {
51
82
  target: scrollRef,
52
83
  enable: !!openToolboxMessageId,
53
84
  });
54
- return (_jsx("div", { "data-testid": "conversation-message-list", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsxs(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableConversationMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: [bottomSlot, loadState.messageList.map((message, index, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, forceHideTip: index === 0 && !!bottomSlot, contextMenuOpen: openToolboxMessageId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenToolboxMessageId(open ? message.clientMsgID : null), onQuoteMessage: setQuotedMessage, onRevokeMessage: closeToolbox, onPressQuoteMessage: onPressQuoteMessage, enableLongPress: true }, message.clientMsgID)))] })) }));
85
+ return (_jsxs("div", { "data-testid": "conversation-message-list", className: "relative flex min-h-0 min-w-0 flex-1 flex-col overflow-x-hidden bg-white", children: [isMessageLoading ? (_jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Spin, {}) })) : (_jsxs(MessageInfiniteScroll, { scrollRef: scrollRef, scrollableTargetId: "scrollableConversationMessagesDiv", loadState: loadState, moreOldLoading: moreOldLoading, onLoadMoreOld: loadMoreOldMessage, onScroll: handleInfiniteScroll, containerClassName: "h-full", empty: _jsx("div", { className: "flex flex-1 items-center justify-center py-12", children: _jsx(Empty, { description: t("empty.no_messages") }) }), children: [bottomSlot, loadState.messageList.map((message, index, array) => (_jsx(MessageItem, { message: message, allMessages: array, isGroupChat: isGroupChat, forceHideTip: index === 0 && !!bottomSlot, contextMenuOpen: openToolboxMessageId === message.clientMsgID, onContextMenuOpenChange: (open) => setOpenToolboxMessageId(open ? message.clientMsgID : null), onQuoteMessage: setQuotedMessage, onRevokeMessage: closeToolbox, onPressQuoteMessage: onPressQuoteMessage, onPinMessage: handlePinMessage, onUnpinMessage: handleUnpinMessage, enableLongPress: true }, message.clientMsgID)))] })), _jsx(PinLimitModal, { open: pinLimitModalOpen, onClose: () => setPinLimitModalOpen(false) })] }));
55
86
  };
56
87
  export default ConversationMessageList;
@@ -12,7 +12,7 @@ const ConversationMoreOptionItem = ({ option, onSelect }) => {
12
12
  return;
13
13
  onSelect(option);
14
14
  };
15
- return (_jsxs("button", { type: "button", onClick: handleClick, disabled: !option.enabled, "aria-label": label, "data-testid": `biz-more-option-${option.labelKey}`, className: clsx("flex flex-col items-center gap-1 rounded-xl px-1 transition-colors", option.enabled
15
+ return (_jsxs("button", { type: "button", onClick: handleClick, disabled: !option.enabled, "aria-label": label, "data-testid": `more-option-${option.labelKey}`, className: clsx("flex flex-col items-center gap-1 rounded-xl px-1 transition-colors", option.enabled
16
16
  ? "active:bg-chat-bubble-neutral/[0.08]"
17
17
  : "cursor-not-allowed opacity-40"), children: [_jsx("span", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-gray-200/10", children: _jsx(Icon, { icon: option.icon, size: ICON_SIZE, className: "text-gray-900" }) }), _jsx("span", { className: "line-clamp-2 text-center text-[12px] leading-[1.4] text-gray-900", children: label })] }));
18
18
  };
@@ -0,0 +1,7 @@
1
+ interface PinLimitModalProps {
2
+ open: boolean;
3
+ onClose: () => void;
4
+ }
5
+ declare const PinLimitModal: ({ open, onClose }: PinLimitModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default PinLimitModal;
7
+ //# sourceMappingURL=PinLimitModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PinLimitModal.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/PinLimitModal.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,aAAa,GAAI,mBAAmB,kBAAkB,4CAoC3D,CAAC;AAEF,eAAe,aAAa,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Modal, Button } from "antd";
3
+ import { useTranslation } from "react-i18next";
4
+ import { MAX_PINNED_MESSAGES_PER_CONVERSATION } from "../../constants";
5
+ const PinLimitModal = ({ open, onClose }) => {
6
+ const { t } = useTranslation();
7
+ return (_jsx(Modal, { open: open, onCancel: onClose, footer: null, centered: true, width: 400, children: _jsxs("div", { className: "flex flex-col gap-2 pt-2", role: "alertdialog", "aria-label": t("pin_limit_modal_title"), children: [_jsx("p", { className: "text-lg font-semibold text-gray-900", children: t("pin_limit_modal_title") }), _jsx("p", { className: "text-base text-gray-600", children: t("pin_limit_modal_description", {
8
+ max: MAX_PINNED_MESSAGES_PER_CONVERSATION,
9
+ }) }), _jsx(Button, { type: "primary", size: "large", block: true, className: "mt-2", onClick: onClose, children: t("pin_limit_modal_confirm") })] }) }));
10
+ };
11
+ export default PinLimitModal;
@@ -0,0 +1,3 @@
1
+ declare const PinnedMessageBar: () => import("react/jsx-runtime").JSX.Element | null;
2
+ export default PinnedMessageBar;
3
+ //# sourceMappingURL=PinnedMessageBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PinnedMessageBar.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/PinnedMessageBar.tsx"],"names":[],"mappings":"AA8GA,QAAA,MAAM,gBAAgB,sDAuErB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import clsx from "clsx";
4
+ import { useTranslation } from "react-i18next";
5
+ import { MessageType } from "@openim/wasm-client-sdk";
6
+ import { Icon } from "../../components/icon";
7
+ import { documentIcon } from "../../assets/svg";
8
+ import useAuthStore from "../../store/auth";
9
+ import useConversationStore from "../../store/conversation";
10
+ import { usePinnedMessages } from "../../hooks/message/usePinnedMessages";
11
+ import { parseUrlMetadata } from "../../utils/common";
12
+ const PINNED_SUBTITLE_KEY_BY_TYPE = {
13
+ [MessageType.UrlTextMessage]: "pinned_from_link",
14
+ [MessageType.PictureMessage]: "pinned_from_image",
15
+ [MessageType.MergeMessage]: "pinned_from_image",
16
+ [MessageType.VideoMessage]: "pinned_from_video",
17
+ [MessageType.FileMessage]: "pinned_from_file",
18
+ };
19
+ const TITLE_LESS_MESSAGE_TYPES = [
20
+ MessageType.PictureMessage,
21
+ MessageType.MergeMessage,
22
+ MessageType.VideoMessage,
23
+ ];
24
+ const getPinnedMessageTitle = (message) => {
25
+ var _a, _b, _c, _d, _e;
26
+ switch (message.contentType) {
27
+ case MessageType.UrlTextMessage: {
28
+ const urlMetadata = parseUrlMetadata(message.ex);
29
+ return ((urlMetadata === null || urlMetadata === void 0 ? void 0 : urlMetadata.title) ||
30
+ ((_a = message.urlTextElem) === null || _a === void 0 ? void 0 : _a.content) ||
31
+ ((_b = message.textElem) === null || _b === void 0 ? void 0 : _b.content) ||
32
+ "");
33
+ }
34
+ case MessageType.FileMessage:
35
+ return ((_c = message.fileElem) === null || _c === void 0 ? void 0 : _c.fileName) || "";
36
+ default:
37
+ return ((_d = message.textElem) === null || _d === void 0 ? void 0 : _d.content) || ((_e = message.quoteElem) === null || _e === void 0 ? void 0 : _e.text) || "";
38
+ }
39
+ };
40
+ const PinnedMessageThumbnail = ({ message }) => {
41
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
42
+ if (message.contentType === MessageType.FileMessage) {
43
+ return (_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden [&>svg]:h-6 [&>svg]:w-6", children: documentIcon }));
44
+ }
45
+ if (message.contentType === MessageType.PictureMessage ||
46
+ message.contentType === MessageType.MergeMessage) {
47
+ const firstSubMessage = (_b = (_a = message.mergeElem) === null || _a === void 0 ? void 0 : _a.multiMessage) === null || _b === void 0 ? void 0 : _b[0];
48
+ const imageUrl = ((_d = (_c = firstSubMessage === null || firstSubMessage === void 0 ? void 0 : firstSubMessage.pictureElem) === null || _c === void 0 ? void 0 : _c.snapshotPicture) === null || _d === void 0 ? void 0 : _d.url) ||
49
+ ((_f = (_e = firstSubMessage === null || firstSubMessage === void 0 ? void 0 : firstSubMessage.pictureElem) === null || _e === void 0 ? void 0 : _e.sourcePicture) === null || _f === void 0 ? void 0 : _f.url) ||
50
+ ((_h = (_g = message.pictureElem) === null || _g === void 0 ? void 0 : _g.snapshotPicture) === null || _h === void 0 ? void 0 : _h.url) ||
51
+ ((_k = (_j = message.pictureElem) === null || _j === void 0 ? void 0 : _j.sourcePicture) === null || _k === void 0 ? void 0 : _k.url);
52
+ if (!imageUrl)
53
+ return null;
54
+ return (_jsx("img", { src: imageUrl, alt: "", className: "h-[51px] w-[51px] shrink-0 object-cover" }));
55
+ }
56
+ if (message.contentType === MessageType.VideoMessage) {
57
+ const posterUrl = (_l = message.videoElem) === null || _l === void 0 ? void 0 : _l.snapshotUrl;
58
+ return (_jsxs("div", { className: "relative h-[51px] w-[51px] shrink-0 overflow-hidden bg-gray-900", children: [posterUrl && (_jsx("img", { src: posterUrl, alt: "", className: "h-full w-full object-cover" })), _jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-black/60", children: _jsx(Icon, { icon: "play-o", size: 12, className: "text-white" }) }) })] }));
59
+ }
60
+ if (message.contentType === MessageType.UrlTextMessage) {
61
+ const imageUrl = (_m = parseUrlMetadata(message.ex)) === null || _m === void 0 ? void 0 : _m.image;
62
+ if (!imageUrl) {
63
+ return (_jsx("div", { className: "flex h-[51px] w-[51px] shrink-0 items-center justify-center", children: _jsx(Icon, { icon: "link-alt-o", size: 24, className: "text-primary-400" }) }));
64
+ }
65
+ return (_jsx("img", { src: imageUrl, alt: "", className: "h-[51px] w-[51px] shrink-0 object-cover" }));
66
+ }
67
+ return null;
68
+ };
69
+ const PinnedMessageBar = () => {
70
+ const { t } = useTranslation();
71
+ const userID = useAuthStore((state) => state.userID);
72
+ const conversation = useConversationStore((state) => state.conversationData);
73
+ const selectedConversationId = useConversationStore((state) => state.selectedConversationId);
74
+ const conversationId = selectedConversationId || (conversation === null || conversation === void 0 ? void 0 : conversation.conversationID) || "";
75
+ const { pinnedMessageList, pinnedTotalCount } = usePinnedMessages(conversationId);
76
+ const latestPinnedMessage = pinnedMessageList[0];
77
+ if (!latestPinnedMessage)
78
+ return null;
79
+ const isMe = latestPinnedMessage.sendID === userID;
80
+ const senderName = isMe
81
+ ? t("your_self")
82
+ : latestPinnedMessage.senderNickname || t("message_sender_unnamed");
83
+ const subtitleKey = PINNED_SUBTITLE_KEY_BY_TYPE[latestPinnedMessage.contentType] ||
84
+ "pinned_from_text";
85
+ const title = getPinnedMessageTitle(latestPinnedMessage);
86
+ const showTitle = !TITLE_LESS_MESSAGE_TYPES.includes(latestPinnedMessage.contentType) &&
87
+ Boolean(title);
88
+ const hasThumbnail = latestPinnedMessage.contentType !== MessageType.TextMessage &&
89
+ latestPinnedMessage.contentType !== MessageType.QuoteMessage;
90
+ const hasFlushThumbnail = hasThumbnail &&
91
+ latestPinnedMessage.contentType !== MessageType.FileMessage;
92
+ const remainingPinnedCount = pinnedTotalCount - 1;
93
+ return (_jsxs("div", { "data-testid": "pinned-message-bar", className: clsx("flex w-full shrink-0 items-center gap-3 bg-gray-cool-25 pr-3", hasFlushThumbnail ? "py-0 pl-0" : "py-1 pl-3"), children: [hasThumbnail && _jsx(PinnedMessageThumbnail, { message: latestPinnedMessage }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col justify-center", children: [showTitle && (_jsx("p", { className: "truncate text-sm font-medium leading-[160%] text-gray-900", children: title })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Icon, { icon: "thumbtack-b", size: 12, className: "text-primary-400" }), _jsx("p", { className: "truncate text-[13px] leading-[160%] text-gray-600", children: t(subtitleKey, { name: senderName }) })] })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [remainingPinnedCount > 0 && (_jsxs("span", { className: "flex min-w-[26px] items-center justify-center rounded-full border-2 border-white bg-gradient-danger-light px-1 text-xs font-bold leading-[160%] text-white", children: ["+", remainingPinnedCount] })), _jsx(Icon, { icon: "angle-down-o", size: 20, className: "text-gray-600" })] })] }));
94
+ };
95
+ export default PinnedMessageBar;
@@ -0,0 +1,3 @@
1
+ declare const BotInactiveNotice: () => import("react/jsx-runtime").JSX.Element;
2
+ export default BotInactiveNotice;
3
+ //# sourceMappingURL=BotInactiveNotice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BotInactiveNotice.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/bot/BotInactiveNotice.tsx"],"names":[],"mappings":"AAOA,QAAA,MAAM,iBAAiB,+CActB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from "react-i18next";
4
+ import { Icon } from "../../../components/icon";
5
+ const ICON_SIZE = 16;
6
+ const BotInactiveNotice = () => {
7
+ const { t } = useTranslation("conversation-inbox");
8
+ return (_jsxs("div", { "data-testid": "conversation-bot-inactive-notice", className: "flex w-full items-center justify-center gap-1 bg-white px-4 py-1", children: [_jsx(Icon, { icon: "info-circle-o", size: ICON_SIZE, className: "shrink-0 text-gray-900" }), _jsx("span", { className: "text-[12px] font-normal leading-[1.6] text-gray-900", children: t("thread_detail.bot.inactive_notice") })] }));
9
+ };
10
+ export default BotInactiveNotice;
@@ -4,4 +4,5 @@ export { default as BotButtonMessage } from "./BotButtonMessage";
4
4
  export type { BotButtonMessageProps } from "./BotButtonMessage";
5
5
  export { default as BotSuggest } from "./BotSuggest";
6
6
  export type { BotSuggestProps } from "./BotSuggest";
7
+ export { default as BotInactiveNotice } from "./BotInactiveNotice";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/bot/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/bot/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1,3 +1,4 @@
1
1
  export { default as BotStartButton } from "./BotStartButton";
2
2
  export { default as BotButtonMessage } from "./BotButtonMessage";
3
3
  export { default as BotSuggest } from "./BotSuggest";
4
+ export { default as BotInactiveNotice } from "./BotInactiveNotice";
@@ -3,6 +3,8 @@ export type { ConversationDetailHeaderProps } from "./ConversationDetailHeader";
3
3
  export { default as ConversationDetailInput } from "./ConversationDetailInput";
4
4
  export type { ConversationDetailInputProps } from "./ConversationDetailInput";
5
5
  export { default as ConversationMessageList } from "./ConversationMessageList";
6
+ export { default as PinnedMessageBar } from "./PinnedMessageBar";
7
+ export { default as PinLimitModal } from "./PinLimitModal";
6
8
  export { default as ConversationMoreOptionPanel } from "./ConversationMoreOptionPanel";
7
9
  export type { ConversationMoreOptionPanelProps } from "./ConversationMoreOptionPanel";
8
10
  export { default as ConversationMoreOptionItem } from "./ConversationMoreOptionItem";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACjF,YAAY,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAEhF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,YAAY,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAE9E,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AACrF,YAAY,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACjF,YAAY,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAEhF,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,YAAY,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAE9E,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AACrF,YAAY,EAAE,+BAA+B,EAAE,MAAM,8BAA8B,CAAC"}
@@ -1,5 +1,7 @@
1
1
  export { default as ConversationDetailHeader } from "./ConversationDetailHeader";
2
2
  export { default as ConversationDetailInput } from "./ConversationDetailInput";
3
3
  export { default as ConversationMessageList } from "./ConversationMessageList";
4
+ export { default as PinnedMessageBar } from "./PinnedMessageBar";
5
+ export { default as PinLimitModal } from "./PinLimitModal";
4
6
  export { default as ConversationMoreOptionPanel } from "./ConversationMoreOptionPanel";
5
7
  export { default as ConversationMoreOptionItem } from "./ConversationMoreOptionItem";
@@ -4,7 +4,8 @@ import dayjs from "dayjs";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import useAuthStore from "../../store/auth";
6
6
  import { formatTimestamp, parseLatestMessage } from "../../utils/common";
7
- import { isBotPeer, resolveConversationAvatarUrl, resolvePeerBadgeLabel, resolveConversationDisplayName, } from "../../utils/conversation";
7
+ import { isBotPeer, resolvePeerBadgeLabel, resolveConversationDisplayName, } from "../../utils/conversation";
8
+ import { useConversationAvatarUrl } from "../../hooks/conversation/useConversationAvatarUrl";
8
9
  import ThreadCard from "../../components/conversation/ThreadCard";
9
10
  import ConversationPeerAvatar from "./ConversationPeerAvatar";
10
11
  import ConversationInboxTitle from "./ConversationInboxTitle";
@@ -20,7 +21,7 @@ const ConversationInboxItem = ({ item, onClick }) => {
20
21
  const { t: tCommon } = useTranslation("common");
21
22
  const currentUserId = useAuthStore((state) => state.userID);
22
23
  const displayName = resolveConversationDisplayName(item, t);
23
- const avatarUrl = resolveConversationAvatarUrl(item);
24
+ const avatarUrl = useConversationAvatarUrl(item);
24
25
  const badgeLabel = resolvePeerBadgeLabel(item.peerType, t);
25
26
  const latestMessagePreview = parseLatestMessage(item.latestMsg, currentUserId, tCommon, isBotPeer(item));
26
27
  const latestMessageTime = formatLatestMessageTime(item.latestMsgSendTime || null);
@@ -3,5 +3,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import clsx from "clsx";
4
4
  import { Icon } from "../../components/icon";
5
5
  import { PeerType } from "../../types/conversation";
6
- const ConversationInboxTitle = ({ peerType, displayName, hasUnread = false, }) => (_jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [peerType === PeerType.GROUP && (_jsx(Icon, { icon: "user-two-b", size: 12, className: "shrink-0 text-black" })), peerType === PeerType.BOT && (_jsx("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-black text-white", children: _jsx(Icon, { icon: "droppii-o-1", size: 8 }) })), _jsx("span", { className: clsx("min-w-0 truncate text-[16px] leading-[160%] text-black [font-feature-settings:'liga'_off,'clig'_off]", hasUnread ? "font-bold tracking-[0.16px]" : "font-[510]"), children: displayName })] }));
6
+ const ConversationInboxTitle = ({ peerType, displayName, hasUnread = false, }) => (_jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [peerType === PeerType.GROUP && (_jsx(Icon, { icon: "user-two-b", size: 12, className: "shrink-0 text-black" })), peerType === PeerType.BOT && (_jsx("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-black text-white", children: _jsx(Icon, { icon: "droppii-b", size: 8 }) })), _jsx("span", { className: clsx("min-w-0 truncate text-[16px] leading-[160%] text-black [font-feature-settings:'liga'_off,'clig'_off]", hasUnread ? "font-bold tracking-[0.16px]" : "font-[510]"), children: displayName })] }));
7
7
  export default ConversationInboxTitle;
@@ -36,6 +36,6 @@ const QuoteMessageItem = ({ message, onPressQuoteMessage, }) => {
36
36
  name: quoteMessage.senderNickname,
37
37
  id: quoteMessage.sendID,
38
38
  });
39
- return (_jsx("div", { className: "flex cursor-pointer items-center overflow-hidden rounded-r-xl bg-white/70", onClick: () => onPressQuoteMessage === null || onPressQuoteMessage === void 0 ? void 0 : onPressQuoteMessage(quoteMessage.clientMsgID || ""), children: _jsx(ReplyQuoteContent, { message: quoteMessage, title: title || "" }) }));
39
+ return (_jsx("div", { className: "flex cursor-pointer items-center overflow-hidden rounded-r-xl bg-white/70", onClick: () => onPressQuoteMessage === null || onPressQuoteMessage === void 0 ? void 0 : onPressQuoteMessage(quoteMessage.clientMsgID || ""), children: _jsx(ReplyQuoteContent, { message: quoteMessage, title: title }) }));
40
40
  };
41
41
  export default QuoteMessageItem;
@@ -1 +1 @@
1
- {"version":3,"file":"TextMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/TextMessage.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAe,MAAM,yBAAyB,CAAC;AAQnE,UAAU,oBAAoB;IAC5B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,eAAe,GAAI,OAAO,oBAAoB,mDAiDnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"TextMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/TextMessage.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAe,MAAM,yBAAyB,CAAC;AAQnE,UAAU,oBAAoB;IAC5B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,eAAe,GAAI,OAAO,oBAAoB,mDAqDnD,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -25,7 +25,7 @@ const TextMessageItem = (props) => {
25
25
  const richHtmlContent = ((_b = (_a = extendMessageInfo === null || extendMessageInfo === void 0 ? void 0 : extendMessageInfo.messageInfo) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.content) || "";
26
26
  if (richHtmlContent && htmlHasFormatting(richHtmlContent)) {
27
27
  const sanitizedContent = sanitizeHtml(richHtmlContent);
28
- return (_jsx("div", { className: clsx("break-normal whitespace-pre-line flex-1", MESSAGE_TEXT_CLASS), style: { overflowWrap: "anywhere" }, dangerouslySetInnerHTML: { __html: sanitizedContent } }));
28
+ return (_jsx("div", { className: clsx("min-w-0 flex-1 whitespace-pre-line [overflow-wrap:anywhere]", MESSAGE_TEXT_CLASS), dangerouslySetInnerHTML: { __html: sanitizedContent } }));
29
29
  }
30
30
  const text = ((_c = message === null || message === void 0 ? void 0 : message.textElem) === null || _c === void 0 ? void 0 : _c.content) ||
31
31
  ((_d = message === null || message === void 0 ? void 0 : message.quoteElem) === null || _d === void 0 ? void 0 : _d.text) ||
@@ -36,6 +36,6 @@ const TextMessageItem = (props) => {
36
36
  if (extractLinkUrls(text).length > 0) {
37
37
  return _jsx(LinkText, { text: text });
38
38
  }
39
- return (_jsx("p", { className: clsx("whitespace-pre-wrap break-words", MESSAGE_TEXT_CLASS), children: text }));
39
+ return (_jsx("p", { className: clsx("min-w-0 whitespace-pre-wrap [overflow-wrap:anywhere]", MESSAGE_TEXT_CLASS), children: text }));
40
40
  };
41
41
  export default TextMessageItem;
@@ -9,8 +9,10 @@ interface MessageItemProps {
9
9
  onRevokeMessage?: (clientMsgID: string) => void;
10
10
  onQuoteMessage?: (message: MessageItemType) => void;
11
11
  onPressQuoteMessage?: (clientMsgID: string) => void;
12
+ onPinMessage?: (message: MessageItemType) => void;
13
+ onUnpinMessage?: (message: MessageItemType) => void;
12
14
  enableLongPress?: boolean;
13
15
  }
14
- declare const MessageItem: ({ message, allMessages, isGroupChat, forceHideTip, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, enableLongPress, }: MessageItemProps) => import("react/jsx-runtime").JSX.Element | null;
16
+ declare const MessageItem: ({ message, allMessages, isGroupChat, forceHideTip, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress, }: MessageItemProps) => import("react/jsx-runtime").JSX.Element | null;
15
17
  export default MessageItem;
16
18
  //# 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,yBAAyB,CAAC;AAuCjC,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,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAeD,QAAA,MAAM,WAAW,GAAI,uKAWlB,gBAAgB,mDA4RlB,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,yBAAyB,CAAC;AAwCjC,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;AAeD,QAAA,MAAM,WAAW,GAAI,qMAalB,gBAAgB,mDA4UlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -26,9 +26,9 @@ import RevokeMessageItem from "./RevokeMessage";
26
26
  import QuoteMessageItem from "./QuoteMessage";
27
27
  import SystemLogMessageItem from "./SystemLogMessage";
28
28
  import { isSystemLogMessageType } from "../../../utils/messageLog";
29
- import { buildMessageToolboxMenuItems, getCopyableMessageText, isCopyableMessageType, MessageToolboxActionKey, MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, useMessageToolboxVisibility, } from "../../../components/message/toolbox";
30
- const MessageAvatar = ({ avatarUrl }) => avatarUrl ? (_jsx("img", { src: avatarUrl, alt: "", className: "h-6 w-6 shrink-0 self-end rounded-full object-cover" })) : (_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center self-end rounded-full bg-chat-avatar", children: _jsx(Icon, { icon: "droppii-o-1", size: 14, className: "text-white" }) }));
31
- const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip = false, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, enableLongPress = false, }) => {
29
+ import { buildMessageToolboxMenuItems, getCopyableMessageText, isCopyableMessageType, isPinnableMessageType, MessageToolboxActionKey, MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, useMessageToolboxVisibility, } from "../../../components/message/toolbox";
30
+ const MessageAvatar = ({ avatarUrl }) => avatarUrl ? (_jsx("img", { src: avatarUrl, alt: "", className: "h-6 w-6 shrink-0 self-end rounded-full object-cover" })) : (_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center self-end rounded-full bg-chat-avatar", children: _jsx(Icon, { icon: "droppii-b", size: 14, className: "text-white" }) }));
31
+ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip = false, contextMenuOpen, onContextMenuOpenChange, onRevokeMessage, onQuoteMessage, onPressQuoteMessage, onPinMessage, onUnpinMessage, enableLongPress = false, }) => {
32
32
  var _a;
33
33
  const { t } = useTranslation();
34
34
  const { resendMessage } = useSendMessage();
@@ -74,12 +74,26 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
74
74
  onClick: () => onQuoteMessage(message),
75
75
  });
76
76
  }
77
- if (isDeliveredForUi) {
77
+ if (isDeliveredForUi &&
78
+ isPinnableMessageType(message === null || message === void 0 ? void 0 : message.contentType) &&
79
+ onPinMessage &&
80
+ onUnpinMessage) {
81
+ const isMessagePinned = Boolean(message === null || message === void 0 ? void 0 : message.isPinned);
78
82
  actions.push({
79
- key: MessageToolboxActionKey.PIN,
80
- label: t("context_menu_pin"),
81
- icon: "thumbstack-o",
82
- onClick: closeToolbox,
83
+ key: isMessagePinned
84
+ ? MessageToolboxActionKey.UNPIN
85
+ : MessageToolboxActionKey.PIN,
86
+ label: isMessagePinned ? t("context_menu_unpin") : t("context_menu_pin"),
87
+ icon: isMessagePinned ? "thumbtack-off-o" : "thumbtack-o",
88
+ onClick: () => {
89
+ if (isMessagePinned) {
90
+ onUnpinMessage(message);
91
+ }
92
+ else {
93
+ onPinMessage(message);
94
+ }
95
+ closeToolbox();
96
+ },
83
97
  });
84
98
  }
85
99
  if (isDeliveredForUi && isCopyableMessageType(message === null || message === void 0 ? void 0 : message.contentType)) {
@@ -98,7 +112,7 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
98
112
  actions.push({
99
113
  key: MessageToolboxActionKey.REVOKE,
100
114
  label: t("context_menu_revoke"),
101
- icon: "trash-alt-o-1",
115
+ icon: "trash-o",
102
116
  danger: true,
103
117
  onClick: () => onRevokeMessage((message === null || message === void 0 ? void 0 : message.clientMsgID) || ""),
104
118
  });
@@ -111,6 +125,8 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
111
125
  onRevokeMessage,
112
126
  message,
113
127
  onQuoteMessage,
128
+ onPinMessage,
129
+ onUnpinMessage,
114
130
  onContextMenuOpenChange,
115
131
  ]);
116
132
  const bubbleWrapperRef = useRef(null);
@@ -160,13 +176,24 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
160
176
  timeBreakNode,
161
177
  _jsx(SystemLogMessageItem, { message: message })));
162
178
  }
163
- 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: (message === null || message === void 0 ? void 0 : message.quoteElem) ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(QuoteMessageItem, { message: message, onPressQuoteMessage: onPressQuoteMessage }), renderMessageByType(message)] })) : (message === null || message === void 0 ? void 0 : message.contentType) === MessageType.MergeMessage ? (_jsxs(_Fragment, { children: [_jsx(MergeImageMessageItem, { message: message }), (message === null || message === void 0 ? void 0 : message.textElem) && _jsx(TextMessageItem, { message: message })] })) : (renderMessageByType(message)) }));
164
- const wrappedBubble = hasContextMenu ? (_jsx(Dropdown, { menu: { items: contextMenuItems }, trigger: ["contextMenu"], open: contextMenuOpen, onOpenChange: onContextMenuOpenChange, overlayClassName: MESSAGE_TOOLBOX_OVERLAY_CLASSNAME, children: _jsx("div", { ref: bubbleWrapperRef, className: "min-w-0", children: bubbleNode }) })) : (bubbleNode);
179
+ const renderBubbleContent = () => {
180
+ if (message === null || message === void 0 ? void 0 : message.quoteElem) {
181
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(QuoteMessageItem, { message: message, onPressQuoteMessage: onPressQuoteMessage }), renderMessageByType(message)] }));
182
+ }
183
+ if ((message === null || message === void 0 ? void 0 : message.contentType) === MessageType.MergeMessage) {
184
+ return (_jsxs(_Fragment, { children: [_jsx(MergeImageMessageItem, { message: message }), (message === null || message === void 0 ? void 0 : message.textElem) && _jsx(TextMessageItem, { message: message })] }));
185
+ }
186
+ return renderMessageByType(message);
187
+ };
188
+ 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() }));
189
+ const showPinnedBadge = Boolean(message === null || message === void 0 ? void 0 : message.isPinned) && Boolean(onPinMessage);
190
+ 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;
191
+ 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] }));
165
192
  return (_createElement("div", Object.assign({}, messageContainerProps, { key: message === null || message === void 0 ? void 0 : message.clientMsgID }),
166
193
  timeBreakNode,
167
194
  _jsxs("div", { className: clsx("flex min-w-0 items-end gap-1", isMine ? "justify-end pl-12 pr-3" : "justify-start pl-3 pr-12"), children: [!isMine &&
168
195
  showPeerInfo &&
169
- (showAvatar ? (_jsx(MessageAvatar, { avatarUrl: message === null || message === void 0 ? void 0 : message.senderFaceUrl })) : (_jsx("div", { className: "h-6 w-6 shrink-0 self-end" }))), _jsxs("div", { className: clsx("flex min-w-0 max-w-[75%] flex-col gap-1", isMine ? "items-end" : "items-start"), children: [_jsxs("div", { className: "flex items-end gap-1.5", children: [isMine && (_jsx(MessageStatusIndicator, { message: message, isLatest: isLatestMine })), wrappedBubble] }), isMine && message.status === MessageStatus.Failed && (_jsx("span", { className: "mt-1 text-xs text-red-500", children: isMediaResendable(message) ? (_jsx(Trans, { i18nKey: "message_send_failed", components: {
196
+ (showAvatar ? (_jsx(MessageAvatar, { avatarUrl: message === null || message === void 0 ? void 0 : message.senderFaceUrl })) : (_jsx("div", { className: "h-6 w-6 shrink-0 self-end" }))), _jsxs("div", { className: clsx("flex min-w-0 max-w-[75%] flex-col gap-1", isMine ? "items-end" : "items-start"), children: [_jsxs("div", { className: clsx("flex items-end gap-1.5", showPinnedBadge && "mt-1.5"), children: [isMine && (_jsx(MessageStatusIndicator, { message: message, isLatest: isLatestMine })), wrappedBubble] }), isMine && message.status === MessageStatus.Failed && (_jsx("span", { className: "mt-1 text-xs text-red-500", children: isMediaResendable(message) ? (_jsx(Trans, { i18nKey: "message_send_failed", components: {
170
197
  bold: (_jsx("strong", { className: "cursor-pointer", onClick: () => resendMessage(message) })),
171
198
  } })) : (t("message_send_failed_no_retry")) }))] })] })));
172
199
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ReplyPreviewMedia.d.ts","sourceRoot":"","sources":["../../../../src/components/message/reply/ReplyPreviewMedia.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,yBAAyB,CAAC;AAKnE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;CACtB;AAYD,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,mDAoD7D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"ReplyPreviewMedia.d.ts","sourceRoot":"","sources":["../../../../src/components/message/reply/ReplyPreviewMedia.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,yBAAyB,CAAC;AAKnE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,mDAoD7D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -2,20 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { MessageType } from "@openim/wasm-client-sdk";
3
3
  import { documentIcon } from "../../../assets/svg";
4
4
  import { Icon } from "../../../components/icon";
5
- const parseUrlMetadataImage = (ex) => {
6
- var _a;
7
- if (!ex)
8
- return undefined;
9
- try {
10
- const extendMessageInfo = JSON.parse(ex);
11
- return (_a = extendMessageInfo === null || extendMessageInfo === void 0 ? void 0 : extendMessageInfo.urlMetadata) === null || _a === void 0 ? void 0 : _a.image;
12
- }
13
- catch (_b) {
14
- return undefined;
15
- }
16
- };
5
+ import { parseUrlMetadata } from "../../../utils/common";
17
6
  const ReplyPreviewMedia = ({ message }) => {
18
- var _a, _b, _c, _d, _e;
7
+ var _a, _b, _c, _d, _e, _f;
19
8
  if (message.contentType === MessageType.FileMessage) {
20
9
  return (_jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden [&>svg]:h-6 [&>svg]:w-6", children: documentIcon }));
21
10
  }
@@ -31,7 +20,7 @@ const ReplyPreviewMedia = ({ message }) => {
31
20
  return (_jsxs("div", { className: "relative h-[42px] w-[42px] shrink-0 overflow-hidden rounded-lg bg-gray-900", children: [posterUrl && (_jsx("img", { src: posterUrl, alt: "", className: "h-full w-full object-cover" })), _jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: _jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-black/60", children: _jsx(Icon, { icon: "play-o", size: 12, className: "text-white" }) }) })] }));
32
21
  }
33
22
  if (message.contentType === MessageType.UrlTextMessage) {
34
- const imageUrl = parseUrlMetadataImage(message.ex);
23
+ const imageUrl = (_f = parseUrlMetadata(message.ex)) === null || _f === void 0 ? void 0 : _f.image;
35
24
  if (!imageUrl)
36
25
  return null;
37
26
  return (_jsx("img", { src: imageUrl, alt: "", className: "h-[42px] w-[42px] shrink-0 rounded-lg object-cover" }));
@@ -2,8 +2,8 @@ import { MessageItem, MessageType } from "@openim/wasm-client-sdk";
2
2
  export declare const hasReplyMediaPreview: (contentType: MessageType) => boolean;
3
3
  interface ReplyQuoteContentProps {
4
4
  message: MessageItem;
5
- title: string;
5
+ title?: string;
6
6
  }
7
- export declare const ReplyQuoteContent: ({ message, title }: ReplyQuoteContentProps) => import("react/jsx-runtime").JSX.Element;
7
+ export declare const ReplyQuoteContent: ({ message, title, }: ReplyQuoteContentProps) => import("react/jsx-runtime").JSX.Element;
8
8
  export default ReplyQuoteContent;
9
9
  //# sourceMappingURL=ReplyQuoteContent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReplyQuoteContent.d.ts","sourceRoot":"","sources":["../../../../src/components/message/reply/ReplyQuoteContent.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AASnE,eAAO,MAAM,oBAAoB,GAAI,aAAa,WAAW,YACb,CAAC;AAEjD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,eAAO,MAAM,iBAAiB,GAAI,oBAAoB,sBAAsB,4CAsB3E,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"ReplyQuoteContent.d.ts","sourceRoot":"","sources":["../../../../src/components/message/reply/ReplyQuoteContent.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AASnE,eAAO,MAAM,oBAAoB,GAAI,aAAa,WAAW,YACb,CAAC;AAEjD,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,iBAAiB,GAAI,qBAG/B,sBAAsB,4CAsBxB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -8,7 +8,7 @@ const TEXT_LIKE_MESSAGE_TYPES = [
8
8
  MessageType.QuoteMessage,
9
9
  ];
10
10
  export const hasReplyMediaPreview = (contentType) => !TEXT_LIKE_MESSAGE_TYPES.includes(contentType);
11
- export const ReplyQuoteContent = ({ message, title }) => {
11
+ export const ReplyQuoteContent = ({ message, title = "", }) => {
12
12
  var _a, _b;
13
13
  const { t } = useTranslation();
14
14
  const hasMedia = hasReplyMediaPreview(message.contentType);
@@ -2,4 +2,5 @@ export * from "./types";
2
2
  export * from "./MessageToolboxMenu";
3
3
  export * from "./useMessageToolboxVisibility";
4
4
  export * from "./getCopyableMessageText";
5
+ export * from "./isPinnableMessageType";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC"}
@@ -2,3 +2,4 @@ export * from "./types";
2
2
  export * from "./MessageToolboxMenu";
3
3
  export * from "./useMessageToolboxVisibility";
4
4
  export * from "./getCopyableMessageText";
5
+ export * from "./isPinnableMessageType";