@droppii-org/chat-sdk 0.1.59 → 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 (53) hide show
  1. package/dist/assets/droppiiFontSelection.json +13195 -3192
  2. package/dist/components/conversation-detail/ConversationDetailInput.js +1 -1
  3. package/dist/components/conversation-detail/ConversationMessageList.d.ts.map +1 -1
  4. package/dist/components/conversation-detail/ConversationMessageList.js +33 -2
  5. package/dist/components/conversation-detail/PinLimitModal.d.ts +7 -0
  6. package/dist/components/conversation-detail/PinLimitModal.d.ts.map +1 -0
  7. package/dist/components/conversation-detail/PinLimitModal.js +11 -0
  8. package/dist/components/conversation-detail/PinnedMessageBar.d.ts +3 -0
  9. package/dist/components/conversation-detail/PinnedMessageBar.d.ts.map +1 -0
  10. package/dist/components/conversation-detail/PinnedMessageBar.js +95 -0
  11. package/dist/components/conversation-detail/index.d.ts +2 -0
  12. package/dist/components/conversation-detail/index.d.ts.map +1 -1
  13. package/dist/components/conversation-detail/index.js +2 -0
  14. package/dist/components/conversation-inbox/ConversationInboxTitle.js +1 -1
  15. package/dist/components/message/item/index.d.ts +3 -1
  16. package/dist/components/message/item/index.d.ts.map +1 -1
  17. package/dist/components/message/item/index.js +29 -11
  18. package/dist/components/message/reply/ReplyPreviewMedia.d.ts.map +1 -1
  19. package/dist/components/message/reply/ReplyPreviewMedia.js +3 -14
  20. package/dist/components/message/toolbox/index.d.ts +1 -0
  21. package/dist/components/message/toolbox/index.d.ts.map +1 -1
  22. package/dist/components/message/toolbox/index.js +1 -0
  23. package/dist/components/message/toolbox/isPinnableMessageType.d.ts +3 -0
  24. package/dist/components/message/toolbox/isPinnableMessageType.d.ts.map +1 -0
  25. package/dist/components/message/toolbox/isPinnableMessageType.js +11 -0
  26. package/dist/components/message/toolbox/types.d.ts +1 -0
  27. package/dist/components/message/toolbox/types.d.ts.map +1 -1
  28. package/dist/components/message/toolbox/types.js +1 -0
  29. package/dist/constants/index.d.ts +1 -0
  30. package/dist/constants/index.d.ts.map +1 -1
  31. package/dist/constants/index.js +1 -0
  32. package/dist/hooks/message/usePinMessage.d.ts +7 -0
  33. package/dist/hooks/message/usePinMessage.d.ts.map +1 -0
  34. package/dist/hooks/message/usePinMessage.js +42 -0
  35. package/dist/hooks/message/usePinnedMessages.d.ts +6 -0
  36. package/dist/hooks/message/usePinnedMessages.d.ts.map +1 -0
  37. package/dist/hooks/message/usePinnedMessages.js +33 -0
  38. package/dist/locales/vi/common.json +13 -0
  39. package/dist/screens/conversation-detail/index.d.ts.map +1 -1
  40. package/dist/screens/conversation-detail/index.js +2 -2
  41. package/dist/store/conversation.d.ts.map +1 -1
  42. package/dist/store/conversation.js +21 -1
  43. package/dist/store/pinnedMessage.d.ts +4 -0
  44. package/dist/store/pinnedMessage.d.ts.map +1 -0
  45. package/dist/store/pinnedMessage.js +17 -0
  46. package/dist/store/type.d.ts +4 -0
  47. package/dist/store/type.d.ts.map +1 -1
  48. package/dist/styles/global.css +1 -1
  49. package/dist/tsconfig.tsbuildinfo +1 -1
  50. package/dist/utils/common.d.ts +2 -0
  51. package/dist/utils/common.d.ts.map +1 -1
  52. package/dist/utils/common.js +11 -0
  53. package/package.json +127 -126
