UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

225 lines (224 loc) 10.6 kB
/* eslint-disable no-continue */ import { nanoid } from 'nanoid'; import { CUSTOM_MESSAGE_TYPE } from '../../constants/messageTypes'; import { isDate } from '../../context/TranslationContext'; /** * processMessages - Transform the input message list according to config parameters * * Inserts date separators btw. messages created on different dates or before unread incoming messages. By default: * - enabled in main message list * - disabled in virtualized message list * - disabled in thread * * Allows to filter out deleted messages, contolled by hideDeletedMessages param. This is disabled by default. * * Sets Giphy preview message for VirtualizedMessageList * * The only required params are messages and userId, the rest are config params: * * @return {StreamMessage<StreamChatGenerics>[]} Transformed list of messages */ export var processMessages = function (params) { var _a, _b; var enableDateSeparator = params.enableDateSeparator, hideDeletedMessages = params.hideDeletedMessages, hideNewMessageSeparator = params.hideNewMessageSeparator, lastRead = params.lastRead, messages = params.messages, setGiphyPreviewMessage = params.setGiphyPreviewMessage, userId = params.userId; var unread = false; var ephemeralMessagePresent = false; var lastDateSeparator; var newMessages = []; for (var i = 0; i < messages.length; i += 1) { var message = messages[i]; if (hideDeletedMessages && message.type === 'deleted') { continue; } if (setGiphyPreviewMessage && message.type === 'ephemeral' && message.command === 'giphy') { ephemeralMessagePresent = true; setGiphyPreviewMessage(message); continue; } var messageDate = (message.created_at && isDate(message.created_at) && message.created_at.toDateString()) || ''; var previousMessage = messages[i - 1]; var prevMessageDate = messageDate; if (enableDateSeparator && (previousMessage === null || previousMessage === void 0 ? void 0 : previousMessage.created_at) && isDate(previousMessage.created_at)) { prevMessageDate = previousMessage.created_at.toDateString(); } if (!unread && !hideNewMessageSeparator) { unread = (lastRead && message.created_at && new Date(lastRead) < message.created_at) || false; // do not show date separator for current user's messages if (enableDateSeparator && unread && ((_a = message.user) === null || _a === void 0 ? void 0 : _a.id) !== userId) { newMessages.push({ customType: CUSTOM_MESSAGE_TYPE.date, date: message.created_at, id: makeDateMessageId(message.created_at), unread: unread, }); } } if (enableDateSeparator && (i === 0 || // always put date separator before the first message messageDate !== prevMessageDate || // add date separator btw. 2 messages created on different date // if hiding deleted messages replace the previous deleted message(s) with A separator if the last rendered message was created on different date (hideDeletedMessages && (previousMessage === null || previousMessage === void 0 ? void 0 : previousMessage.type) === 'deleted' && lastDateSeparator !== messageDate)) && ((_b = newMessages === null || newMessages === void 0 ? void 0 : newMessages[newMessages.length - 1]) === null || _b === void 0 ? void 0 : _b.customType) !== CUSTOM_MESSAGE_TYPE.date // do not show two date separators in a row) ) { lastDateSeparator = messageDate; newMessages.push({ customType: CUSTOM_MESSAGE_TYPE.date, date: message.created_at, id: makeDateMessageId(message.created_at), }, message); } else { newMessages.push(message); } } // clean up the giphy preview component state after a Cancel action if (setGiphyPreviewMessage && !ephemeralMessagePresent) { setGiphyPreviewMessage(undefined); } return newMessages; }; export var makeDateMessageId = function (date) { var idSuffix; try { idSuffix = !date ? nanoid() : date instanceof Date ? date.toISOString() : date; } catch (e) { idSuffix = nanoid(); } return "".concat(CUSTOM_MESSAGE_TYPE.date, "-").concat(idSuffix); }; // fast since it usually iterates just the last few messages export var getLastReceived = function (messages) { for (var i = messages.length - 1; i > 0; i -= 1) { if (messages[i].status === 'received') { return messages[i].id; } } return null; }; export var getReadStates = function (messages, read, returnAllReadData) { if (read === void 0) { read = {}; } // create object with empty array for each message id var readData = {}; Object.values(read).forEach(function (readState) { if (!readState.last_read) return; var userLastReadMsgId; // loop messages sent by current user and add read data for other users in channel messages.forEach(function (msg) { if (msg.updated_at && msg.updated_at < readState.last_read) { userLastReadMsgId = msg.id; // if true, save other user's read data for all messages they've read if (returnAllReadData) { if (!readData[userLastReadMsgId]) { readData[userLastReadMsgId] = []; } readData[userLastReadMsgId].push(readState.user); } } }); // if true, only save read data for other user's last read message if (userLastReadMsgId && !returnAllReadData) { if (!readData[userLastReadMsgId]) { readData[userLastReadMsgId] = []; } readData[userLastReadMsgId].push(readState.user); } }); return readData; }; export var insertIntro = function (messages, headerPosition) { var newMessages = messages; var intro = { customType: CUSTOM_MESSAGE_TYPE.intro, }; // if no headerPosition is set, HeaderComponent will go at the top if (!headerPosition) { newMessages.unshift(intro); return newMessages; } // if no messages, intro gets inserted if (!newMessages.length) { newMessages.unshift(intro); return newMessages; } // else loop over the messages for (var i = 0; i < messages.length; i += 1) { var message = messages[i]; var messageTime = message.created_at && isDate(message.created_at) ? message.created_at.getTime() : null; var nextMessage = messages[i + 1]; var nextMessageTime = nextMessage.created_at && isDate(nextMessage.created_at) ? nextMessage.created_at.getTime() : null; // header position is smaller than message time so comes after; if (messageTime && messageTime < headerPosition) { // if header position is also smaller than message time continue; if (nextMessageTime && nextMessageTime < headerPosition) { if (messages[i + 1] && messages[i + 1].customType === CUSTOM_MESSAGE_TYPE.date) continue; if (!nextMessageTime) { newMessages.push(intro); return newMessages; } } else { newMessages.splice(i + 1, 0, intro); return newMessages; } } } return newMessages; }; export var getGroupStyles = function (message, previousMessage, nextMessage, noGroupByUser) { var _a, _b, _c, _d, _e, _f, _g; if (message.customType === CUSTOM_MESSAGE_TYPE.date) return ''; if (message.customType === CUSTOM_MESSAGE_TYPE.intro) return ''; if (noGroupByUser || ((_a = message.attachments) === null || _a === void 0 ? void 0 : _a.length) !== 0) return 'single'; var isTopMessage = !previousMessage || previousMessage.customType === CUSTOM_MESSAGE_TYPE.intro || previousMessage.customType === CUSTOM_MESSAGE_TYPE.date || previousMessage.type === 'system' || ((_b = previousMessage.attachments) === null || _b === void 0 ? void 0 : _b.length) !== 0 || ((_c = message.user) === null || _c === void 0 ? void 0 : _c.id) !== ((_d = previousMessage.user) === null || _d === void 0 ? void 0 : _d.id) || previousMessage.type === 'error' || previousMessage.deleted_at || (message.reaction_counts && Object.keys(message.reaction_counts).length > 0); var isBottomMessage = !nextMessage || nextMessage.customType === CUSTOM_MESSAGE_TYPE.date || nextMessage.type === 'system' || nextMessage.customType === CUSTOM_MESSAGE_TYPE.intro || ((_e = nextMessage.attachments) === null || _e === void 0 ? void 0 : _e.length) !== 0 || ((_f = message.user) === null || _f === void 0 ? void 0 : _f.id) !== ((_g = nextMessage.user) === null || _g === void 0 ? void 0 : _g.id) || nextMessage.type === 'error' || nextMessage.deleted_at || (nextMessage.reaction_counts && Object.keys(nextMessage.reaction_counts).length > 0); if (!isTopMessage && !isBottomMessage) { if (message.deleted_at || message.type === 'error') return 'single'; return 'middle'; } if (isBottomMessage) { if (isTopMessage || message.deleted_at || message.type === 'error') return 'single'; return 'bottom'; } if (isTopMessage) return 'top'; return ''; }; // "Probably" included, because it may happen that the last page was returned and it has exactly the size of the limit // but the back-end cannot provide us with information on whether it has still more messages in the DB // FIXME: once the pagination state is moved from Channel to MessageList, these should be moved as well. // The MessageList should have configurable the limit for performing the requests. // This parameter would then be used within these functions export var hasMoreMessagesProbably = function (returnedCountMessages, limit) { return returnedCountMessages === limit; }; export var hasNotMoreMessages = function (returnedCountMessages, limit) { return returnedCountMessages < limit; };