UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

120 lines (119 loc) 9.25 kB
import React, { useMemo, useState } from 'react'; import clsx from 'clsx'; import { MessageErrorIcon } from './icons'; import { MessageBouncePrompt as DefaultMessageBouncePrompt } from '../MessageBounce'; import { MessageDeleted as DefaultMessageDeleted } from './MessageDeleted'; import { MessageBlocked as DefaultMessageBlocked } from './MessageBlocked'; import { MessageOptions as DefaultMessageOptions } from './MessageOptions'; import { MessageRepliesCountButton as DefaultMessageRepliesCountButton } from './MessageRepliesCountButton'; import { MessageStatus as DefaultMessageStatus } from './MessageStatus'; import { MessageText } from './MessageText'; import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp'; import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText'; import { isDateSeparatorMessage } from '../MessageList'; import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator'; import { ReminderNotification as DefaultReminderNotification } from './ReminderNotification'; import { useMessageReminder } from './hooks'; import { areMessageUIPropsEqual, isMessageBlocked, isMessageBounced, isMessageEdited, messageHasAttachments, messageHasReactions, } from './utils'; import { Avatar as DefaultAvatar } from '../Avatar'; import { Attachment as DefaultAttachment } from '../Attachment'; import { EditMessageModal } from '../MessageInput'; import { Poll } from '../Poll'; import { ReactionsList as DefaultReactionList } from '../Reactions'; import { MessageBounceModal } from '../MessageBounce/MessageBounceModal'; import { useComponentContext } from '../../context/ComponentContext'; import { useMessageContext } from '../../context/MessageContext'; import { useChatContext, useTranslationContext } from '../../context'; import { MessageEditedTimestamp } from './MessageEditedTimestamp'; const MessageSimpleWithContext = (props) => { const { additionalMessageInputProps, editing, endOfGroup, firstOfGroup, groupedByUser, handleAction, handleOpenThread, handleRetry, highlighted, isMessageAIGenerated, isMyMessage, message, onUserClick, onUserHover, renderText, threadList, } = props; const { client } = useChatContext('MessageSimple'); const { t } = useTranslationContext('MessageSimple'); const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false); const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false); const reminder = useMessageReminder(message.id); const { Attachment = DefaultAttachment, Avatar = DefaultAvatar, MessageOptions = DefaultMessageOptions, // TODO: remove this "passthrough" in the next // major release and use the new default instead MessageActions = MessageOptions, MessageBlocked = DefaultMessageBlocked, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageDeleted = DefaultMessageDeleted, MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator, MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, ReactionsList = DefaultReactionList, ReminderNotification = DefaultReminderNotification, StreamedMessageText = DefaultStreamedMessageText, PinIndicator, } = useComponentContext('MessageSimple'); const hasAttachment = messageHasAttachments(message); const hasReactions = messageHasReactions(message); const isAIGenerated = useMemo(() => isMessageAIGenerated?.(message), [isMessageAIGenerated, message]); const finalAttachments = useMemo(() => !message.shared_location && !message.attachments ? [] : !message.shared_location ? message.attachments : [message.shared_location, ...(message.attachments ?? [])], [message]); if (isDateSeparatorMessage(message)) { return null; } if (message.deleted_at || message.type === 'deleted') { return React.createElement(MessageDeleted, { message: message }); } if (isMessageBlocked(message)) { return React.createElement(MessageBlocked, null); } const showMetadata = !groupedByUser || endOfGroup; const showReplyCountButton = !threadList && !!message.reply_count; const showIsReplyInChannel = !threadList && message.show_in_channel && message.parent_id; const allowRetry = message.status === 'failed' && message.error?.status !== 403; const isBounced = isMessageBounced(message); const isEdited = isMessageEdited(message) && !isAIGenerated; let handleClick = undefined; if (allowRetry) { handleClick = () => handleRetry(message); } else if (isBounced) { handleClick = () => setIsBounceDialogOpen(true); } else if (isEdited) { handleClick = () => setEditedTimestampOpen((prev) => !prev); } const rootClassName = clsx('str-chat__message str-chat__message-simple', `str-chat__message--${message.type}`, `str-chat__message--${message.status}`, isMyMessage() ? 'str-chat__message--me str-chat__message-simple--me' : 'str-chat__message--other', message.text ? 'str-chat__message--has-text' : 'has-no-text', { 'str-chat__message--has-attachment': hasAttachment, 'str-chat__message--highlighted': highlighted, 'str-chat__message--pinned pinned-message': message.pinned, 'str-chat__message--with-reactions': hasReactions, 'str-chat__message-send-can-be-retried': message?.status === 'failed' && message?.error?.status !== 403, 'str-chat__message-with-thread-link': showReplyCountButton || showIsReplyInChannel, 'str-chat__virtual-message__wrapper--end': endOfGroup, 'str-chat__virtual-message__wrapper--first': firstOfGroup, 'str-chat__virtual-message__wrapper--group': groupedByUser, }); const poll = message.poll_id && client.polls.fromState(message.poll_id); return (React.createElement(React.Fragment, null, editing && (React.createElement(EditMessageModal, { additionalMessageInputProps: additionalMessageInputProps })), isBounceDialogOpen && (React.createElement(MessageBounceModal, { MessageBouncePrompt: MessageBouncePrompt, onClose: () => setIsBounceDialogOpen(false), open: isBounceDialogOpen })), React.createElement("div", { className: rootClassName, key: message.id }, PinIndicator && React.createElement(PinIndicator, null), !!reminder && React.createElement(ReminderNotification, { reminder: reminder }), message.user && (React.createElement(Avatar, { image: message.user.image, name: message.user.name || message.user.id, onClick: onUserClick, onMouseOver: onUserHover, user: message.user })), React.createElement("div", { className: clsx('str-chat__message-inner', { 'str-chat__simple-message--error-failed': allowRetry || isBounced, }), "data-testid": 'message-inner', onClick: handleClick, onKeyUp: handleClick }, React.createElement(MessageActions, null), React.createElement("div", { className: 'str-chat__message-reactions-host' }, hasReactions && React.createElement(ReactionsList, { reverse: true })), React.createElement("div", { className: 'str-chat__message-bubble' }, poll && React.createElement(Poll, { poll: poll }), finalAttachments?.length && !message.quoted_message ? (React.createElement(Attachment, { actionHandler: handleAction, attachments: finalAttachments })) : null, isAIGenerated ? (React.createElement(StreamedMessageText, { message: message, renderText: renderText })) : (React.createElement(MessageText, { message: message, renderText: renderText })), React.createElement(MessageErrorIcon, null))), showReplyCountButton && (React.createElement(MessageRepliesCountButton, { onClick: handleOpenThread, reply_count: message.reply_count })), showIsReplyInChannel && React.createElement(MessageIsThreadReplyInChannelButtonIndicator, null), showMetadata && (React.createElement("div", { className: 'str-chat__message-metadata' }, React.createElement(MessageStatus, null), !isMyMessage() && !!message.user && (React.createElement("span", { className: 'str-chat__message-simple-name' }, message.user.name || message.user.id)), React.createElement(MessageTimestamp, { customClass: 'str-chat__message-simple-timestamp' }), isEdited && (React.createElement("span", { className: 'str-chat__mesage-simple-edited' }, t('Edited'))), isEdited && (React.createElement(MessageEditedTimestamp, { calendar: true, open: isEditedTimestampOpen }))))))); }; const MemoizedMessageSimple = React.memo(MessageSimpleWithContext, areMessageUIPropsEqual); /** * The default UI component that renders a message and receives functionality and logic from the MessageContext. */ export const MessageSimple = (props) => { const messageContext = useMessageContext('MessageSimple'); return React.createElement(MemoizedMessageSimple, { ...messageContext, ...props }); };