@@ -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;
@@ -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;
@@ -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";
@@ -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;
@@ -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,mDAoSlB,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);
@@ -170,12 +186,14 @@ const MessageItem = ({ message, allMessages, isGroupChat = false, forceHideTip =
170
186
  return renderMessageByType(message);
171
187
  };
172
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() }));
173
- 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);
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] }));
174
192
  return (_createElement("div", Object.assign({}, messageContainerProps, { key: message === null || message === void 0 ? void 0 : message.clientMsgID }),
175
193
  timeBreakNode,
176
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 &&
177
195
  showPeerInfo &&
178
- (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: {
179
197
  bold: (_jsx("strong", { className: "cursor-pointer", onClick: () => resendMessage(message) })),
180
198
  } })) : (t("message_send_failed_no_retry")) }))] })] })));
181
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,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";
@@ -0,0 +1,3 @@
1
+ import { MessageType } from "@openim/wasm-client-sdk";
2
+ export declare const isPinnableMessageType: (contentType?: MessageType) => boolean;
3
+ //# sourceMappingURL=isPinnableMessageType.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isPinnableMessageType.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/isPinnableMessageType.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAYtD,eAAO,MAAM,qBAAqB,GAAI,cAAc,WAAW,YACY,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { MessageType } from "@openim/wasm-client-sdk";
2
+ const PINNABLE_MESSAGE_TYPES = [
3
+ MessageType.TextMessage,
4
+ MessageType.QuoteMessage,
5
+ MessageType.UrlTextMessage,
6
+ MessageType.PictureMessage,
7
+ MessageType.VideoMessage,
8
+ MessageType.FileMessage,
9
+ MessageType.MergeMessage
10
+ ];
11
+ export const isPinnableMessageType = (contentType) => contentType !== undefined && PINNABLE_MESSAGE_TYPES.includes(contentType);
@@ -1,6 +1,7 @@
1
1
  export declare const MessageToolboxActionKey: {
2
2
  readonly REPLY: "REPLY";
3
3
  readonly PIN: "PIN";
4
+ readonly UNPIN: "UNPIN";
4
5
  readonly COPY_MESSAGE: "COPY_MESSAGE";
5
6
  readonly REVOKE: "REVOKE";
6
7
  };
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB;;;;;CAK1B,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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/message/toolbox/types.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB;;;;;;CAM1B,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"}
@@ -1,6 +1,7 @@
1
1
  export const MessageToolboxActionKey = {
2
2
  REPLY: "REPLY",
3
3
  PIN: "PIN",
4
+ UNPIN: "UNPIN",
4
5
  COPY_MESSAGE: "COPY_MESSAGE",
5
6
  REVOKE: "REVOKE",
6
7
  };
@@ -7,4 +7,5 @@ export declare const PAGE_SIZE: {
7
7
  };
8
8
  export declare const MSG_ITEM_PREFIX = "msg-item-";
9
9
  export declare const MSG_ITEM_CONTENT_PREFIX = "msg-item-content-";
10
+ export declare const MAX_PINNED_MESSAGES_PER_CONVERSATION = 5;
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,qFAC2D,CAAC;AAErF,eAAO,MAAM,SAAS;;;;;CAKZ,CAAC;AAEX,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,uBAAuB,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,qFAC2D,CAAC;AAErF,eAAO,MAAM,SAAS;;;;;CAKZ,CAAC;AAEX,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAE3D,eAAO,MAAM,oCAAoC,IAAI,CAAC"}
@@ -7,3 +7,4 @@ export const PAGE_SIZE = {
7
7
  };
8
8
  export const MSG_ITEM_PREFIX = "msg-item-";
9
9
  export const MSG_ITEM_CONTENT_PREFIX = "msg-item-content-";
