@droppii-org/chat-sdk 0.6.37 → 0.6.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/images/stickerInvalid.png +0 -0
- package/dist/components/conversation-detail/ConversationDetailInput.d.ts +3 -1
- package/dist/components/conversation-detail/ConversationDetailInput.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationDetailInput.js +32 -5
- package/dist/components/conversation-detail/TypingIndicatorBar.d.ts +10 -0
- package/dist/components/conversation-detail/TypingIndicatorBar.d.ts.map +1 -0
- package/dist/components/conversation-detail/TypingIndicatorBar.js +24 -0
- package/dist/components/conversation-detail/sticker-picker/StickerCategoryTabs.d.ts +9 -0
- package/dist/components/conversation-detail/sticker-picker/StickerCategoryTabs.d.ts.map +1 -0
- package/dist/components/conversation-detail/sticker-picker/StickerCategoryTabs.js +38 -0
- package/dist/components/conversation-detail/sticker-picker/StickerPicker.d.ts +7 -0
- package/dist/components/conversation-detail/sticker-picker/StickerPicker.d.ts.map +1 -0
- package/dist/components/conversation-detail/sticker-picker/StickerPicker.js +113 -0
- package/dist/components/conversation-detail/sticker-picker/StickerSection.d.ts +12 -0
- package/dist/components/conversation-detail/sticker-picker/StickerSection.d.ts.map +1 -0
- package/dist/components/conversation-detail/sticker-picker/StickerSection.js +46 -0
- package/dist/components/conversation-detail/sticker-picker/constants.d.ts +8 -0
- package/dist/components/conversation-detail/sticker-picker/constants.d.ts.map +1 -0
- package/dist/components/conversation-detail/sticker-picker/constants.js +1 -0
- package/dist/components/message/item/StickerMessage.d.ts.map +1 -1
- package/dist/components/message/item/StickerMessage.js +3 -5
- package/dist/components/message/reply/ReplyPreviewMedia.d.ts.map +1 -1
- package/dist/components/message/reply/ReplyPreviewMedia.js +8 -2
- package/dist/constants/images.d.ts +1 -0
- package/dist/constants/images.d.ts.map +1 -1
- package/dist/constants/images.js +2 -0
- package/dist/context/ChatContext.d.ts.map +1 -1
- package/dist/context/ChatContext.js +1 -1
- package/dist/hooks/global/useGlobalEvent.d.ts.map +1 -1
- package/dist/hooks/global/useGlobalEvent.js +10 -1
- package/dist/hooks/message/useReportTypingState.d.ts +10 -0
- package/dist/hooks/message/useReportTypingState.d.ts.map +1 -0
- package/dist/hooks/message/useReportTypingState.js +73 -0
- package/dist/hooks/message/useSendMessage.d.ts +7 -1
- package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
- package/dist/hooks/message/useSendMessage.js +45 -2
- package/dist/hooks/message/useTypingStatus.d.ts +11 -0
- package/dist/hooks/message/useTypingStatus.d.ts.map +1 -0
- package/dist/hooks/message/useTypingStatus.js +120 -0
- package/dist/hooks/sticker/useStickers.d.ts +5 -0
- package/dist/hooks/sticker/useStickers.d.ts.map +1 -0
- package/dist/hooks/sticker/useStickers.js +39 -0
- package/dist/hooks/user/useAuth.d.ts.map +1 -1
- package/dist/hooks/user/useAuth.js +2 -0
- package/dist/locales/vi/conversation-inbox.json +11 -0
- package/dist/screens/conversation-detail/index.d.ts.map +1 -1
- package/dist/screens/conversation-detail/index.js +2 -1
- package/dist/screens/conversation-inbox/index.d.ts +2 -0
- package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
- package/dist/screens/conversation-inbox/index.js +2 -2
- package/dist/services/query.d.ts +3 -0
- package/dist/services/query.d.ts.map +1 -1
- package/dist/services/query.js +3 -0
- package/dist/services/routes.d.ts +3 -0
- package/dist/services/routes.d.ts.map +1 -1
- package/dist/services/routes.js +3 -0
- package/dist/store/typing.d.ts +13 -0
- package/dist/store/typing.d.ts.map +1 -0
- package/dist/store/typing.js +63 -0
- package/dist/styles/global.css +1 -1
- package/dist/types/chat.d.ts +1 -0
- package/dist/types/chat.d.ts.map +1 -1
- package/dist/types/sticker.d.ts +20 -0
- package/dist/types/sticker.d.ts.map +1 -0
- package/dist/types/sticker.js +1 -0
- package/package.json +1 -1
|
Binary file
|
|
@@ -5,7 +5,9 @@ export interface ConversationDetailInputProps {
|
|
|
5
5
|
onInputChange?: (value: string) => void;
|
|
6
6
|
moreOptions?: MoreOptionConfig[];
|
|
7
7
|
onOpenBotMenu?: () => void;
|
|
8
|
+
/** Shows the sticker picker entry in the composer. Defaults to true. */
|
|
9
|
+
showSticker?: boolean;
|
|
8
10
|
}
|
|
9
|
-
declare const ConversationDetailInput: ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, }: ConversationDetailInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
declare const ConversationDetailInput: ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, showSticker, }: ConversationDetailInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
12
|
export default ConversationDetailInput;
|
|
11
13
|
//# sourceMappingURL=ConversationDetailInput.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConversationDetailInput.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/ConversationDetailInput.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAoB/D,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,QAAA,MAAM,uBAAuB,GAAI,yFAO9B,4BAA4B,4CAkvB9B,CAAC;AAEF,eAAe,uBAAuB,CAAC"}
|
|
@@ -7,6 +7,7 @@ import clsx from "clsx";
|
|
|
7
7
|
import { Button, message as toastMessage } from "antd";
|
|
8
8
|
import { Icon } from "../../components/icon";
|
|
9
9
|
import { useSendMessage } from "../../hooks/message/useSendMessage";
|
|
10
|
+
import { useReportTypingState } from "../../hooks/message/useReportTypingState";
|
|
10
11
|
import { useJoinPublicGroup } from "../../hooks/group/useJoinPublicGroup";
|
|
11
12
|
import useAuthStore from "../../store/auth";
|
|
12
13
|
import useConversationStore from "../../store/conversation";
|
|
@@ -19,6 +20,7 @@ import { FilePreviewList } from "../../components/message/footer/FilePreview";
|
|
|
19
20
|
import LinkInputPreview from "../../components/message/footer/LinkInputPreview";
|
|
20
21
|
import { ReplyPreviewBar } from "../../components/message/reply/ReplyPreviewBar";
|
|
21
22
|
import ConversationMoreOptionPanel from "./ConversationMoreOptionPanel";
|
|
23
|
+
import StickerPicker from "./sticker-picker/StickerPicker";
|
|
22
24
|
import { useMentionTrigger } from "./mention/useMentionTrigger";
|
|
23
25
|
import { useMentionOptions } from "../../components/message/footer/mention/useMentionOptions";
|
|
24
26
|
import { useMentionTokens } from "./mention/useMentionTokens";
|
|
@@ -30,7 +32,7 @@ const MAX_LINES = 5;
|
|
|
30
32
|
const CONTAINER_MIN_HEIGHT = 48;
|
|
31
33
|
const FALLBACK_LINE_HEIGHT = 25.6;
|
|
32
34
|
const MAX_MENTIONED_USERS = 10;
|
|
33
|
-
const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, }) => {
|
|
35
|
+
const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, moreOptions, onOpenBotMenu, showSticker = true, }) => {
|
|
34
36
|
var _a;
|
|
35
37
|
const { t } = useTranslation("conversation-inbox");
|
|
36
38
|
const { t: tCommon } = useTranslation("common");
|
|
@@ -74,7 +76,8 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
74
76
|
const sendMessageOptions = useMemo(() => ({
|
|
75
77
|
scrollEvent: "CONVERSATION_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM",
|
|
76
78
|
}), []);
|
|
77
|
-
const { sendMergeMessage } = useSendMessage(sendMessageOptions);
|
|
79
|
+
const { sendMergeMessage, sendStickerMessage } = useSendMessage(sendMessageOptions);
|
|
80
|
+
const { reportActivity: reportTyping, stopTyping } = useReportTypingState(conversationId);
|
|
78
81
|
const userID = useAuthStore((state) => state.userID);
|
|
79
82
|
const setDraft = useMessageDraftStore((state) => state.setDraft);
|
|
80
83
|
const clearDraft = useMessageDraftStore((state) => state.clearDraft);
|
|
@@ -93,6 +96,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
93
96
|
const [isSending, setIsSending] = useState(false);
|
|
94
97
|
const [previewDismissed, setPreviewDismissed] = useState(false);
|
|
95
98
|
const [isMoreOptionOpen, { setFalse: closeMoreOption, toggle: toggleMoreOption },] = useBoolean(false);
|
|
99
|
+
const [isStickerOpen, { setFalse: closeSticker, toggle: toggleSticker },] = useBoolean(false);
|
|
96
100
|
const [caretIndex, setCaretIndex] = useState(0);
|
|
97
101
|
const rawMentionTrigger = useMentionTrigger(value, caretIndex);
|
|
98
102
|
// Escape dismisses the menu for the CURRENT trigger span without erasing
|
|
@@ -167,10 +171,11 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
167
171
|
}, [getTextareaHeights]);
|
|
168
172
|
useEffect(() => {
|
|
169
173
|
closeMoreOption();
|
|
174
|
+
closeSticker();
|
|
170
175
|
setListUploadFiles([]);
|
|
171
176
|
setPendingVideoUids([]);
|
|
172
177
|
resetTokens();
|
|
173
|
-
}, [conversationId, userID, closeMoreOption, resetTokens]);
|
|
178
|
+
}, [conversationId, userID, closeMoreOption, closeSticker, resetTokens]);
|
|
174
179
|
useEffect(() => {
|
|
175
180
|
setSelectedMentionIndex(0);
|
|
176
181
|
}, [mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.triggerStart, mentionTrigger === null || mentionTrigger === void 0 ? void 0 : mentionTrigger.query]);
|
|
@@ -284,6 +289,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
284
289
|
if (conversationId && userID) {
|
|
285
290
|
setDraft(userID, conversationId, nextValue);
|
|
286
291
|
}
|
|
292
|
+
reportTyping(nextValue);
|
|
287
293
|
onInputChange === null || onInputChange === void 0 ? void 0 : onInputChange(nextValue);
|
|
288
294
|
};
|
|
289
295
|
// Covers caret-only movement (click, arrow keys) with no value change,
|
|
@@ -294,6 +300,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
294
300
|
setCaretIndex((_a = event.currentTarget.selectionStart) !== null && _a !== void 0 ? _a : 0);
|
|
295
301
|
};
|
|
296
302
|
const resetComposer = () => {
|
|
303
|
+
stopTyping();
|
|
297
304
|
setValue("");
|
|
298
305
|
setListUploadFiles([]);
|
|
299
306
|
setPendingVideoUids([]);
|
|
@@ -345,18 +352,35 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
345
352
|
const handleFocus = () => {
|
|
346
353
|
setIsFocused(true);
|
|
347
354
|
closeMoreOption();
|
|
355
|
+
closeSticker();
|
|
348
356
|
emit("CONVERSATION_CHAT_SCROLL_TO_BOTTOM_IF_AT_BOTTOM");
|
|
349
357
|
};
|
|
350
358
|
const handleBlur = () => {
|
|
351
359
|
setIsFocused(false);
|
|
360
|
+
stopTyping();
|
|
352
361
|
};
|
|
353
362
|
const handleToggleMoreOption = () => {
|
|
354
363
|
var _a;
|
|
355
364
|
if (!isMoreOptionOpen) {
|
|
356
365
|
(_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
366
|
+
closeSticker();
|
|
357
367
|
}
|
|
358
368
|
toggleMoreOption();
|
|
359
369
|
};
|
|
370
|
+
const handleToggleSticker = () => {
|
|
371
|
+
var _a;
|
|
372
|
+
if (!isStickerOpen) {
|
|
373
|
+
(_a = textareaRef.current) === null || _a === void 0 ? void 0 : _a.blur();
|
|
374
|
+
closeMoreOption();
|
|
375
|
+
}
|
|
376
|
+
toggleSticker();
|
|
377
|
+
};
|
|
378
|
+
// Picker stays open after a send so several stickers can go out in a row.
|
|
379
|
+
const handleSelectSticker = (sticker) => {
|
|
380
|
+
if (!isConversationReady)
|
|
381
|
+
return;
|
|
382
|
+
void sendStickerMessage(sticker);
|
|
383
|
+
};
|
|
360
384
|
const insertNewline = (textarea) => {
|
|
361
385
|
const { selectionStart, selectionEnd } = textarea;
|
|
362
386
|
const nextValue = value.slice(0, selectionStart) + "\n" + value.slice(selectionEnd);
|
|
@@ -388,6 +412,7 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
388
412
|
if (conversationId && userID) {
|
|
389
413
|
setDraft(userID, conversationId, nextValue);
|
|
390
414
|
}
|
|
415
|
+
reportTyping(nextValue);
|
|
391
416
|
onInputChange === null || onInputChange === void 0 ? void 0 : onInputChange(nextValue);
|
|
392
417
|
const nextCaret = mentionTrigger.triggerStart + insertedText.length + 1;
|
|
393
418
|
// Update caretIndex synchronously (not deferred to the RAF below) so
|
|
@@ -461,8 +486,10 @@ const ConversationDetailInput = ({ placeholder, onSendMessage, onInputChange, mo
|
|
|
461
486
|
: "border-gray-50"), style: { minHeight: CONTAINER_MIN_HEIGHT }, children: _jsxs("div", { className: "flex min-w-0 flex-[1_0_0] items-center gap-2 self-stretch px-3 py-1", children: [onOpenBotMenu && (_jsx("button", { type: "button", onClick: onOpenBotMenu, "aria-label": t("thread_detail.bot.menu.open_menu"), "data-testid": "conversation-detail-open-bot-menu", className: "flex shrink-0 items-center justify-center", children: _jsx(Icon, { icon: "chat-suggest-o", size: 24, className: "text-gray-600" }) })), _jsx("textarea", { ref: textareaRef, value: value, rows: 1, onChange: (event) => {
|
|
462
487
|
var _a;
|
|
463
488
|
return handleChange(event.target.value, (_a = event.target.selectionStart) !== null && _a !== void 0 ? _a : event.target.value.length);
|
|
464
|
-
}, onSelect: handleSelectionChange, 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" }), isMentionMenuOpen && mentionMenuPosition && (_jsx(BizMentionMenu, { options: mentionOptions, selectedIndex: selectedMentionIndex, onSelect: selectMentionOption, onHoverIndex: setSelectedMentionIndex, position: mentionMenuPosition, limitReached: mentionLimitReached, limitMax: MAX_MENTIONED_USERS, visible: true }))
|
|
489
|
+
}, onSelect: handleSelectionChange, 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" }), isMentionMenuOpen && mentionMenuPosition && (_jsx(BizMentionMenu, { options: mentionOptions, selectedIndex: selectedMentionIndex, onSelect: selectMentionOption, onHoverIndex: setSelectedMentionIndex, position: mentionMenuPosition, limitReached: mentionLimitReached, limitMax: MAX_MENTIONED_USERS, visible: true })), showSticker && (_jsx("button", { type: "button", onClick: handleToggleSticker, "aria-label": t("thread_detail.sticker.toggle"), "aria-expanded": isStickerOpen, "data-testid": "conversation-detail-sticker-toggle", className: "flex shrink-0 items-center justify-center", children: _jsx(Icon, { icon: isStickerOpen ? "sticker-b" : "sticker-o", size: 24, className: isStickerOpen
|
|
490
|
+
? "text-[var(--dc-color-primary)]"
|
|
491
|
+
: "text-gray-600" }) }))] }) }), _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
|
|
465
492
|
? "bg-[var(--dc-color-primary)]"
|
|
466
|
-
: "bg-[var(--dc-color-primary)] opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(ConversationMoreOptionPanel, { disabled: !isConversationReady, options: moreOptions, 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(); } }))] }));
|
|
493
|
+
: "bg-[var(--dc-color-primary)] opacity-25"), children: _jsx(Icon, { icon: "send-b", size: 20, className: "text-white" }) }) })] }), isMoreOptionOpen && (_jsx(ConversationMoreOptionPanel, { disabled: !isConversationReady, options: moreOptions, 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(); } })), showSticker && isStickerOpen && (_jsx(StickerPicker, { onSelectSticker: handleSelectSticker }))] }));
|
|
467
494
|
};
|
|
468
495
|
export default ConversationDetailInput;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface TypingIndicatorBarProps {
|
|
2
|
+
conversationId?: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Overlays the bottom edge of the message list rather than taking layout
|
|
6
|
+
* space, so showing/hiding it never shifts the viewer's scroll position.
|
|
7
|
+
*/
|
|
8
|
+
declare const TypingIndicatorBar: ({ conversationId }: TypingIndicatorBarProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
+
export default TypingIndicatorBar;
|
|
10
|
+
//# sourceMappingURL=TypingIndicatorBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TypingIndicatorBar.d.ts","sourceRoot":"","sources":["../../../src/components/conversation-detail/TypingIndicatorBar.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAqBD;;;GAGG;AACH,QAAA,MAAM,kBAAkB,GAAI,oBAAoB,uBAAuB,mDAkCtE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import { useTypingState } from "../../hooks/message/useTypingStatus";
|
|
6
|
+
const TYPING_DOT_DELAYS_MS = [0, 150, 300];
|
|
7
|
+
// Sampled from the design: name + text share one gray, dots are lighter.
|
|
8
|
+
const TEXT_COLOR = "text-[#747B7E]";
|
|
9
|
+
const TypingDots = () => (_jsx("span", { "aria-hidden": "true", className: "ml-1 inline-flex items-end gap-[2px] pb-[4px]", children: TYPING_DOT_DELAYS_MS.map((delay) => (_jsx("span", { className: "h-[3px] w-[3px] animate-bounce rounded-full bg-[#8D8E90]", style: { animationDelay: `${delay}ms` } }, delay))) }));
|
|
10
|
+
/**
|
|
11
|
+
* Overlays the bottom edge of the message list rather than taking layout
|
|
12
|
+
* space, so showing/hiding it never shifts the viewer's scroll position.
|
|
13
|
+
*/
|
|
14
|
+
const TypingIndicatorBar = ({ conversationId }) => {
|
|
15
|
+
const { t } = useTranslation("conversation-inbox");
|
|
16
|
+
const { names, count } = useTypingState(conversationId);
|
|
17
|
+
if (count === 0)
|
|
18
|
+
return null;
|
|
19
|
+
// Names are rendered as JSX (not interpolated markup) so user-supplied
|
|
20
|
+
// nicknames can never be parsed as formatting tags.
|
|
21
|
+
const content = count > names.length ? (t("typing.many_label")) : (_jsxs(_Fragment, { children: [names.map((name, index) => (_jsxs(Fragment, { children: [index > 0 && ` ${t("typing.and")} `, _jsx("strong", { className: "font-semibold", children: name })] }, `${index}-${name}`))), " ", t("typing.suffix")] }));
|
|
22
|
+
return (_jsxs("div", { "data-testid": "conversation-typing-indicator", "data-cp-name": "TypingIndicatorBar", "aria-live": "polite", className: `pointer-events-none absolute inset-x-0 bottom-0 z-10 truncate bg-white/90 px-4 py-1 text-[13px] leading-[1.6] ${TEXT_COLOR}`, children: [content, _jsx(TypingDots, {})] }));
|
|
23
|
+
};
|
|
24
|
+
export default TypingIndicatorBar;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type StickerTab } from "./constants";
|
|
2
|
+
interface StickerCategoryTabsProps {
|
|
3
|
+
tabs: StickerTab[];
|
|
4
|
+
activeTabId: string | null;
|
|
5
|
+
onSelectTab: (tabId: string) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const StickerCategoryTabs: ({ tabs, activeTabId, onSelectTab, }: StickerCategoryTabsProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default StickerCategoryTabs;
|
|
9
|
+
//# sourceMappingURL=StickerCategoryTabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StickerCategoryTabs.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/StickerCategoryTabs.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAErE,UAAU,wBAAwB;IAChC,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAID,QAAA,MAAM,mBAAmB,GAAI,qCAI1B,wBAAwB,4CAiE1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { Icon } from "../../../components/icon";
|
|
6
|
+
import { RECENT_STICKER_TAB_ID } from "./constants";
|
|
7
|
+
const TAB_ROW_PADDING = 12;
|
|
8
|
+
const StickerCategoryTabs = ({ tabs, activeTabId, onSelectTab, }) => {
|
|
9
|
+
const rowRef = useRef(null);
|
|
10
|
+
const tabRefs = useRef(new Map());
|
|
11
|
+
// Scrolls only the tab row: scrollIntoView would also drag the host page
|
|
12
|
+
// when the picker sits partly off-screen.
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const row = rowRef.current;
|
|
15
|
+
const tab = activeTabId ? tabRefs.current.get(activeTabId) : undefined;
|
|
16
|
+
if (!row || !tab)
|
|
17
|
+
return;
|
|
18
|
+
const tabRight = tab.offsetLeft + tab.offsetWidth;
|
|
19
|
+
if (tab.offsetLeft < row.scrollLeft) {
|
|
20
|
+
row.scrollLeft = tab.offsetLeft - TAB_ROW_PADDING;
|
|
21
|
+
}
|
|
22
|
+
else if (tabRight > row.scrollLeft + row.clientWidth) {
|
|
23
|
+
row.scrollLeft = tabRight - row.clientWidth + TAB_ROW_PADDING;
|
|
24
|
+
}
|
|
25
|
+
}, [activeTabId]);
|
|
26
|
+
return (_jsx("div", { ref: rowRef, role: "tablist", "data-testid": "sticker-picker-tabs", className: "relative flex h-[52px] shrink-0 items-center gap-3 overflow-x-auto px-3 py-2 [scrollbar-width:none]", children: tabs.map((tab) => {
|
|
27
|
+
const isActive = tab.id === activeTabId;
|
|
28
|
+
return (_jsx("button", { ref: (element) => {
|
|
29
|
+
if (element)
|
|
30
|
+
tabRefs.current.set(tab.id, element);
|
|
31
|
+
else
|
|
32
|
+
tabRefs.current.delete(tab.id);
|
|
33
|
+
}, type: "button", role: "tab", "aria-selected": isActive, "aria-label": tab.name, "data-testid": "sticker-picker-tab", "data-tab-id": tab.id, onClick: () => onSelectTab(tab.id), className: clsx("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border p-1", isActive
|
|
34
|
+
? "border-[#505759] bg-[#393E40]/10"
|
|
35
|
+
: "border-transparent"), children: tab.id === RECENT_STICKER_TAB_ID ? (_jsx(Icon, { icon: "time-circle-b", size: 24, className: "text-[#393E40]" })) : tab.iconUrl ? (_jsx("img", { src: tab.iconUrl, alt: "", className: "h-7 w-7 object-contain" })) : (_jsx("span", { className: "h-7 w-7 rounded bg-gray-100" })) }, tab.id));
|
|
36
|
+
}) }));
|
|
37
|
+
};
|
|
38
|
+
export default StickerCategoryTabs;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StickerItem } from "../../../types/sticker";
|
|
2
|
+
interface StickerPickerProps {
|
|
3
|
+
onSelectSticker: (sticker: StickerItem) => void;
|
|
4
|
+
}
|
|
5
|
+
declare const StickerPicker: ({ onSelectSticker }: StickerPickerProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export default StickerPicker;
|
|
7
|
+
//# sourceMappingURL=StickerPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StickerPicker.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/StickerPicker.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAKnD,UAAU,kBAAkB;IAC1B,eAAe,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAMD,QAAA,MAAM,aAAa,GAAI,qBAAqB,kBAAkB,4CA0J7D,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
import { useStickerCategories, useStickerHistory, } from "../../../hooks/sticker/useStickers";
|
|
6
|
+
import StickerCategoryTabs from "./StickerCategoryTabs";
|
|
7
|
+
import StickerSection from "./StickerSection";
|
|
8
|
+
import { RECENT_STICKER_TAB_ID } from "./constants";
|
|
9
|
+
// Long enough for a smooth scrollTo to finish, so the scroll-spy doesn't
|
|
10
|
+
// flick the active tab through every section passed on the way.
|
|
11
|
+
const TAB_SCROLL_LOCK_MS = 600;
|
|
12
|
+
const StickerPicker = ({ onSelectSticker }) => {
|
|
13
|
+
const { t } = useTranslation("conversation-inbox");
|
|
14
|
+
const categoriesQuery = useStickerCategories();
|
|
15
|
+
const historyQuery = useStickerHistory();
|
|
16
|
+
const recentStickers = useMemo(() => { var _a; return (_a = historyQuery.data) !== null && _a !== void 0 ? _a : []; }, [historyQuery.data]);
|
|
17
|
+
const tabs = useMemo(() => {
|
|
18
|
+
var _a;
|
|
19
|
+
const categoryTabs = ((_a = categoriesQuery.data) !== null && _a !== void 0 ? _a : []).map((category) => {
|
|
20
|
+
var _a;
|
|
21
|
+
return ({
|
|
22
|
+
id: category.id,
|
|
23
|
+
name: category.name,
|
|
24
|
+
iconUrl: (_a = category.icon) === null || _a === void 0 ? void 0 : _a.fullUrl,
|
|
25
|
+
stickerCount: category.stickerCount,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
if (recentStickers.length === 0)
|
|
29
|
+
return categoryTabs;
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
id: RECENT_STICKER_TAB_ID,
|
|
33
|
+
name: t("thread_detail.sticker.recent"),
|
|
34
|
+
stickerCount: recentStickers.length,
|
|
35
|
+
},
|
|
36
|
+
...categoryTabs,
|
|
37
|
+
];
|
|
38
|
+
}, [categoriesQuery.data, recentStickers.length, t]);
|
|
39
|
+
// Wait for history too: a Recent tab arriving after the categories would
|
|
40
|
+
// shove every section down and flip the default tab under the user.
|
|
41
|
+
const isLoading = categoriesQuery.isLoading ||
|
|
42
|
+
(historyQuery.isLoading && !historyQuery.isError);
|
|
43
|
+
const [activeTabId, setActiveTabId] = useState(null);
|
|
44
|
+
const [scrollRoot, setScrollRoot] = useState(null);
|
|
45
|
+
const sectionRefs = useRef(new Map());
|
|
46
|
+
const isTabScrollLockedRef = useRef(false);
|
|
47
|
+
const lockTimerRef = useRef(null);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
if (isLoading)
|
|
51
|
+
return;
|
|
52
|
+
if (!activeTabId || !tabs.some((tab) => tab.id === activeTabId)) {
|
|
53
|
+
setActiveTabId((_b = (_a = tabs[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null);
|
|
54
|
+
}
|
|
55
|
+
}, [activeTabId, isLoading, tabs]);
|
|
56
|
+
useEffect(() => () => {
|
|
57
|
+
if (lockTimerRef.current)
|
|
58
|
+
clearTimeout(lockTimerRef.current);
|
|
59
|
+
}, []);
|
|
60
|
+
const registerSectionRef = useCallback((tabId, element) => {
|
|
61
|
+
if (element)
|
|
62
|
+
sectionRefs.current.set(tabId, element);
|
|
63
|
+
else
|
|
64
|
+
sectionRefs.current.delete(tabId);
|
|
65
|
+
}, []);
|
|
66
|
+
const handleScroll = useCallback(() => {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
if (!scrollRoot || isTabScrollLockedRef.current)
|
|
69
|
+
return;
|
|
70
|
+
const { scrollTop, clientHeight, scrollHeight } = scrollRoot;
|
|
71
|
+
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
|
72
|
+
let nextTabId = (_b = (_a = tabs[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : null;
|
|
73
|
+
for (const tab of tabs) {
|
|
74
|
+
const section = sectionRefs.current.get(tab.id);
|
|
75
|
+
if (section && section.offsetTop <= scrollTop + 1)
|
|
76
|
+
nextTabId = tab.id;
|
|
77
|
+
}
|
|
78
|
+
if (isAtBottom && tabs.length > 0)
|
|
79
|
+
nextTabId = tabs[tabs.length - 1].id;
|
|
80
|
+
setActiveTabId(nextTabId);
|
|
81
|
+
}, [scrollRoot, tabs]);
|
|
82
|
+
const handleSelectTab = useCallback((tabId) => {
|
|
83
|
+
setActiveTabId(tabId);
|
|
84
|
+
const section = sectionRefs.current.get(tabId);
|
|
85
|
+
if (!scrollRoot || !section)
|
|
86
|
+
return;
|
|
87
|
+
isTabScrollLockedRef.current = true;
|
|
88
|
+
if (lockTimerRef.current)
|
|
89
|
+
clearTimeout(lockTimerRef.current);
|
|
90
|
+
lockTimerRef.current = setTimeout(() => {
|
|
91
|
+
isTabScrollLockedRef.current = false;
|
|
92
|
+
}, TAB_SCROLL_LOCK_MS);
|
|
93
|
+
if (typeof scrollRoot.scrollTo === "function") {
|
|
94
|
+
scrollRoot.scrollTo({ top: section.offsetTop, behavior: "smooth" });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
scrollRoot.scrollTop = section.offsetTop;
|
|
98
|
+
}
|
|
99
|
+
}, [scrollRoot]);
|
|
100
|
+
const renderBody = () => {
|
|
101
|
+
if (isLoading) {
|
|
102
|
+
return (_jsx("div", { className: "grid grid-cols-4 gap-2 px-3 py-3", children: Array.from({ length: 8 }, (_, index) => (_jsx("div", { "data-testid": "sticker-picker-skeleton", className: "aspect-square w-full animate-pulse rounded-lg bg-gray-100" }, index))) }));
|
|
103
|
+
}
|
|
104
|
+
if (categoriesQuery.isError || tabs.length === 0) {
|
|
105
|
+
return (_jsx("p", { className: "px-3 py-6 text-center text-sm text-gray-500", children: categoriesQuery.isError
|
|
106
|
+
? t("thread_detail.sticker.load_failed")
|
|
107
|
+
: t("thread_detail.sticker.empty") }));
|
|
108
|
+
}
|
|
109
|
+
return tabs.map((tab) => (_jsx(StickerSection, { tab: tab, recentStickers: recentStickers, scrollRoot: scrollRoot, registerRef: registerSectionRef, onSelectSticker: onSelectSticker }, tab.id)));
|
|
110
|
+
};
|
|
111
|
+
return (_jsxs("div", { "data-testid": "sticker-picker", "data-cp-name": "StickerPicker", className: "flex h-[312px] flex-col bg-white", children: [!isLoading && tabs.length > 0 && (_jsx(StickerCategoryTabs, { tabs: tabs, activeTabId: activeTabId, onSelectTab: handleSelectTab })), _jsx("div", { ref: setScrollRoot, onScroll: handleScroll, "data-testid": "sticker-picker-scroll", className: "relative min-h-0 flex-1 overflow-y-auto", children: renderBody() })] }));
|
|
112
|
+
};
|
|
113
|
+
export default StickerPicker;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StickerItem } from "../../../types/sticker";
|
|
2
|
+
import { type StickerTab } from "./constants";
|
|
3
|
+
interface StickerSectionProps {
|
|
4
|
+
tab: StickerTab;
|
|
5
|
+
recentStickers: StickerItem[];
|
|
6
|
+
scrollRoot: HTMLElement | null;
|
|
7
|
+
registerRef: (tabId: string, element: HTMLElement | null) => void;
|
|
8
|
+
onSelectSticker: (sticker: StickerItem) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const StickerSection: ({ tab, recentStickers, scrollRoot, registerRef, onSelectSticker, }: StickerSectionProps) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export default StickerSection;
|
|
12
|
+
//# sourceMappingURL=StickerSection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StickerSection.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/StickerSection.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAIrE,UAAU,mBAAmB;IAC3B,GAAG,EAAE,UAAU,CAAC;IAChB,cAAc,EAAE,WAAW,EAAE,CAAC;IAC9B,UAAU,EAAE,WAAW,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAClE,eAAe,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CACjD;AAyCD,QAAA,MAAM,cAAc,GAAI,oEAMrB,mBAAmB,4CAiErB,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import { images } from "../../../constants/images";
|
|
6
|
+
import { useStickersByCategory } from "../../../hooks/sticker/useStickers";
|
|
7
|
+
import { RECENT_STICKER_TAB_ID } from "./constants";
|
|
8
|
+
const PLACEHOLDER_CELL_COUNT = 4;
|
|
9
|
+
const StickerCell = ({ sticker, onSelect, }) => {
|
|
10
|
+
var _a;
|
|
11
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
12
|
+
const [hasError, setHasError] = useState(false);
|
|
13
|
+
return (_jsx("button", { type: "button", "aria-label": sticker.name, "data-testid": "sticker-picker-item", onClick: () => onSelect(sticker), className: clsx("relative aspect-square w-full rounded-lg", !isLoaded && "animate-pulse bg-gray-100"), children: _jsx("img", { src: hasError ? images.stickerInvalid : (_a = sticker.file) === null || _a === void 0 ? void 0 : _a.fullUrl, alt: "", loading: "lazy", onLoad: () => setIsLoaded(true), onError: () => {
|
|
14
|
+
setHasError(true);
|
|
15
|
+
setIsLoaded(true);
|
|
16
|
+
}, className: clsx("h-full w-full object-contain", !isLoaded && "opacity-0") }) }));
|
|
17
|
+
};
|
|
18
|
+
const StickerSection = ({ tab, recentStickers, scrollRoot, registerRef, onSelectSticker, }) => {
|
|
19
|
+
const sectionRef = useRef(null);
|
|
20
|
+
const isRecent = tab.id === RECENT_STICKER_TAB_ID;
|
|
21
|
+
const [hasBeenVisible, setHasBeenVisible] = useState(() => typeof IntersectionObserver === "undefined");
|
|
22
|
+
// Categories load on first approach, like chat-mobile, so opening the
|
|
23
|
+
// picker doesn't fire one request per category up front.
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const element = sectionRef.current;
|
|
26
|
+
if (hasBeenVisible || isRecent || !element || !scrollRoot)
|
|
27
|
+
return;
|
|
28
|
+
const observer = new IntersectionObserver((entries) => {
|
|
29
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
30
|
+
setHasBeenVisible(true);
|
|
31
|
+
}
|
|
32
|
+
}, { root: scrollRoot, rootMargin: "200px 0px" });
|
|
33
|
+
observer.observe(element);
|
|
34
|
+
return () => observer.disconnect();
|
|
35
|
+
}, [hasBeenVisible, isRecent, scrollRoot]);
|
|
36
|
+
const { data: categoryStickers, isError } = useStickersByCategory(tab.id, !isRecent && hasBeenVisible);
|
|
37
|
+
const stickers = isRecent ? recentStickers : categoryStickers;
|
|
38
|
+
const placeholderCount = tab.stickerCount || PLACEHOLDER_CELL_COUNT;
|
|
39
|
+
return (_jsxs("div", { ref: (element) => {
|
|
40
|
+
sectionRef.current = element;
|
|
41
|
+
registerRef(tab.id, element);
|
|
42
|
+
}, "data-testid": "sticker-picker-section", "data-tab-id": tab.id, children: [_jsx("p", { className: "px-3 py-1 text-[14px] font-bold leading-[22px] text-[#747B7E]", children: tab.name }), isError ? null : (_jsx("div", { className: "grid grid-cols-4 gap-2 px-3 pb-3", children: stickers
|
|
43
|
+
? stickers.map((sticker) => (_jsx(StickerCell, { sticker: sticker, onSelect: onSelectSticker }, sticker.id)))
|
|
44
|
+
: Array.from({ length: placeholderCount }, (_, index) => (_jsx("div", { "data-testid": "sticker-picker-skeleton", className: "aspect-square w-full animate-pulse rounded-lg bg-gray-100" }, index))) }))] }));
|
|
45
|
+
};
|
|
46
|
+
export default StickerSection;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../src/components/conversation-detail/sticker-picker/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,WAAW,CAAC;AAE9C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RECENT_STICKER_TAB_ID = "recent";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StickerMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/StickerMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,UAAU,uBAAuB;IAC/B,OAAO,EAAE,WAAW,CAAC;CACtB;AAID,QAAA,MAAM,kBAAkB,GAAI,aAAa,uBAAuB,
|
|
1
|
+
{"version":3,"file":"StickerMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/StickerMessage.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,UAAU,uBAAuB;IAC/B,OAAO,EAAE,WAAW,CAAC;CACtB;AAID,QAAA,MAAM,kBAAkB,GAAI,aAAa,uBAAuB,4CAe/D,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import clsx from "clsx";
|
|
4
|
-
import {
|
|
4
|
+
import { images } from "../../../constants/images";
|
|
5
5
|
const STICKER_SIZE_CLASS = "h-[120px] w-[120px]";
|
|
6
6
|
const StickerMessageItem = ({ message }) => {
|
|
7
7
|
var _a;
|
|
8
8
|
const [hasLoadError, setHasLoadError] = useState(false);
|
|
9
9
|
const stickerUrl = (_a = message.stickerElem) === null || _a === void 0 ? void 0 : _a.content;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
return (_jsx("img", { src: stickerUrl, alt: "", className: clsx("block object-cover", STICKER_SIZE_CLASS), onError: () => setHasLoadError(true) }));
|
|
10
|
+
const isInvalid = !stickerUrl || hasLoadError;
|
|
11
|
+
return (_jsx("img", { src: isInvalid ? images.stickerInvalid : stickerUrl, alt: "", "data-testid": "sticker-message-image", "data-invalid": isInvalid || undefined, className: clsx("block object-contain", STICKER_SIZE_CLASS), onError: () => setHasLoadError(true) }));
|
|
14
12
|
};
|
|
15
13
|
export default StickerMessageItem;
|
|
@@ -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,8BAA8B,CAAC;AAKxE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,
|
|
1
|
+
{"version":3,"file":"ReplyPreviewMedia.d.ts","sourceRoot":"","sources":["../../../../src/components/message/reply/ReplyPreviewMedia.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,8BAA8B,CAAC;AAKxE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,QAAA,MAAM,iBAAiB,GAAI,aAAa,sBAAsB,mDAgE7D,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
|
|
@@ -4,7 +4,7 @@ import { documentIcon } from "../../../assets/svg";
|
|
|
4
4
|
import { Icon } from "../../../components/icon";
|
|
5
5
|
import { parseUrlMetadata } from "../../../utils/common";
|
|
6
6
|
const ReplyPreviewMedia = ({ message }) => {
|
|
7
|
-
var _a, _b, _c, _d, _e, _f;
|
|
7
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
8
8
|
if (message.contentType === MessageType.FileMessage) {
|
|
9
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 }));
|
|
10
10
|
}
|
|
@@ -19,8 +19,14 @@ const ReplyPreviewMedia = ({ message }) => {
|
|
|
19
19
|
const posterUrl = (_e = message.videoElem) === null || _e === void 0 ? void 0 : _e.snapshotUrl;
|
|
20
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" }) }) })] }));
|
|
21
21
|
}
|
|
22
|
+
if (message.contentType === MessageType.StickerMessage) {
|
|
23
|
+
const stickerUrl = (_f = message.stickerElem) === null || _f === void 0 ? void 0 : _f.content;
|
|
24
|
+
if (!stickerUrl)
|
|
25
|
+
return null;
|
|
26
|
+
return (_jsx("img", { src: stickerUrl, alt: "", className: "h-[42px] w-[42px] shrink-0 object-contain" }));
|
|
27
|
+
}
|
|
22
28
|
if (message.contentType === MessageType.UrlTextMessage) {
|
|
23
|
-
const imageUrl = (
|
|
29
|
+
const imageUrl = (_g = parseUrlMetadata(message.ex)) === null || _g === void 0 ? void 0 : _g.image;
|
|
24
30
|
if (!imageUrl)
|
|
25
31
|
return null;
|
|
26
32
|
return (_jsx("img", { src: imageUrl, alt: "", className: "h-[42px] w-[42px] shrink-0 rounded-lg object-cover" }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/constants/images.ts"],"names":[],"mappings":"AAwBA,eAAO,MAAM,MAAM;;;;;;;CAOlB,CAAC"}
|
package/dist/constants/images.js
CHANGED
|
@@ -4,10 +4,12 @@ const imageFailed = new URL("../assets/images/imageFailed.png", import.meta.url)
|
|
|
4
4
|
const emptyResources = new URL("../assets/images/emptyResources.png", import.meta.url).href;
|
|
5
5
|
const muteNotificationWarning = new URL("../assets/images/muteNotificationWarning.png", import.meta.url).href;
|
|
6
6
|
const searchEmpty = new URL("../assets/images/searchEmpty.png", import.meta.url).href;
|
|
7
|
+
const stickerInvalid = new URL("../assets/images/stickerInvalid.png", import.meta.url).href;
|
|
7
8
|
export const images = {
|
|
8
9
|
conversationBg,
|
|
9
10
|
imageFailed,
|
|
10
11
|
emptyResources,
|
|
11
12
|
muteNotificationWarning,
|
|
12
13
|
searchEmpty,
|
|
14
|
+
stickerInvalid,
|
|
13
15
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatContext.tsx"],"names":[],"mappings":"AAEA,OAAO,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,iBAAiB,EAGlB,MAAM,cAAc,CAAC;AAUtB,eAAO,MAAM,WAAW,0CAStB,CAAC;AAEH,eAAO,MAAM,cAAc,uBAAgC,CAAC;AAc5D,eAAO,MAAM,YAAY,GAAI,sIAS1B,iBAAiB,
|
|
1
|
+
{"version":3,"file":"ChatContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatContext.tsx"],"names":[],"mappings":"AAEA,OAAO,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,iBAAiB,EAGlB,MAAM,cAAc,CAAC;AAUtB,eAAO,MAAM,WAAW,0CAStB,CAAC;AAEH,eAAO,MAAM,cAAc,uBAAgC,CAAC;AAc5D,eAAO,MAAM,YAAY,GAAI,sIAS1B,iBAAiB,4CA2JnB,CAAC"}
|
|
@@ -141,5 +141,5 @@ export const ChatProvider = ({ children, config, renderCustomMessage, onNavigate
|
|
|
141
141
|
useUrlState,
|
|
142
142
|
allowPinMessageWithoutPermission,
|
|
143
143
|
renderGroupAccessModal,
|
|
144
|
-
}, children: _jsx(ConfigProvider, { getPopupContainer: () => { var _a; return (_a = rootRef.current) !== null && _a !== void 0 ? _a : document.body; }, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx("div", { ref: rootRef, style: themeStyle, className: "contents", children: _jsx(MainLayout, { children: children }) }) }) }) }));
|
|
144
|
+
}, children: _jsx(ConfigProvider, { getPopupContainer: () => { var _a; return (_a = rootRef.current) !== null && _a !== void 0 ? _a : document.body; }, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx("div", { ref: rootRef, style: themeStyle, className: "contents", "data-dchat-root": "", children: _jsx(MainLayout, { children: children }) }) }) }) }));
|
|
145
145
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useGlobalEvent.d.ts","sourceRoot":"","sources":["../../../src/hooks/global/useGlobalEvent.ts"],"names":[],"mappings":"AA+CA,eAAO,MAAM,cAAc,YA+jB1B,CAAC"}
|
|
@@ -6,6 +6,7 @@ import { BusinessNotificationType, ConnectStatus, CustomType, SyncStatus, } from
|
|
|
6
6
|
import { pushNewMessage, updateOneMessage } from "../../hooks/message/useMessage";
|
|
7
7
|
import { fetchPinnedMessages } from "../../hooks/message/usePinnedMessages";
|
|
8
8
|
import { applyRevokedMessage } from "../../hooks/message/useRevokeMessage";
|
|
9
|
+
import { handleInputStatusChanged, removeTyperOnNewMessage, } from "../../hooks/message/useTypingStatus";
|
|
9
10
|
import { useChatContext } from "../../context/ChatContext";
|
|
10
11
|
import { useChatNavigate } from "../../hooks/conversation/useChatNavigate";
|
|
11
12
|
import useConversationStore from "../../store/conversation";
|
|
@@ -152,7 +153,13 @@ export const useGlobalEvent = () => {
|
|
|
152
153
|
};
|
|
153
154
|
const newMessageHandler = ({ data }) => {
|
|
154
155
|
var _a;
|
|
155
|
-
(_a = data === null || data === void 0 ? void 0 : data.map) === null || _a === void 0 ? void 0 : _a.call(data, (message) =>
|
|
156
|
+
(_a = data === null || data === void 0 ? void 0 : data.map) === null || _a === void 0 ? void 0 : _a.call(data, (message) => {
|
|
157
|
+
removeTyperOnNewMessage(message);
|
|
158
|
+
handleNewMessage(message);
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
const inputStatusChangedHandler = ({ data, }) => {
|
|
162
|
+
handleInputStatusChanged(data);
|
|
156
163
|
};
|
|
157
164
|
const userTokenHandler = () => {
|
|
158
165
|
refetchChatToken(undefined, {
|
|
@@ -387,6 +394,7 @@ export const useGlobalEvent = () => {
|
|
|
387
394
|
DChatSDK.on(CbEvents.OnRecvNewMessages, newMessageHandler);
|
|
388
395
|
DChatSDK.on(CbEvents.OnNewRecvMessageRevoked, revokedMessageHandler);
|
|
389
396
|
DChatSDK.on(CbEvents.OnRecvMessagePinned, messagePinnedHandler);
|
|
397
|
+
DChatSDK.on(CbEvents.OnConversationUserInputStatusChanged, inputStatusChangedHandler);
|
|
390
398
|
// conversation
|
|
391
399
|
DChatSDK.on(CbEvents.OnConversationChanged, conversationChangeHandler);
|
|
392
400
|
DChatSDK.on(CbEvents.OnNewConversation, newConversationHandler);
|
|
@@ -421,6 +429,7 @@ export const useGlobalEvent = () => {
|
|
|
421
429
|
DChatSDK.off(CbEvents.OnRecvNewMessages, newMessageHandler);
|
|
422
430
|
DChatSDK.off(CbEvents.OnNewRecvMessageRevoked, revokedMessageHandler);
|
|
423
431
|
DChatSDK.off(CbEvents.OnRecvMessagePinned, messagePinnedHandler);
|
|
432
|
+
DChatSDK.off(CbEvents.OnConversationUserInputStatusChanged, inputStatusChangedHandler);
|
|
424
433
|
// conversation
|
|
425
434
|
DChatSDK.off(CbEvents.OnConversationChanged, conversationChangeHandler);
|
|
426
435
|
DChatSDK.off(CbEvents.OnNewConversation, newConversationHandler);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports the viewer's own composer activity for `conversationId` so other
|
|
3
|
+
* participants see a typing indicator. `reportActivity` on every edit,
|
|
4
|
+
* `stopTyping` when the composer is cleared, blurred or the message is sent.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useReportTypingState: (conversationId: string) => {
|
|
7
|
+
reportActivity: (composerValue: string) => void;
|
|
8
|
+
stopTyping: () => void;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=useReportTypingState.d.ts.map
|