diff --git a/lib/shared/chat-message-item-utils.js b/lib/shared/chat-message-item-utils.js --- a/lib/shared/chat-message-item-utils.js +++ b/lib/shared/chat-message-item-utils.js @@ -43,7 +43,7 @@ if (item.messageInfo) { return messageKey(item.messageInfo); } - return item.messageInfos.map(messageKey).join('|'); + return item.messageInfos.map(messageKey).join('^'); } function chatMessageInfoItemTimestamp(item: BaseChatMessageInfoItem): string { diff --git a/lib/shared/messages/add-members-message-spec.js b/lib/shared/messages/add-members-message-spec.js --- a/lib/shared/messages/add-members-message-spec.js +++ b/lib/shared/messages/add-members-message-spec.js @@ -6,8 +6,10 @@ CreateMessageInfoParams, MessageSpec, NotificationTextsParams, + MergeRobotextMessageItemResult, } from './message-spec.js'; import { joinResult } from './utils.js'; +import type { RobotextChatMessageInfoItem } from '../../selectors/chat-selectors.js'; import { messageTypes } from '../../types/message-types-enum.js'; import type { ClientDBMessageInfo, @@ -30,6 +32,18 @@ import { values } from '../../utils/objects.js'; import { notifRobotextForMessageInfo } from '../notif-utils.js'; +function getAddMembersRobotext(messageInfo: AddMembersMessageInfo): EntityText { + const users = messageInfo.addedMembers; + invariant(users.length !== 0, 'added who??'); + + const creator = ET.user({ userInfo: messageInfo.creator }); + const addedUsers = pluralizeEntityText( + users.map(user => ET`${ET.user({ userInfo: user })}`), + ); + + return ET`${creator} added ${addedUsers}`; +} + type AddMembersMessageSpec = MessageSpec< AddMembersMessageData, RawAddMembersMessageInfo, @@ -111,15 +125,7 @@ }, robotext(messageInfo: AddMembersMessageInfo): EntityText { - const users = messageInfo.addedMembers; - invariant(users.length !== 0, 'added who??'); - - const creator = ET.user({ userInfo: messageInfo.creator }); - const addedUsers = pluralizeEntityText( - users.map(user => ET`${ET.user({ userInfo: user })}`), - ); - - return ET`${creator} added ${addedUsers}`; + return getAddMembersRobotext(messageInfo); }, async notificationTexts( @@ -180,4 +186,42 @@ canBePinned: false, validator: rawAddMembersMessageInfoValidator, + + mergeIntoPrecedingRobotextMessageItem( + messageInfo: AddMembersMessageInfo, + precedingMessageInfoItem: RobotextChatMessageInfoItem, + ): MergeRobotextMessageItemResult { + if (precedingMessageInfoItem.messageInfos.length === 0) { + return { shouldMerge: false }; + } + + const addedMembers = []; + const creatorID = messageInfo.creator.id; + for (const precedingMessageInfo of precedingMessageInfoItem.messageInfos) { + if ( + precedingMessageInfo.type !== messageTypes.ADD_MEMBERS || + precedingMessageInfo.creator.id !== creatorID + ) { + return { shouldMerge: false }; + } + for (const addedMember of precedingMessageInfo.addedMembers) { + addedMembers.push(addedMember); + } + } + + const messageInfos = [ + messageInfo, + ...precedingMessageInfoItem.messageInfos, + ]; + const newRobotext = getAddMembersRobotext({ + ...messageInfo, + addedMembers, + }); + const mergedItem = { + ...precedingMessageInfoItem, + messageInfos, + robotext: newRobotext, + }; + return { shouldMerge: true, item: mergedItem }; + }, }); diff --git a/lib/shared/messages/join-thread-message-spec.js b/lib/shared/messages/join-thread-message-spec.js --- a/lib/shared/messages/join-thread-message-spec.js +++ b/lib/shared/messages/join-thread-message-spec.js @@ -2,8 +2,13 @@ import invariant from 'invariant'; -import type { MessageSpec, RobotextParams } from './message-spec.js'; +import type { + MessageSpec, + RobotextParams, + MergeRobotextMessageItemResult, +} from './message-spec.js'; import { joinResult } from './utils.js'; +import type { RobotextChatMessageInfoItem } from '../../selectors/chat-selectors.js'; import { messageTypes } from '../../types/message-types-enum.js'; import type { ClientDBMessageInfo, @@ -25,6 +30,19 @@ } from '../../utils/entity-text.js'; import { values } from '../../utils/objects.js'; +function getJoinThreadRobotext( + joinerString: EntityText, + threadID: string, + params: RobotextParams, +): EntityText { + return ET`${joinerString} joined ${ET.thread({ + display: 'alwaysDisplayShortName', + threadID, + threadType: params.threadInfo?.type, + parentThreadID: params.threadInfo?.parentThreadID, + })}`; +} + export const joinThreadMessageSpec: MessageSpec< JoinThreadMessageData, RawJoinThreadMessageInfo, @@ -79,12 +97,7 @@ params: RobotextParams, ): EntityText { const creator = ET.user({ userInfo: messageInfo.creator }); - return ET`${creator} joined ${ET.thread({ - display: 'alwaysDisplayShortName', - threadID: messageInfo.threadID, - threadType: params.threadInfo?.type, - parentThreadID: params.threadInfo?.parentThreadID, - })}`; + return getJoinThreadRobotext(ET`${creator}`, messageInfo.threadID, params); }, async notificationTexts( @@ -124,4 +137,37 @@ canBePinned: false, validator: rawJoinThreadMessageInfoValidator, + + mergeIntoPrecedingRobotextMessageItem( + messageInfo: JoinThreadMessageInfo, + precedingMessageInfoItem: RobotextChatMessageInfoItem, + params: RobotextParams, + ): MergeRobotextMessageItemResult { + if (precedingMessageInfoItem.messageInfos.length === 0) { + return { shouldMerge: false }; + } + for (const precedingMessageInfo of precedingMessageInfoItem.messageInfos) { + if (precedingMessageInfo.type !== messageTypes.JOIN_THREAD) { + return { shouldMerge: false }; + } + } + const messageInfos = [ + messageInfo, + ...precedingMessageInfoItem.messageInfos, + ]; + const joiningUsers = pluralizeEntityText( + messageInfos.map(info => ET`${ET.user({ userInfo: info.creator })}`), + ); + const newRobotext = getJoinThreadRobotext( + joiningUsers, + messageInfo.threadID, + params, + ); + const mergedItem = { + ...precedingMessageInfoItem, + messageInfos, + robotext: newRobotext, + }; + return { shouldMerge: true, item: mergedItem }; + }, }); diff --git a/lib/shared/messages/leave-thread-message-spec.js b/lib/shared/messages/leave-thread-message-spec.js --- a/lib/shared/messages/leave-thread-message-spec.js +++ b/lib/shared/messages/leave-thread-message-spec.js @@ -2,8 +2,13 @@ import invariant from 'invariant'; -import type { MessageSpec, RobotextParams } from './message-spec.js'; +import type { + MessageSpec, + RobotextParams, + MergeRobotextMessageItemResult, +} from './message-spec.js'; import { joinResult } from './utils.js'; +import type { RobotextChatMessageInfoItem } from '../../selectors/chat-selectors.js'; import { messageTypes } from '../../types/message-types-enum.js'; import type { ClientDBMessageInfo, @@ -25,6 +30,19 @@ } from '../../utils/entity-text.js'; import { values } from '../../utils/objects.js'; +function getLeaveThreadRobotext( + leaverString: EntityText, + threadID: string, + params: RobotextParams, +): EntityText { + return ET`${leaverString} left ${ET.thread({ + display: 'alwaysDisplayShortName', + threadID, + threadType: params.threadInfo?.type, + parentThreadID: params.threadInfo?.parentThreadID, + })}`; +} + export const leaveThreadMessageSpec: MessageSpec< LeaveThreadMessageData, RawLeaveThreadMessageInfo, @@ -79,12 +97,7 @@ params: RobotextParams, ): EntityText { const creator = ET.user({ userInfo: messageInfo.creator }); - return ET`${creator} left ${ET.thread({ - display: 'alwaysDisplayShortName', - threadID: messageInfo.threadID, - threadType: params.threadInfo?.type, - parentThreadID: params.threadInfo?.parentThreadID, - })}`; + return getLeaveThreadRobotext(ET`${creator}`, messageInfo.threadID, params); }, async notificationTexts( @@ -124,4 +137,37 @@ canBePinned: false, validator: rawLeaveThreadMessageInfoValidator, + + mergeIntoPrecedingRobotextMessageItem( + messageInfo: LeaveThreadMessageInfo, + precedingMessageInfoItem: RobotextChatMessageInfoItem, + params: RobotextParams, + ): MergeRobotextMessageItemResult { + if (precedingMessageInfoItem.messageInfos.length === 0) { + return { shouldMerge: false }; + } + for (const precedingMessageInfo of precedingMessageInfoItem.messageInfos) { + if (precedingMessageInfo.type !== messageTypes.LEAVE_THREAD) { + return { shouldMerge: false }; + } + } + const messageInfos = [ + messageInfo, + ...precedingMessageInfoItem.messageInfos, + ]; + const leavingUsers = pluralizeEntityText( + messageInfos.map(info => ET`${ET.user({ userInfo: info.creator })}`), + ); + const newRobotext = getLeaveThreadRobotext( + leavingUsers, + messageInfo.threadID, + params, + ); + const mergedItem = { + ...precedingMessageInfoItem, + messageInfos, + robotext: newRobotext, + }; + return { shouldMerge: true, item: mergedItem }; + }, }); diff --git a/lib/shared/messages/remove-members-message-spec.js b/lib/shared/messages/remove-members-message-spec.js --- a/lib/shared/messages/remove-members-message-spec.js +++ b/lib/shared/messages/remove-members-message-spec.js @@ -6,8 +6,10 @@ CreateMessageInfoParams, MessageSpec, NotificationTextsParams, + MergeRobotextMessageItemResult, } from './message-spec.js'; import { joinResult } from './utils.js'; +import type { RobotextChatMessageInfoItem } from '../../selectors/chat-selectors.js'; import { messageTypes } from '../../types/message-types-enum.js'; import type { ClientDBMessageInfo, @@ -30,6 +32,20 @@ import { values } from '../../utils/objects.js'; import { notifRobotextForMessageInfo } from '../notif-utils.js'; +function getRemoveMembersRobotext( + messageInfo: RemoveMembersMessageInfo, +): EntityText { + const users = messageInfo.removedMembers; + invariant(users.length !== 0, 'added who??'); + + const creator = ET.user({ userInfo: messageInfo.creator }); + const removedUsers = pluralizeEntityText( + users.map(user => ET`${ET.user({ userInfo: user })}`), + ); + + return ET`${creator} removed ${removedUsers}`; +} + type RemoveMembersMessageSpec = MessageSpec< RemoveMembersMessageData, RawRemoveMembersMessageInfo, @@ -112,15 +128,7 @@ }, robotext(messageInfo: RemoveMembersMessageInfo): EntityText { - const users = messageInfo.removedMembers; - invariant(users.length !== 0, 'added who??'); - - const creator = ET.user({ userInfo: messageInfo.creator }); - const removedUsers = pluralizeEntityText( - users.map(user => ET`${ET.user({ userInfo: user })}`), - ); - - return ET`${creator} removed ${removedUsers}`; + return getRemoveMembersRobotext(messageInfo); }, async notificationTexts( @@ -185,5 +193,42 @@ canBePinned: false, validator: rawRemoveMembersMessageInfoValidator, + + mergeIntoPrecedingRobotextMessageItem( + messageInfo: RemoveMembersMessageInfo, + precedingMessageInfoItem: RobotextChatMessageInfoItem, + ): MergeRobotextMessageItemResult { + if (precedingMessageInfoItem.messageInfos.length === 0) { + return { shouldMerge: false }; + } + const removedMembers = []; + const creatorID = messageInfo.creator.id; + for (const precedingMessageInfo of precedingMessageInfoItem.messageInfos) { + if ( + precedingMessageInfo.type !== messageTypes.REMOVE_MEMBERS || + precedingMessageInfo.creator.id !== creatorID + ) { + return { shouldMerge: false }; + } + for (const removedMember of precedingMessageInfo.removedMembers) { + removedMembers.push(removedMember); + } + } + + const messageInfos = [ + messageInfo, + ...precedingMessageInfoItem.messageInfos, + ]; + const newRobotext = getRemoveMembersRobotext({ + ...messageInfo, + removedMembers, + }); + const mergedItem = { + ...precedingMessageInfoItem, + messageInfos, + robotext: newRobotext, + }; + return { shouldMerge: true, item: mergedItem }; + }, }, );