UNPKG

@sendbird/uikit-react

Version:

Sendbird UIKit for React: A feature-rich and customizable chat UI kit with messaging, channel management, and user authentication.

312 lines (307 loc) 20.6 kB
import { e as __spreadArray, _ as __assign } from './bundle-Bpofr334.js'; import React__default, { useState, useEffect, useRef, useCallback, useLayoutEffect, useMemo } from 'react'; import { u as useTypingLifecycle } from './bundle-AsQ1wnFm.js'; import { f as format } from './bundle-DyosazG-.js'; import { u as useLocalization } from './bundle-Cdqsdoa8.js'; import { M as MAX_USER_MENTION_COUNT, a as MAX_USER_SUGGESTION_COUNT } from './bundle-CFc2hy8g.js'; import { b as isDisabledBecauseFrozen, d as isDisabledBecauseMuted } from './bundle-DiO7lolz.js'; import { useDirtyGetMentions } from '../Message/hooks/useDirtyGetMentions.js'; import { p as getSuggestedReplies } from './bundle-BZSLsKkw.js'; import DateSeparator from '../ui/DateSeparator.js'; import { L as Label, a as LabelColors, c as LabelTypography } from './bundle-Cdplrrlw.js'; import { M as MessageInput } from './bundle-BCjR1Qiq.js'; import { M as MessageInputKeys } from './bundle-BEPoP7sp.js'; import { MessageContent } from '../ui/MessageContent.js'; import SuggestedReplies from '../GroupChannel/components/SuggestedReplies.js'; import { S as SuggestedMentionListView } from './bundle-CnFrQOtC.js'; import { d as deleteNullish, c as classnames } from './bundle-DX6fRIJl.js'; import { u as useSendbird } from './bundle-4clodtJA.js'; import { C as Colors, c as changeColorToClassName } from './bundle-C1npFBfj.js'; var useDidMountEffect = function (func, deps) { var _a = useState(false), didMount = _a[0], setDidMount = _a[1]; useEffect(function () { if (didMount) { func(); } else { setDidMount(true); } }, deps); }; var NewMessageIndicator = function (_a) { var _b = _a.children, children = _b === void 0 ? undefined : _b, _c = _a.className, className = _c === void 0 ? '' : _c, onVisibilityChange = _a.onVisibilityChange, _d = _a.separatorColor, separatorColor = _d === void 0 ? Colors.PRIMARY : _d; var separatorRef = useRef(null); var handleVisibilityChange = useCallback(function (isVisible) { onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isVisible); }, [onVisibilityChange]); useLayoutEffect(function () { var element = separatorRef.current; if (!element || !onVisibilityChange) return; var observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { var visible = entry.isIntersecting; handleVisibilityChange(visible); }); }, { threshold: 1.0, rootMargin: '0px', root: null, }); observer.observe(element); return function () { observer.disconnect(); }; }, [handleVisibilityChange, onVisibilityChange]); return (React__default.createElement("div", { ref: separatorRef, className: __spreadArray(__spreadArray([], (Array.isArray(className) ? className : [className]), true), [ 'sendbird-separator', ], false).join(' ') }, React__default.createElement("div", { className: ['sendbird-separator__left', "".concat(changeColorToClassName(separatorColor), "--background-color")].join(' ') }), React__default.createElement("div", { className: "sendbird-separator__text" }, children || (React__default.createElement(Label, { type: LabelTypography.CAPTION_2, color: LabelColors.PRIMARY }, "New Messages"))), React__default.createElement("div", { className: ['sendbird-separator__right', "".concat(changeColorToClassName(separatorColor), "--background-color")].join(' ') }))); }; // TODO: Refactor this component, is too complex now var MessageView = function (props) { var _a, _b; var // MessageProps message = props.message, children = props.children, hasSeparator = props.hasSeparator, hasNewMessageSeparator = props.hasNewMessageSeparator, chainTop = props.chainTop, chainBottom = props.chainBottom, handleScroll = props.handleScroll, onNewMessageSeparatorVisibilityChange = props.onNewMessageSeparatorVisibilityChange, scrollMessageOverflowToTop = props.scrollMessageOverflowToTop, // MessageViewProps channel = props.channel, emojiContainer = props.emojiContainer, editInputDisabled = props.editInputDisabled, shouldRenderSuggestedReplies = props.shouldRenderSuggestedReplies, isReactionEnabled = props.isReactionEnabled, replyType = props.replyType, threadReplySelectType = props.threadReplySelectType, nicknamesMap = props.nicknamesMap, scrollToMessage = props.scrollToMessage, toggleReaction = props.toggleReaction, setQuoteMessage = props.setQuoteMessage, onQuoteMessageClick = props.onQuoteMessageClick, onReplyInThreadClick = props.onReplyInThreadClick, onBeforeDownloadFileMessage = props.onBeforeDownloadFileMessage, sendUserMessage = props.sendUserMessage, updateUserMessage = props.updateUserMessage, resendMessage = props.resendMessage, deleteMessage = props.deleteMessage, markAsUnread = props.markAsUnread, setAnimatedMessageId = props.setAnimatedMessageId, animatedMessageId = props.animatedMessageId, onMessageAnimated = props.onMessageAnimated, _c = props.usedInLegacy, usedInLegacy = _c === void 0 ? true : _c, newMessageIds = props.newMessageIds, setNewMessageIds = props.setNewMessageIds; var _d = deleteNullish(props), renderUserMentionItem = _d.renderUserMentionItem, renderMessage = _d.renderMessage, _e = _d.renderMessageContent, renderMessageContent = _e === void 0 ? function (props) { return React__default.createElement(MessageContent, __assign({}, props)); } : _e, _f = _d.renderSuggestedReplies, renderSuggestedReplies = _f === void 0 ? function (props) { return React__default.createElement(SuggestedReplies, __assign({}, props)); } : _f, renderCustomSeparator = _d.renderCustomSeparator, renderEditInput = _d.renderEditInput, renderFileViewer = _d.renderFileViewer, renderRemoveMessageModal = _d.renderRemoveMessageModal, filterEmojiCategoryIds = _d.filterEmojiCategoryIds; var _g = useLocalization(), dateLocale = _g.dateLocale, stringSet = _g.stringSet; var state = useSendbird().state; var _h = state.config, userId = _h.userId, isOnline = _h.isOnline, userMention = _h.userMention, logger = _h.logger, groupChannel = _h.groupChannel; var maxUserMentionCount = (userMention === null || userMention === void 0 ? void 0 : userMention.maxMentionCount) || MAX_USER_MENTION_COUNT; var maxUserSuggestionCount = (userMention === null || userMention === void 0 ? void 0 : userMention.maxSuggestionCount) || MAX_USER_SUGGESTION_COUNT; var _j = useState(false), showEdit = _j[0], setShowEdit = _j[1]; var _k = useState(false), showRemove = _k[0], setShowRemove = _k[1]; var _l = useState(false), showFileViewer = _l[0], setShowFileViewer = _l[1]; // isAnimated state removed — animation now driven by animatedMessageId + onAnimationEnd var _m = useState(''), mentionNickname = _m[0], setMentionNickname = _m[1]; var _o = useState([]), mentionedUsers = _o[0], setMentionedUsers = _o[1]; var _p = useState([]), mentionedUserIds = _p[0], setMentionedUserIds = _p[1]; var _q = useState(null), messageInputEvent = _q[0], setMessageInputEvent = _q[1]; var _r = useState(null), selectedUser = _r[0], setSelectedUser = _r[1]; var _s = useState([]), mentionSuggestedUsers = _s[0], setMentionSuggestedUsers = _s[1]; var editMessageInputRef = useRef(null); var messageScrollRef = useRef(null); var displaySuggestedMentionList = isOnline && groupChannel.enableMention && mentionNickname.length > 0 && !isDisabledBecauseFrozen(channel) && !isDisabledBecauseMuted(channel); var mentionNodes = useDirtyGetMentions({ ref: editMessageInputRef }, { logger: logger }); var ableMention = (mentionNodes === null || mentionNodes === void 0 ? void 0 : mentionNodes.length) < maxUserMentionCount; useEffect(function () { setMentionedUsers(mentionedUsers.filter(function (_a) { var userId = _a.userId; var i = mentionedUserIds.indexOf(userId); if (i < 0) { return false; } else { mentionedUserIds.splice(i, 1); return true; } })); }, [mentionedUserIds]); var _t = useTypingLifecycle(channel, showEdit), startTyping = _t.startTyping, stopTyping = _t.stopTyping; // Side effect: scroll position update when showEdit is toggled or reactions updated useDidMountEffect(function () { handleScroll === null || handleScroll === void 0 ? void 0 : handleScroll(); }, [showEdit, (_a = message === null || message === void 0 ? void 0 : message.reactions) === null || _a === void 0 ? void 0 : _a.length]); // Side effect: scroll position update when message updated useDidMountEffect(function () { handleScroll === null || handleScroll === void 0 ? void 0 : handleScroll(true); }, [message === null || message === void 0 ? void 0 : message.updatedAt, message === null || message === void 0 ? void 0 : message.message]); // Side effect: scroll position update when suggested replies are rendered or hidden var prevShouldRenderSuggestedReplies = useRef(shouldRenderSuggestedReplies); useEffect(function () { if (prevShouldRenderSuggestedReplies.current !== shouldRenderSuggestedReplies) { handleScroll === null || handleScroll === void 0 ? void 0 : handleScroll(); } else { prevShouldRenderSuggestedReplies.current = shouldRenderSuggestedReplies; } }, [shouldRenderSuggestedReplies]); useLayoutEffect(function () { // Keep the scrollBottom value after fetching new message list (but GroupChannel module is not needed.) if (usedInLegacy) handleScroll === null || handleScroll === void 0 ? void 0 : handleScroll(true); }, []); // Animation: once triggered, protect with local state until CSS animation completes var _u = useState(false), showBounce = _u[0], setShowBounce = _u[1]; var isAnimationTarget = animatedMessageId === message.messageId; // Fallback timer ref so handleAnimationEnd can cancel it once the real // animation completes (avoids double cleanup). var fallbackTimerRef = useRef(null); // Hold cleanup logic in a ref so we don't have to put unstable props // (animatedMessageId, onMessageAnimated) in effect deps — otherwise a // non-memoized onMessageAnimated would cancel the fallback timer on every // render before it can fire. var finalizeAnimationRef = useRef(function () { }); finalizeAnimationRef.current = function () { setShowBounce(false); // Only clear if this message is still the animation target if (animatedMessageId === message.messageId) { setAnimatedMessageId(null); onMessageAnimated === null || onMessageAnimated === void 0 ? void 0 : onMessageAnimated(); } }; useEffect(function () { if (isAnimationTarget) { setShowBounce(true); // The bounce keyframe is applied to a descendant `.sendbird-message-content`, // which is not rendered when consumers supply a custom `renderMessage`. // In that case `onAnimationEnd` never fires and the animation state would // be stuck, so schedule a fallback to force cleanup after the CSS // animation duration (1s) plus a small buffer (matches the prior // setTimeout-based timing). if (fallbackTimerRef.current !== null) clearTimeout(fallbackTimerRef.current); fallbackTimerRef.current = setTimeout(function () { fallbackTimerRef.current = null; finalizeAnimationRef.current(); }, 1600); } return function () { if (fallbackTimerRef.current !== null) { clearTimeout(fallbackTimerRef.current); fallbackTimerRef.current = null; } }; }, [isAnimationTarget]); var handleAnimationEnd = useCallback(function (e) { if (e.animationName === 'bounce') { if (fallbackTimerRef.current !== null) { clearTimeout(fallbackTimerRef.current); fallbackTimerRef.current = null; } finalizeAnimationRef.current(); } }, []); useLayoutEffect(function () { if ((newMessageIds === null || newMessageIds === void 0 ? void 0 : newMessageIds.length) > 0 && newMessageIds.includes(message.messageId)) { var rafId_1 = null; rafId_1 = requestAnimationFrame(function () { scrollMessageOverflowToTop(messageScrollRef, message); setNewMessageIds([]); }); return function () { if (rafId_1 !== null) { cancelAnimationFrame(rafId_1); } }; } }, [newMessageIds]); var renderedCustomSeparator = useMemo(function () { var _a; return (_a = renderCustomSeparator === null || renderCustomSeparator === void 0 ? void 0 : renderCustomSeparator({ message: message })) !== null && _a !== void 0 ? _a : null; }, [message, renderCustomSeparator]); var renderChildren = function () { if (children) { return children; } if (renderMessage) { var messageProps = __assign(__assign({}, props), { renderMessage: undefined }); return renderMessage(messageProps); } return (React__default.createElement(React__default.Fragment, null, renderMessageContent({ className: 'sendbird-message-hoc__message-content', userId: userId, scrollToMessage: scrollToMessage, channel: channel, message: message, disabled: !isOnline, chainTop: chainTop, chainBottom: chainBottom, isReactionEnabled: isReactionEnabled, replyType: replyType, threadReplySelectType: threadReplySelectType, nicknamesMap: nicknamesMap, emojiContainer: emojiContainer, showEdit: setShowEdit, showRemove: setShowRemove, showFileViewer: setShowFileViewer, resendMessage: resendMessage, deleteMessage: deleteMessage, toggleReaction: toggleReaction, setQuoteMessage: setQuoteMessage, onReplyInThread: onReplyInThreadClick, onQuoteMessageClick: onQuoteMessageClick, onMessageHeightChange: handleScroll, onBeforeDownloadFileMessage: onBeforeDownloadFileMessage, filterEmojiCategoryIds: filterEmojiCategoryIds, markAsUnread: markAsUnread, }), shouldRenderSuggestedReplies && renderSuggestedReplies({ replyOptions: getSuggestedReplies(message), onSendMessage: sendUserMessage, type: groupChannel === null || groupChannel === void 0 ? void 0 : groupChannel.suggestedRepliesDirection, }), showRemove && (renderRemoveMessageModal === null || renderRemoveMessageModal === void 0 ? void 0 : renderRemoveMessageModal({ message: message, onCancel: function () { return setShowRemove(false); } })), showFileViewer && renderFileViewer({ message: message, onCancel: function () { return setShowFileViewer(false); } }))); }; if (showEdit && ((_b = message === null || message === void 0 ? void 0 : message.isUserMessage) === null || _b === void 0 ? void 0 : _b.call(message))) { return ((renderEditInput === null || renderEditInput === void 0 ? void 0 : renderEditInput()) || (React__default.createElement(React__default.Fragment, null, displaySuggestedMentionList && (React__default.createElement(SuggestedMentionListView, { currentChannel: channel, targetNickname: mentionNickname, inputEvent: messageInputEvent !== null && messageInputEvent !== void 0 ? messageInputEvent : undefined, renderUserMentionItem: renderUserMentionItem, onUserItemClick: function (user) { if (user) { setMentionedUsers(__spreadArray(__spreadArray([], mentionedUsers, true), [user], false)); } setMentionNickname(''); setSelectedUser(user); setMessageInputEvent(null); }, onFocusItemChange: function () { setMessageInputEvent(null); }, onFetchUsers: function (users) { setMentionSuggestedUsers(users); }, ableAddMention: ableMention, maxMentionCount: maxUserMentionCount, maxSuggestionCount: maxUserSuggestionCount })), React__default.createElement(MessageInput, { isEdit: true, channel: channel, disabled: editInputDisabled, ref: editMessageInputRef, mentionSelectedUser: selectedUser, isMentionEnabled: groupChannel.enableMention, message: message, onStartTyping: startTyping, onStopTyping: stopTyping, onUpdateMessage: function (_a) { var messageId = _a.messageId, editedMessage = _a.message, mentionTemplate = _a.mentionTemplate, currentMentionedUserIds = _a.mentionedUserIds; updateUserMessage(messageId, { message: editedMessage, mentionedUserIds: currentMentionedUserIds !== null && currentMentionedUserIds !== void 0 ? currentMentionedUserIds : [], mentionedMessageTemplate: mentionTemplate, }); setShowEdit(false); stopTyping(); }, onCancelEdit: function () { setMentionNickname(''); setMentionedUsers([]); setMentionedUserIds([]); setMentionSuggestedUsers([]); setShowEdit(false); stopTyping(); }, onUserMentioned: function (user) { if ((selectedUser === null || selectedUser === void 0 ? void 0 : selectedUser.userId) === (user === null || user === void 0 ? void 0 : user.userId)) { setSelectedUser(null); setMentionNickname(''); } }, onMentionStringChange: function (mentionText) { setMentionNickname(mentionText); }, onMentionedUserIdsUpdated: function (userIds) { setMentionedUserIds(userIds); }, onKeyDown: function (e) { if (displaySuggestedMentionList && (mentionSuggestedUsers === null || mentionSuggestedUsers === void 0 ? void 0 : mentionSuggestedUsers.length) > 0 && ((e.key === MessageInputKeys.Enter && ableMention) || e.key === MessageInputKeys.ArrowUp || e.key === MessageInputKeys.ArrowDown)) { setMessageInputEvent(e); return true; } return false; } })))); } return (React__default.createElement("div", { className: classnames('sendbird-msg-hoc sendbird-msg--scroll-ref', showBounce && 'sendbird-msg-hoc__animated'), onAnimationEnd: showBounce ? handleAnimationEnd : undefined, "data-testid": "sendbird-message-view", style: children || renderMessage ? undefined : { marginBottom: '2px' }, "data-sb-message-id": message.messageId, "data-sb-created-at": message.createdAt, ref: messageScrollRef }, hasSeparator && (renderedCustomSeparator || (React__default.createElement(DateSeparator, null, React__default.createElement(Label, { type: LabelTypography.CAPTION_2, color: LabelColors.ONBACKGROUND_2 }, format(message.createdAt, stringSet.DATE_FORMAT__MESSAGE_LIST__DATE_SEPARATOR, { locale: dateLocale, }))))), hasNewMessageSeparator && (React__default.createElement(NewMessageIndicator, { onVisibilityChange: onNewMessageSeparatorVisibilityChange }, React__default.createElement(Label, { type: LabelTypography.CAPTION_2, color: LabelColors.PRIMARY }, "New Messages"))), renderChildren())); }; export { MessageView as M }; //# sourceMappingURL=bundle-DmOfz_GY.js.map