diff --git a/lib/selectors/chat-selectors.js b/lib/selectors/chat-selectors.js --- a/lib/selectors/chat-selectors.js +++ b/lib/selectors/chat-selectors.js @@ -272,6 +272,7 @@ endsCluster: boolean, +robotext: string, +threadCreatedFromMessage: ?ThreadInfo, + +reactions: $ReadOnlyMap, }; export type ChatMessageInfoItem = | RobotextChatMessageInfoItem @@ -283,8 +284,14 @@ +startsCluster: boolean, endsCluster: boolean, +threadCreatedFromMessage: ?ThreadInfo, + +reactions: $ReadOnlyMap, }; export type ChatMessageItem = { itemType: 'loader' } | ChatMessageInfoItem; +export type MessageReactionInfo = { + +viewerReacted: boolean, + +users: $ReadOnlySet, +}; + const msInFiveMinutes = 5 * 60 * 1000; function createChatMessageItems( threadID: string, @@ -304,6 +311,81 @@ ? sortMessageInfoList([...threadMessageInfos, ...additionalMessages]) : threadMessageInfos; + const targetMessageReactionsMap = new Map< + string, + Map< + string, + { + +users: Set, + viewerReacted: boolean, + }, + >, + >(); + + for (let i = messages.length - 1; i >= 0; i--) { + const messageInfo = messages[i]; + if (messageInfo.type !== messageTypes.REACTION) { + continue; + } + + if (!targetMessageReactionsMap.has(messageInfo.targetMessageID)) { + const reactsMap = new Map< + string, + { + +users: Set, + viewerReacted: boolean, + }, + >(); + + const usersSet = new Set(); + usersSet.add(JSON.stringify(messageInfo.creator)); + + const messageReactionInfo = { + viewerReacted: messageInfo.creator.isViewer, + users: usersSet, + }; + + reactsMap.set(messageInfo.reaction, messageReactionInfo); + targetMessageReactionsMap.set(messageInfo.targetMessageID, reactsMap); + continue; + } + + const messageReactsMap = targetMessageReactionsMap.get( + messageInfo.targetMessageID, + ); + invariant(messageReactsMap, 'messageReactsInfo should be set'); + + if (!messageReactsMap.has(messageInfo.reaction)) { + const usersSet = new Set(); + usersSet.add(JSON.stringify(messageInfo.creator)); + + const messageReactionInfo = { + viewerReacted: messageInfo.creator.isViewer, + users: usersSet, + }; + + messageReactsMap.set(messageInfo.reaction, messageReactionInfo); + continue; + } + + const messageReactionInfo = messageReactsMap.get(messageInfo.reaction); + invariant(messageReactionInfo, 'specificReactionInfo should be set'); + + if (messageInfo.action === 'add_reaction') { + if (messageInfo.creator.isViewer) { + messageReactionInfo.viewerReacted = true; + } + + messageReactionInfo.users.add(JSON.stringify(messageInfo.creator)); + } else { + if (messageInfo.creator.isViewer) { + messageReactionInfo.viewerReacted = false; + } + + messageReactionInfo.users.delete(JSON.stringify(messageInfo.creator)); + } + } + const chatMessageItems = []; let lastMessageInfo = null; for (let i = messages.length - 1; i >= 0; i--) { @@ -346,6 +428,33 @@ messageInfo.id && threadInfos[threadID]?.type !== threadTypes.SIDEBAR ? threadInfoFromSourceMessageID[messageInfo.id] : undefined; + + const renderedReactions: $ReadOnlyMap< + string, + MessageReactionInfo, + > = (() => { + let result; + + if (originalMessageInfo.id) { + result = targetMessageReactionsMap.get(originalMessageInfo.id); + } + + if (!result) { + result = new Map(); + } + + for (const reaction of result.keys()) { + const reactionInfo = result.get(reaction); + invariant(reactionInfo, 'reactionInfo should be set'); + + if (reactionInfo.users.size === 0) { + result.delete(reaction); + } + } + + return result; + })(); + if (isComposableMessageType(originalMessageInfo.type)) { // We use these invariants instead of just checking the messageInfo.type // directly in the conditional above so that isComposableMessageType can @@ -358,6 +467,7 @@ ); const localMessageInfo = messageStore.local[messageKey(originalMessageInfo)]; + chatMessageItems.push({ itemType: 'message', messageInfo: originalMessageInfo, @@ -366,6 +476,7 @@ startsCluster, endsCluster: false, threadCreatedFromMessage, + reactions: renderedReactions, }); } else { invariant( @@ -386,6 +497,7 @@ endsCluster: false, threadCreatedFromMessage, robotext, + reactions: renderedReactions, }); } lastMessageInfo = originalMessageInfo;