10
+ export const MAX_PINNED_MESSAGES_PER_CONVERSATION = 5;
@@ -0,0 +1,7 @@
1
+ export declare const usePinMessage: () => {
2
+ pinMessage: (conversationID: string, clientMsgID: string) => Promise<void>;
3
+ unpinMessage: (conversationID: string, clientMsgID: string) => Promise<void>;
4
+ pinLoading: boolean;
5
+ unpinLoading: boolean;
6
+ };
7
+ //# sourceMappingURL=usePinMessage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePinMessage.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/usePinMessage.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,aAAa;;;;;CA6CzB,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { useRequest } from "ahooks";
2
+ import { DChatSDK } from "../../constants/sdk";
3
+ import useAuthStore from "../../store/auth";
4
+ import { updateOneMessage } from "../../hooks/message/useMessage";
5
+ import { fetchPinnedMessages } from "./usePinnedMessages";
6
+ export const usePinMessage = () => {
7
+ const userID = useAuthStore((state) => state.userID);
8
+ const { runAsync: pinMessage, loading: pinLoading } = useRequest(async (conversationID, clientMsgID) => {
9
+ await DChatSDK.pinMsg({
10
+ conversationID,
11
+ clientMsgID,
12
+ });
13
+ updateOneMessage({
14
+ clientMsgID,
15
+ isPinned: true,
16
+ pinnedByUserID: userID,
17
+ pinnedTime: Date.now(),
18
+ });
19
+ await fetchPinnedMessages(conversationID);
20
+ }, {
21
+ manual: true,
22
+ });
23
+ const { runAsync: unpinMessage, loading: unpinLoading } = useRequest(async (conversationID, clientMsgID) => {
24
+ await DChatSDK.unpinMsg({
25
+ conversationID,
26
+ clientMsgID,
27
+ });
28
+ updateOneMessage({
29
+ clientMsgID,
30
+ isPinned: false,
31
+ });
32
+ await fetchPinnedMessages(conversationID);
33
+ }, {
34
+ manual: true,
35
+ });
36
+ return {
37
+ pinMessage,
38
+ unpinMessage,
39
+ pinLoading,
40
+ unpinLoading,
41
+ };
42
+ };
@@ -0,0 +1,6 @@
1
+ export declare const fetchPinnedMessages: (conversationID: string) => Promise<void>;
2
+ export declare const usePinnedMessages: (conversationID: string) => {
3
+ pinnedMessageList: import("@openim/wasm-client-sdk").MessageItem[];
4
+ pinnedTotalCount: number;
5
+ };
6
+ //# sourceMappingURL=usePinnedMessages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePinnedMessages.d.ts","sourceRoot":"","sources":["../../../src/hooks/message/usePinnedMessages.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,mBAAmB,GAAU,gBAAgB,MAAM,kBAmB/D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,gBAAgB,MAAM;;;CAiBvD,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { useEffect } from "react";
2
+ import { DChatSDK } from "../../constants/sdk";
3
+ import { PAGE_SIZE } from "../../constants";
4
+ import useConversationStore from "../../store/conversation";
5
+ export const fetchPinnedMessages = async (conversationID) => {
6
+ var _a;
7
+ const { setPinnedMessageData } = useConversationStore.getState();
8
+ try {
9
+ const { data } = await DChatSDK.getPinnedMessageList({
10
+ conversationID,
11
+ offset: 0,
12
+ pageSize: PAGE_SIZE.small,
13
+ });
14
+ const sortedMessageList = [...(data.messageList || [])].sort((first, second) => (second.pinnedTime || 0) - (first.pinnedTime || 0));
15
+ setPinnedMessageData(sortedMessageList, data.totalCount || 0, (_a = data.isEnd) !== null && _a !== void 0 ? _a : true);
16
+ }
17
+ catch (error) {
18
+ console.error("fetchPinnedMessages failed", error);
19
+ }
20
+ };
21
+ export const usePinnedMessages = (conversationID) => {
22
+ const pinnedMessageList = useConversationStore((state) => state.pinnedMessageList);
23
+ const pinnedTotalCount = useConversationStore((state) => state.pinnedTotalCount);
24
+ useEffect(() => {
25
+ if (!conversationID)
26
+ return;
27
+ void fetchPinnedMessages(conversationID);
28
+ }, [conversationID]);
29
+ return {
30
+ pinnedMessageList,
31
+ pinnedTotalCount,
32
+ };
33
+ };