UNPKG

@sendbird/uikit-react

Version:

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

318 lines (310 loc) 21.2 kB
'use strict'; var _tslib = require('./bundle-jAsAzWpU.js'); var React = require('react'); var useTypingLifecycle = require('./bundle-BjZvm-U5.js'); var index = require('./bundle--ZBrECLK.js'); var LocalizationContext = require('./bundle-ClT0IexP.js'); var _const = require('./bundle-CeCg868O.js'); var utils$1 = require('./bundle-DwLWArJq.js'); var Message_hooks_useDirtyGetMentions = require('../Message/hooks/useDirtyGetMentions.js'); var index$1 = require('./bundle-CskFALvU.js'); var ui_DateSeparator = require('../ui/DateSeparator.js'); var ui_Label = require('./bundle-DxZzcGya.js'); var ui_MessageInput = require('./bundle-CzhNQgac.js'); var _const$1 = require('./bundle-BSEj3ItE.js'); var ui_MessageContent = require('../ui/MessageContent.js'); var GroupChannel_components_SuggestedReplies = require('../GroupChannel/components/SuggestedReplies.js'); var SuggestedMentionListView = require('./bundle-Iy7lVhfX.js'); var utils = require('./bundle-1F9guuKw.js'); var useSendbird = require('./bundle-on0zTbLT.js'); var color = require('./bundle-mNJHRgJ3.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefaultCompat(React); var useDidMountEffect = function (func, deps) { var _a = React.useState(false), didMount = _a[0], setDidMount = _a[1]; React.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 ? color.Colors.PRIMARY : _d; var separatorRef = React.useRef(null); var handleVisibilityChange = React.useCallback(function (isVisible) { onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isVisible); }, [onVisibilityChange]); React.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.default.createElement("div", { ref: separatorRef, className: _tslib.__spreadArray(_tslib.__spreadArray([], (Array.isArray(className) ? className : [className]), true), [ 'sendbird-separator', ], false).join(' ') }, React__default.default.createElement("div", { className: ['sendbird-separator__left', "".concat(color.changeColorToClassName(separatorColor), "--background-color")].join(' ') }), React__default.default.createElement("div", { className: "sendbird-separator__text" }, children || (React__default.default.createElement(ui_Label.Label, { type: ui_Label.LabelTypography.CAPTION_2, color: ui_Label.LabelColors.PRIMARY }, "New Messages"))), React__default.default.createElement("div", { className: ['sendbird-separator__right', "".concat(color.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 = utils.deleteNullish(props), renderUserMentionItem = _d.renderUserMentionItem, renderMessage = _d.renderMessage, _e = _d.renderMessageContent, renderMessageContent = _e === void 0 ? function (props) { return React__default.default.createElement(ui_MessageContent.MessageContent, _tslib.__assign({}, props)); } : _e, _f = _d.renderSuggestedReplies, renderSuggestedReplies = _f === void 0 ? function (props) { return React__default.default.createElement(GroupChannel_components_SuggestedReplies.default, _tslib.__assign({}, props)); } : _f, renderCustomSeparator = _d.renderCustomSeparator, renderEditInput = _d.renderEditInput, renderFileViewer = _d.renderFileViewer, renderRemoveMessageModal = _d.renderRemoveMessageModal, filterEmojiCategoryIds = _d.filterEmojiCategoryIds; var _g = LocalizationContext.useLocalization(), dateLocale = _g.dateLocale, stringSet = _g.stringSet; var state = useSendbird.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) || _const.MAX_USER_MENTION_COUNT; var maxUserSuggestionCount = (userMention === null || userMention === void 0 ? void 0 : userMention.maxSuggestionCount) || _const.MAX_USER_SUGGESTION_COUNT; var _j = React.useState(false), showEdit = _j[0], setShowEdit = _j[1]; var _k = React.useState(false), showRemove = _k[0], setShowRemove = _k[1]; var _l = React.useState(false), showFileViewer = _l[0], setShowFileViewer = _l[1]; // isAnimated state removed — animation now driven by animatedMessageId + onAnimationEnd var _m = React.useState(''), mentionNickname = _m[0], setMentionNickname = _m[1]; var _o = React.useState([]), mentionedUsers = _o[0], setMentionedUsers = _o[1]; var _p = React.useState([]), mentionedUserIds = _p[0], setMentionedUserIds = _p[1]; var _q = React.useState(null), messageInputEvent = _q[0], setMessageInputEvent = _q[1]; var _r = React.useState(null), selectedUser = _r[0], setSelectedUser = _r[1]; var _s = React.useState([]), mentionSuggestedUsers = _s[0], setMentionSuggestedUsers = _s[1]; var editMessageInputRef = React.useRef(null); var messageScrollRef = React.useRef(null); var displaySuggestedMentionList = isOnline && groupChannel.enableMention && mentionNickname.length > 0 && !utils$1.isDisabledBecauseFrozen(channel) && !utils$1.isDisabledBecauseMuted(channel); var mentionNodes = Message_hooks_useDirtyGetMentions.useDirtyGetMentions({ ref: editMessageInputRef }, { logger: logger }); var ableMention = (mentionNodes === null || mentionNodes === void 0 ? void 0 : mentionNodes.length) < maxUserMentionCount; React.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.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 = React.useRef(shouldRenderSuggestedReplies); React.useEffect(function () { if (prevShouldRenderSuggestedReplies.current !== shouldRenderSuggestedReplies) { handleScroll === null || handleScroll === void 0 ? void 0 : handleScroll(); } else { prevShouldRenderSuggestedReplies.current = shouldRenderSuggestedReplies; } }, [shouldRenderSuggestedReplies]); React.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 = React.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 = React.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 = React.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(); } }; React.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 = React.useCallback(function (e) { if (e.animationName === 'bounce') { if (fallbackTimerRef.current !== null) { clearTimeout(fallbackTimerRef.current); fallbackTimerRef.current = null; } finalizeAnimationRef.current(); } }, []); React.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 = React.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 = _tslib.__assign(_tslib.__assign({}, props), { renderMessage: undefined }); return renderMessage(messageProps); } return (React__default.default.createElement(React__default.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: index$1.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.default.createElement(React__default.default.Fragment, null, displaySuggestedMentionList && (React__default.default.createElement(SuggestedMentionListView.SuggestedMentionListView, { currentChannel: channel, targetNickname: mentionNickname, inputEvent: messageInputEvent !== null && messageInputEvent !== void 0 ? messageInputEvent : undefined, renderUserMentionItem: renderUserMentionItem, onUserItemClick: function (user) { if (user) { setMentionedUsers(_tslib.__spreadArray(_tslib.__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.default.createElement(ui_MessageInput.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 === _const$1.MessageInputKeys.Enter && ableMention) || e.key === _const$1.MessageInputKeys.ArrowUp || e.key === _const$1.MessageInputKeys.ArrowDown)) { setMessageInputEvent(e); return true; } return false; } })))); } return (React__default.default.createElement("div", { className: utils.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.default.createElement(ui_DateSeparator, null, React__default.default.createElement(ui_Label.Label, { type: ui_Label.LabelTypography.CAPTION_2, color: ui_Label.LabelColors.ONBACKGROUND_2 }, index.format(message.createdAt, stringSet.DATE_FORMAT__MESSAGE_LIST__DATE_SEPARATOR, { locale: dateLocale, }))))), hasNewMessageSeparator && (React__default.default.createElement(NewMessageIndicator, { onVisibilityChange: onNewMessageSeparatorVisibilityChange }, React__default.default.createElement(ui_Label.Label, { type: ui_Label.LabelTypography.CAPTION_2, color: ui_Label.LabelColors.PRIMARY }, "New Messages"))), renderChildren())); }; exports.MessageView = MessageView; //# sourceMappingURL=bundle-DaeRuO3M.js.map