diff --git a/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue b/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue index 7990c5b4c0..a72f9a3225 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessagePart/MessageBody.vue @@ -312,7 +312,7 @@ export default { }, isLastCallStartedMessage() { - return this.systemMessage === 'call_started' && this.id === this.$store.getters.getLastCallStartedMessageId + return this.systemMessage === 'call_started' && this.id === this.$store.getters.getLastCallStartedMessageId(this.token) }, showJoinCallButton() { diff --git a/src/services/messagesService.ts b/src/services/messagesService.ts index 7954d1af3d..21b4def34e 100644 --- a/src/services/messagesService.ts +++ b/src/services/messagesService.ts @@ -46,7 +46,7 @@ type EditMessagePayload = { token: string, messageId: number, updatedMessage: ed * @param [data.limit=100] Number of messages to load * @param options options; */ -const fetchMessages = async function({ token, lastKnownMessageId, includeLastKnown, limit = 100 }: ReceiveMessagesPayload, options: object): receiveMessagesResponse { +const fetchMessages = async function({ token, lastKnownMessageId, includeLastKnown, limit = 100 }: ReceiveMessagesPayload, options?: object): receiveMessagesResponse { return axios.get(generateOcsUrl('apps/spreed/api/v1/chat/{token}', { token }, options), { ...options, params: { @@ -69,7 +69,7 @@ const fetchMessages = async function({ token, lastKnownMessageId, includeLastKno * @param [data.limit=100] Number of messages to load * @param options options */ -const lookForNewMessages = async ({ token, lastKnownMessageId, limit = 100 }: ReceiveMessagesPayload, options: object): receiveMessagesResponse => { +const lookForNewMessages = async ({ token, lastKnownMessageId, limit = 100 }: ReceiveMessagesPayload, options?: object): receiveMessagesResponse => { return axios.get(generateOcsUrl('apps/spreed/api/v1/chat/{token}', { token }, options), { ...options, params: { @@ -94,7 +94,7 @@ const lookForNewMessages = async ({ token, lastKnownMessageId, limit = 100 }: Re * @param [data.limit=50] Number of messages to load * @param options options; */ -const getMessageContext = async function({ token, messageId, limit = 50 }: GetMessageContextPayload, options: object): getMessageContextResponse { +const getMessageContext = async function({ token, messageId, limit = 50 }: GetMessageContextPayload, options?: object): getMessageContextResponse { return axios.get(generateOcsUrl('apps/spreed/api/v1/chat/{token}/{messageId}/context', { token, messageId }, options), { ...options, params: { @@ -129,9 +129,10 @@ const postNewMessage = async function({ token, message, actorDisplayName, refere * Clears the conversation history * * @param token The token of the conversation to be deleted. + * @param options request options */ -const clearConversationHistory = async function(token: string): clearHistoryResponse { - return axios.delete(generateOcsUrl('apps/spreed/api/v1/chat/{token}', { token })) +const clearConversationHistory = async function(token: string, options?: object): clearHistoryResponse { + return axios.delete(generateOcsUrl('apps/spreed/api/v1/chat/{token}', { token }, options), options) } /** @@ -142,7 +143,7 @@ const clearConversationHistory = async function(token: string): clearHistoryResp * @param param0.id The id of the message to be deleted * @param options request options */ -const deleteMessage = async function({ token, id }: DeleteMessagePayload, options: object): deleteMessageResponse { +const deleteMessage = async function({ token, id }: DeleteMessagePayload, options?: object): deleteMessageResponse { return axios.delete(generateOcsUrl('apps/spreed/api/v1/chat/{token}/{id}', { token, id }, options), options) } @@ -155,7 +156,7 @@ const deleteMessage = async function({ token, id }: DeleteMessagePayload, option * @param param0.updatedMessage The modified text of the message / file share caption * @param options request options */ -const editMessage = async function({ token, messageId, updatedMessage }: EditMessagePayload, options: object): editMessageResponse { +const editMessage = async function({ token, messageId, updatedMessage }: EditMessagePayload, options?: object): editMessageResponse { return axios.put(generateOcsUrl('apps/spreed/api/v1/chat/{token}/{messageId}', { token, messageId }, options), { message: updatedMessage, } as editMessageParams, options) @@ -172,7 +173,7 @@ const editMessage = async function({ token, messageId, updatedMessage }: EditMes * @param data.referenceId generated reference id, leave empty to generate it based on the other args * @param options request options */ -const postRichObjectToConversation = async function(token: string, { objectType, objectId, metaData, referenceId }: postRichObjectParams, options: object): postRichObjectResponse { +const postRichObjectToConversation = async function(token: string, { objectType, objectId, metaData, referenceId }: postRichObjectParams, options?: object): postRichObjectResponse { if (!referenceId) { const tempId = 'richobject-' + objectType + '-' + objectId + '-' + token + '-' + (new Date().getTime()) referenceId = Hex.stringify(SHA256(tempId)) @@ -192,7 +193,7 @@ const postRichObjectToConversation = async function(token: string, { objectType, * @param lastReadMessage id of the last read message to set * @param options request options */ -const updateLastReadMessage = async function(token: string, lastReadMessage: setReadMarkerParams, options: object): setReadMarkerResponse { +const updateLastReadMessage = async function(token: string, lastReadMessage?: number|null, options?: object): setReadMarkerResponse { return axios.post(generateOcsUrl('apps/spreed/api/v1/chat/{token}/read', { token }, options), { lastReadMessage, } as setReadMarkerParams, options) @@ -202,9 +203,10 @@ const updateLastReadMessage = async function(token: string, lastReadMessage: set * Set conversation as unread * * @param token The token of the conversation to be set as unread + * @param options request options */ -const setConversationUnread = async function(token: string): markUnreadResponse { - return axios.delete(generateOcsUrl('apps/spreed/api/v1/chat/{token}/read', { token })) +const setConversationUnread = async function(token: string, options?: object): markUnreadResponse { + return axios.delete(generateOcsUrl('apps/spreed/api/v1/chat/{token}/read', { token }, options), options) } export { diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index ec6a609de6..5a686abc1a 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -209,8 +209,8 @@ const getters = { return null }, - getLastCallStartedMessageId: (state, getters, rootState, rootGetters) => { - return getters.messagesList(rootGetters.getToken()).findLast((message) => message.systemMessage === 'call_started')?.id + getLastCallStartedMessageId: (state, getters) => (token) => { + return getters.messagesList(token).findLast((message) => message.systemMessage === 'call_started')?.id }, getFirstDisplayableMessageIdAfterReadMarker: (state, getters) => (token, readMessageId) => { @@ -833,6 +833,7 @@ const actions = { context.dispatch('updateLastReadMessage', { token, id: null, updateVisually }) return } + // federated conversations don't proxy lastMessage id if (!conversation?.lastMessage?.id) { return } @@ -862,9 +863,14 @@ const actions = { } // optimistic early commit to avoid indicator flickering - context.dispatch('updateConversationLastReadMessage', { token, lastReadMessage: id }) - if (updateVisually) { - context.commit('setVisualLastReadMessageId', { token, id }) + // skip for federated conversations + const idToUpdate = (id === null) ? conversation.lastMessage?.id : id + if (idToUpdate) { + context.dispatch('updateConversationLastReadMessage', { token, lastReadMessage: idToUpdate }) + } + const visualIdToUpdate = idToUpdate ?? context.getters.messagesList(token).at(-1)?.id + if (updateVisually && visualIdToUpdate) { + context.commit('setVisualLastReadMessageId', { token, id: visualIdToUpdate }) } if (context.getters.getUserId()) { diff --git a/src/store/messagesStore.spec.js b/src/store/messagesStore.spec.js index 65a6ee1665..23d696ed26 100644 --- a/src/store/messagesStore.spec.js +++ b/src/store/messagesStore.spec.js @@ -53,6 +53,15 @@ jest.mock('@nextcloud/dialogs', () => ({ showError: jest.fn(), })) +// Test actions with 'chat-read-last' feature +jest.mock('@nextcloud/capabilities', () => ({ + getCapabilities: jest.fn(() => ({ + spreed: { + features: ['chat-read-last'], + }, + })) +})) + describe('messagesStore', () => { const TOKEN = 'XXTOKENXX' let localVue = null @@ -665,10 +674,32 @@ describe('messagesStore', () => { lastReadMessage: 123, }) - expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 123) + expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, null) expect(store.getters.getVisualLastReadMessageId(TOKEN)).toBe(100) }) + test('clears last read message for federated conversation', async () => { + getUserIdMock.mockReturnValue('federated-user-1') + conversationMock.mockReturnValue({ + lastMessage: {}, + remoteServer: 'nextcloud.com', + }) + + store.commit('addMessage', { token: TOKEN, message: { id: 123 } }) + store.dispatch('setVisualLastReadMessageId', { token: TOKEN, id: 100 }) + await store.dispatch('clearLastReadMessage', { + token: TOKEN, + updateVisually: true, + }) + + expect(conversationMock).toHaveBeenCalled() + expect(getUserIdMock).toHaveBeenCalled() + expect(updateConversationLastReadMessageMock).not.toHaveBeenCalled() + + expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, null) + expect(store.getters.getVisualLastReadMessageId(TOKEN)).toBe(123) + }) + test('clears last read message and update visually', async () => { getUserIdMock.mockReturnValue('user-1') @@ -685,7 +716,7 @@ describe('messagesStore', () => { lastReadMessage: 123, }) - expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, 123) + expect(updateLastReadMessage).toHaveBeenCalledWith(TOKEN, null) expect(store.getters.getVisualLastReadMessageId(TOKEN)).toBe(123) })