UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

93 lines (92 loc) 6.39 kB
import clsx from 'clsx'; import throttle from 'lodash.throttle'; import React from 'react'; import { EmptyStateIndicator as DefaultEmptyStateIndicator } from '../EmptyStateIndicator'; import { LoadingIndicator as DefaultLoadingIndicator } from '../Loading'; import { isMessageEdited, Message } from '../Message'; import { useComponentContext } from '../../context'; import { getIsFirstUnreadMessage, isDateSeparatorMessage, isIntroMessage } from './utils'; const PREPEND_OFFSET = 10 ** 7; export function calculateItemIndex(virtuosoIndex, numItemsPrepended) { return virtuosoIndex + numItemsPrepended - PREPEND_OFFSET; } export function calculateFirstItemIndex(numItemsPrepended) { return PREPEND_OFFSET - numItemsPrepended; } export const makeItemsRenderedHandler = (renderedItemsActions, processedMessages) => throttle((items) => { const renderedMessages = items .map((item) => { if (!item.originalIndex) return undefined; return processedMessages[calculateItemIndex(item.originalIndex, PREPEND_OFFSET)]; }) .filter((msg) => !!msg); renderedItemsActions.forEach((action) => action(renderedMessages)); }, 200); // using 'display: inline-block' // traps CSS margins of the item elements, preventing incorrect item measurements export const Item = ({ context, ...props }) => { if (!context) return React.createElement(React.Fragment, null); const message = context.processedMessages[calculateItemIndex(props['data-item-index'], context.numItemsPrepended)]; const groupStyles = context.messageGroupStyles[message.id]; return (React.createElement("div", { ...props, className: context?.customClasses?.virtualMessage || clsx('str-chat__virtual-list-message-wrapper str-chat__li', { [`str-chat__li--${groupStyles}`]: groupStyles, }) })); }; export const Header = ({ context }) => { const { LoadingIndicator = DefaultLoadingIndicator } = useComponentContext('VirtualizedMessageListHeader'); return (React.createElement(React.Fragment, null, context?.head, context?.loadingMore && LoadingIndicator && (React.createElement("div", { className: 'str-chat__virtual-list__loading' }, React.createElement(LoadingIndicator, { size: 20 }))))); }; export const EmptyPlaceholder = ({ context }) => { const { EmptyStateIndicator = DefaultEmptyStateIndicator } = useComponentContext('VirtualizedMessageList'); // prevent showing that there are no messages if there actually are messages (for some reason virtuoso decides to render empty placeholder first, even though it has the totalCount prop > 0) if (typeof context?.processedMessages !== 'undefined' && context.processedMessages.length > 0) return null; return (React.createElement(React.Fragment, null, EmptyStateIndicator && (React.createElement(EmptyStateIndicator, { listType: context?.threadList ? 'thread' : 'message' })))); }; export const messageRenderer = (virtuosoIndex, _data, virtuosoContext) => { const { additionalMessageInputProps, closeReactionSelectorOnClick, customMessageActions, customMessageRenderer, DateSeparator, firstUnreadMessageId, formatDate, lastReadDate, lastReadMessageId, lastReceivedMessageId, Message: MessageUIComponent, messageActions, messageGroupStyles, MessageSystem, numItemsPrepended, openThread, ownMessagesReadByOthers, processedMessages: messageList, reactionDetailsSort, shouldGroupByUser, sortReactionDetails, sortReactions, threadList, unreadMessageCount = 0, UnreadMessagesSeparator, virtuosoRef, } = virtuosoContext; const streamMessageIndex = calculateItemIndex(virtuosoIndex, numItemsPrepended); if (customMessageRenderer) { return customMessageRenderer(messageList, streamMessageIndex); } const message = messageList[streamMessageIndex]; if (!message || isIntroMessage(message)) return React.createElement("div", { style: { height: '1px' } }); // returning null or zero height breaks the virtuoso if (isDateSeparatorMessage(message)) { return DateSeparator ? (React.createElement(DateSeparator, { date: message.date, unread: message.unread })) : null; } if (message.type === 'system') { return MessageSystem ? React.createElement(MessageSystem, { message: message }) : null; } const maybePrevMessage = messageList[streamMessageIndex - 1]; const maybeNextMessage = messageList[streamMessageIndex + 1]; const groupedByUser = shouldGroupByUser && streamMessageIndex > 0 && message.user?.id === maybePrevMessage?.user?.id; // FIXME: firstOfGroup & endOfGroup should be derived from groupStyles which apply a more complex logic const firstOfGroup = shouldGroupByUser && (message.user?.id !== maybePrevMessage?.user?.id || (maybePrevMessage && isMessageEdited(maybePrevMessage))); const endOfGroup = shouldGroupByUser && (message.user?.id !== maybeNextMessage?.user?.id || isMessageEdited(message)); const isFirstUnreadMessage = getIsFirstUnreadMessage({ firstUnreadMessageId, isFirstMessage: streamMessageIndex === 0, lastReadDate, lastReadMessageId, message, previousMessage: streamMessageIndex ? messageList[streamMessageIndex - 1] : undefined, unreadMessageCount, }); return (React.createElement(React.Fragment, null, isFirstUnreadMessage && (React.createElement("div", { className: 'str-chat__unread-messages-separator-wrapper' }, React.createElement(UnreadMessagesSeparator, { unreadCount: unreadMessageCount }))), React.createElement(Message, { additionalMessageInputProps: additionalMessageInputProps, autoscrollToBottom: virtuosoRef.current?.autoscrollToBottom, closeReactionSelectorOnClick: closeReactionSelectorOnClick, customMessageActions: customMessageActions, endOfGroup: endOfGroup, firstOfGroup: firstOfGroup, formatDate: formatDate, groupedByUser: groupedByUser, groupStyles: [messageGroupStyles[message.id] ?? ''], lastReceivedId: lastReceivedMessageId, message: message, Message: MessageUIComponent, messageActions: messageActions, openThread: openThread, reactionDetailsSort: reactionDetailsSort, readBy: ownMessagesReadByOthers[message.id] || [], sortReactionDetails: sortReactionDetails, sortReactions: sortReactions, threadList: threadList }))); };