diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue index 079a9ec4bcb..b7d49c175fc 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.vue +++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue @@ -232,6 +232,17 @@ export default { type: Boolean, default: undefined, }, + /** + * Specifies if the message is inside a collapsed group. + */ + isCollapsedSystemMessage: { + type: Boolean, + default: false, + }, + lastCollapsedMessageId: { + type: [String, Number], + default: 0, + }, /** * The type of the message. */ @@ -329,8 +340,17 @@ export default { return !this.nextMessageId || this.id === this.conversation?.lastMessage?.id }, + visualLastLastReadMessageId() { + return this.$store.getters.getVisualLastReadMessageId(this.token) + }, + isLastReadMessage() { - return !this.isLastMessage && this.id === this.$store.getters.getVisualLastReadMessageId(this.token) + if (this.isLastMessage) { + return false + } + return (!this.isCollapsedSystemMessage && this.id === this.visualLastLastReadMessageId) + || (this.isCollapsedSystemMessage && this.id === this.visualLastLastReadMessageId && this.id !== this.lastCollapsedMessageId) + || (this.isCombinedSystemMessage && this.lastCollapsedMessageId === this.visualLastLastReadMessageId) }, isSystemMessage() { diff --git a/src/components/MessagesList/MessagesGroup/MessagesSystemGroup.vue b/src/components/MessagesList/MessagesGroup/MessagesSystemGroup.vue index 46d23ac6100..01dbd38b20a 100644 --- a/src/components/MessagesList/MessagesGroup/MessagesSystemGroup.vue +++ b/src/components/MessagesList/MessagesGroup/MessagesSystemGroup.vue @@ -32,6 +32,7 @@ :is-combined-system-message-collapsed="messagesCollapsed.collapsed" :next-message-id="getNextMessageId(messagesCollapsed.messages.at(-1))" :previous-message-id="getPrevMessageId(messagesCollapsed.messages.at(0))" + :last-collapsed-message-id="messagesCollapsed.lastId" @toggle-combined-system-message="toggleCollapsed(messagesCollapsed)" /> @@ -153,6 +156,14 @@ export default { return 'user_added' } + // Group users removed by one actor + if (message1.systemMessage === 'user_removed' + && message1.systemMessage === message2.systemMessage + && message1.actorId === message2.actorId + && message1.actorType === message2.actorType) { + return 'user_removed' + } + // Group users reconnected in a minute if (message1.systemMessage === 'call_joined' && message2.systemMessage === 'call_left' @@ -194,30 +205,30 @@ export default { let lastMessage = null let forceNextGroup = false for (const message of messages) { - const isLastRead = message.id === this.lastReadMessageId const groupingType = this.messagesShouldBeGrouped(message, lastMessage) if (!groupingType || forceNextGroup) { - groups.push({ id: message.id, messages: [message], type: '', collapsed: this.groupIsCollapsed[message.id] ?? !isLastRead }) + groups.push({ id: message.id, lastId: message.id, messages: [message], type: '', collapsed: this.groupIsCollapsed[message.id] ?? true }) forceNextGroup = false } else { if (groupingType === 'call_reconnected') { - groups.push({ id: message.id, messages: [groups.at(-1).messages.pop()], type: '', collapsed: this.groupIsCollapsed[message.id] ?? !isLastRead }) + groups.push({ id: message.id, lastId: message.id, messages: [groups.at(-1).messages.pop()], type: '', collapsed: this.groupIsCollapsed[message.id] ?? true }) + groups.at(-1).lastId = groups.at(-1).messages.at(-1).id forceNextGroup = true } groups.at(-1).messages.push(message) + groups.at(-1).lastId = message.id groups.at(-1).type = groupingType - if (isLastRead) { + + // Check if last read message is hidden inside the collapsed group, and open it, if so. + // Otherwise, combined system message will show a marker + const isLastReadInsideGroup = this.lastReadMessageId >= groups.at(-1).id && this.lastReadMessageId < groups.at(-1).lastId + if (isLastReadInsideGroup) { groups.at(-1).collapsed = false } } lastMessage = message } - groups.forEach(group => { - if (this.groupIsCollapsed[group.id] === undefined) { - this.groupIsCollapsed[group.id] = group.collapsed - } - }) return groups }, diff --git a/src/components/MessagesList/MessagesList.vue b/src/components/MessagesList/MessagesList.vue index 58c9778cd0f..e83128c4c75 100644 --- a/src/components/MessagesList/MessagesList.vue +++ b/src/components/MessagesList/MessagesList.vue @@ -431,10 +431,10 @@ export default { }, softUpdateAuthorGroups(oldGroups, newGroups, dateTimestamp) { - const oldKeys = Object.keys(oldGroups) Object.entries(newGroups).forEach(([id, newGroup]) => { if (!oldGroups[id]) { - const oldId = oldKeys.find(key => id < key && oldGroups[key].nextMessageId <= newGroup.nextMessageId) + const oldId = Object.keys(oldGroups) + .find(key => id < key && oldGroups[key].nextMessageId <= newGroup.nextMessageId) if (oldId) { // newGroup includes oldGroup and more old messages, remove oldGroup delete this.messagesGroupedByDateByAuthor[dateTimestamp][oldId] diff --git a/src/composables/useCombinedSystemMessage.js b/src/composables/useCombinedSystemMessage.js index a37640c86bc..c9810a86b7c 100644 --- a/src/composables/useCombinedSystemMessage.js +++ b/src/composables/useCombinedSystemMessage.js @@ -145,6 +145,57 @@ export function useCombinedSystemMessage() { } } + // Handle cases when actor removed users from conversation (when remove team/group, for example) + if (type === 'user_removed') { + messages.forEach(message => { + if (checkIfSelfIsOneOfUsers(message)) { + selfIsUser = true + } else { + combinedMessage.messageParameters[`user${referenceIndex}`] = message.messageParameters.user + referenceIndex++ + } + usersCounter++ + }) + + if (checkIfSelfIsActor(combinedMessage)) { + if (usersCounter === 2) { + combinedMessage.message = t('spreed', 'You removed {user0} and {user1}') + } else { + combinedMessage.message = n('spreed', + 'You removed {user0}, {user1} and %n more participant', + 'You removed {user0}, {user1} and %n more participants', usersCounter - 2) + } + } else if (selfIsUser) { + if (usersCounter === 2) { + combinedMessage.message = actorIsAdministrator + ? t('spreed', 'An administrator removed you and {user0}') + : t('spreed', '{actor} removed you and {user0}') + } else { + combinedMessage.message = actorIsAdministrator + ? n('spreed', + 'An administrator removed you, {user0} and %n more participant', + 'An administrator removed you, {user0} and %n more participants', usersCounter - 2) + : n('spreed', + '{actor} removed you, {user0} and %n more participant', + '{actor} removed you, {user0} and %n more participants', usersCounter - 2) + } + } else { + if (usersCounter === 2) { + combinedMessage.message = actorIsAdministrator + ? t('spreed', 'An administrator removed {user0} and {user1}') + : t('spreed', '{actor} removed {user0} and {user1}') + } else { + combinedMessage.message = actorIsAdministrator + ? n('spreed', + 'An administrator removed {user0}, {user1} and %n more participant', + 'An administrator removed {user0}, {user1} and %n more participants', usersCounter - 2) + : n('spreed', + '{actor} removed {user0}, {user1} and %n more participant', + '{actor} removed {user0}, {user1} and %n more participants', usersCounter - 2) + } + } + } + // Handle cases when users joined or left the call if (type === 'call_joined' || type === 'call_left') { const storedUniqueUsers = []