From eb7c31962c38f1f33170faf467953cbb6e027d85 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 5 Dec 2023 18:03:13 +0100 Subject: [PATCH 1/4] fix(store): rename action removeUserAbsence, ease payload Signed-off-by: Maksim Sukharev --- src/components/NewMessage/NewMessage.vue | 4 +--- src/stores/__tests__/chatExtras.spec.js | 4 ++-- src/stores/chatExtras.js | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index eeead3bcd09..2a9bc363aa8 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -837,9 +837,7 @@ export default { }) } else { // Remove stored absence status - this.chatExtrasStore.resetUserAbsence({ - token: this.token, - }) + this.chatExtrasStore.removeUserAbsence(this.token) } } }, diff --git a/src/stores/__tests__/chatExtras.spec.js b/src/stores/__tests__/chatExtras.spec.js index abe6c7a40eb..24d4829876d 100644 --- a/src/stores/__tests__/chatExtras.spec.js +++ b/src/stores/__tests__/chatExtras.spec.js @@ -64,8 +64,8 @@ describe('chatExtrasStore', () => { // Act await chatExtrasStore.getUserAbsence({ token, userId }) - chatExtrasStore.resetUserAbsence({ token }) - chatExtrasStore.resetUserAbsence({ token: token2 }) + chatExtrasStore.removeUserAbsence(token) + chatExtrasStore.removeUserAbsence(token2) // Assert expect(chatExtrasStore.absence[token]).toEqual(undefined) diff --git a/src/stores/chatExtras.js b/src/stores/chatExtras.js index d9ad5657606..1b382aa0ff6 100644 --- a/src/stores/chatExtras.js +++ b/src/stores/chatExtras.js @@ -56,11 +56,10 @@ export const useChatExtrasStore = defineStore('chatExtras', { /** * Drop an absence status from the store * - * @param {object} payload action payload - * @param {string} payload.token The conversation token + * @param {string} token The conversation token * */ - resetUserAbsence({ token }) { + removeUserAbsence(token) { if (this.absence[token]) { Vue.delete(this.absence, token) } From 6eb0968dcbf0a42f2be7956d69e0f7d6fa2dc69c Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 5 Dec 2023 18:31:41 +0100 Subject: [PATCH 2/4] fix(store): migrate getters, actions and tests Signed-off-by: Maksim Sukharev --- src/stores/__tests__/chatExtras.spec.js | 67 +++++++++++++++++- src/stores/chatExtras.js | 90 +++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/src/stores/__tests__/chatExtras.spec.js b/src/stores/__tests__/chatExtras.spec.js index 24d4829876d..6a24bf51c95 100644 --- a/src/stores/__tests__/chatExtras.spec.js +++ b/src/stores/__tests__/chatExtras.spec.js @@ -68,9 +68,72 @@ describe('chatExtrasStore', () => { chatExtrasStore.removeUserAbsence(token2) // Assert - expect(chatExtrasStore.absence[token]).toEqual(undefined) - expect(chatExtrasStore.absence[token2]).toEqual(undefined) + expect(chatExtrasStore.absence[token]).not.toBeDefined() + expect(chatExtrasStore.absence[token2]).not.toBeDefined() }) }) + + describe('reply message', () => { + it('adds reply message id to the store', () => { + // Act + chatExtrasStore.addMessageToBeReplied({ token, id: 101 }) + + // Assert + expect(chatExtrasStore.getMessageToBeReplied(token)).toBe(101) + }) + + it('clears reply message', () => { + // Arrange + chatExtrasStore.addMessageToBeReplied({ token, id: 101 }) + + // Act + chatExtrasStore.removeMessageToBeReplied(token) + + // Assert + expect(chatExtrasStore.getMessageToBeReplied(token)).not.toBeDefined() + }) + }) + + describe('current input message', () => { + it('sets current input message', () => { + // Act + chatExtrasStore.setCurrentMessageInput({ token: 'token-1', text: 'message-1' }) + + // Assert + expect(chatExtrasStore.getCurrentMessageInput('token-1')).toStrictEqual('message-1') + }) + + it('clears current input message', () => { + // Arrange + chatExtrasStore.setCurrentMessageInput({ token: 'token-1', text: 'message-1' }) + + // Act + chatExtrasStore.removeCurrentMessageInput('token-1') + + // Assert + expect(chatExtrasStore.currentMessageInput['token-1']).not.toBeDefined() + expect(chatExtrasStore.getCurrentMessageInput('token-1')).toBe('') + }) + }) + + describe('purge store', () => { + it('clears store for provided token', async () => { + // Arrange + const response = generateOCSResponse({ payload }) + getUserAbsence.mockResolvedValueOnce(response) + + await chatExtrasStore.getUserAbsence({ token: 'token-1', userId }) + chatExtrasStore.addMessageToBeReplied({ token: 'token-1', id: 101 }) + chatExtrasStore.setCurrentMessageInput({ token: 'token-1', text: 'message-1' }) + + // Act + chatExtrasStore.purgeChatExtras('token-1') + + // Assert + expect(chatExtrasStore.absence['token-1']).not.toBeDefined() + expect(chatExtrasStore.messagesToBeReplied['token-1']).not.toBeDefined() + expect(chatExtrasStore.currentMessageInput['token-1']).not.toBeDefined() + }) + }) }) diff --git a/src/stores/chatExtras.js b/src/stores/chatExtras.js index 1b382aa0ff6..d9baa07e5a7 100644 --- a/src/stores/chatExtras.js +++ b/src/stores/chatExtras.js @@ -2,6 +2,7 @@ * @copyright Copyright (c) 2023 Maksim Sukharev * * @author Maksim Sukharev + * @author Marco Ambrosini * * @license AGPL-3.0-or-later * @@ -25,11 +26,42 @@ import Vue from 'vue' import { getUserAbsence } from '../services/participantsService.js' +/** + * @typedef {string} Token + */ + +/** + * @typedef {object} State + * @property {{[key: Token]: object}} absence - The absence status per conversation. + * @property {{[key: Token]: number}} messagesToBeReplied - The parent message id to reply per conversation. + * @property {{[key: Token]: string}} currentMessageInput -The input value per conversation. + */ + +/** + * Store for conversation extra chat features apart from messages + * + * @param {string} id store name + * @param {State} options.state store state structure + */ export const useChatExtrasStore = defineStore('chatExtras', { state: () => ({ absence: {}, + messagesToBeReplied: {}, + currentMessageInput: {}, }), + getters: { + getMessageToBeReplied: (state) => (token) => { + if (state.messagesToBeReplied[token]) { + return state.messagesToBeReplied[token] + } + }, + + getCurrentMessageInput: (state) => (token) => { + return state.currentMessageInput[token] ?? '' + }, + }, + actions: { /** * Fetch an absence status for user and save to store @@ -64,5 +96,63 @@ export const useChatExtrasStore = defineStore('chatExtras', { Vue.delete(this.absence, token) } }, + + /** + * Add a reply message id to the store + * + * @param {object} payload action payload + * @param {string} payload.token The conversation token + * @param {number} payload.id The id of message + */ + addMessageToBeReplied({ token, id }) { + Vue.set(this.messagesToBeReplied, token, id) + }, + + /** + * Removes a reply message id from the store + * (after posting message or dismissing the operation) + * + * @param {string} token The conversation token + */ + removeMessageToBeReplied(token) { + Vue.delete(this.messagesToBeReplied, token) + }, + + /** + * Add a current input value to the store for a given conversation token + * + * @param {object} payload action payload + * @param {string} payload.token The conversation token + * @param {string} payload.text The string to store + */ + setCurrentMessageInput({ token, text }) { + // FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492 + const temp = document.createElement('textarea') + temp.innerHTML = text.replace(/&/gmi, '&') + const parsedText = temp.value.replace(/&/gmi, '&').replace(/</gmi, '<') + .replace(/>/gmi, '>').replace(/§/gmi, '§') + + Vue.set(this.currentMessageInput, token, parsedText) + }, + + /** + * Remove a current input value from the store for a given conversation token + * + * @param {string} token The conversation token + */ + removeCurrentMessageInput(token) { + Vue.delete(this.currentMessageInput, token) + }, + + /** + * Clears store for a deleted conversation + * + * @param {string} token the token of the conversation to be deleted + */ + purgeChatExtras(token) { + this.removeMessageToBeReplied(token) + this.removeUserAbsence(token) + this.removeCurrentMessageInput(token) + }, }, }) From e80b2495f5725abd9d06e7184b8ec0d1e5629ce2 Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Tue, 5 Dec 2023 18:32:07 +0100 Subject: [PATCH 3/4] fix(store): migrate usage, remove old store Signed-off-by: Maksim Sukharev --- .../MessagesGroup/Message/Message.vue | 12 +- .../MessageButtonsBar.spec.js | 2 - src/components/NewMessage/NewMessage.vue | 16 +- src/components/Quote.vue | 14 +- src/store/conversationsStore.js | 7 +- src/store/conversationsStore.spec.js | 2 + src/store/messagesStore.js | 4 +- src/store/messagesStore.spec.js | 11 +- src/store/quoteReplyStore.js | 142 ------------------ src/store/quoteReplyStore.spec.js | 79 ---------- src/store/storeConfig.js | 2 - 11 files changed, 41 insertions(+), 250 deletions(-) delete mode 100644 src/store/quoteReplyStore.js delete mode 100644 src/store/quoteReplyStore.spec.js diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue index 6cadb7666f3..0ea28aa3604 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.vue +++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue @@ -283,6 +283,7 @@ import { useIsInCall } from '../../../../composables/useIsInCall.js' import { ATTENDEE, CONVERSATION, PARTICIPANT } from '../../../../constants.js' import participant from '../../../../mixins/participant.js' import { EventBus } from '../../../../services/EventBus.js' +import { useChatExtrasStore } from '../../../../stores/chatExtras.js' import { useGuestNameStore } from '../../../../stores/guestName.js' import { getItemTypeFromMessage } from '../../../../utils/getItemTypeFromMessage.js' @@ -467,8 +468,15 @@ export default { setup() { const isInCall = useIsInCall() + const chatExtrasStore = useChatExtrasStore() const guestNameStore = useGuestNameStore() - return { isInCall, isTranslationAvailable, guestNameStore } + + return { + isInCall, + isTranslationAvailable, + chatExtrasStore, + guestNameStore + } }, expose: ['highlightMessage'], @@ -881,7 +889,7 @@ export default { }, handleReply() { - this.$store.dispatch('addMessageToBeReplied', { + this.chatExtrasStore.addMessageToBeReplied({ token: this.token, id: this.id, }) diff --git a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js index b799329ea47..38b32dfe4c8 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js +++ b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageButtonsBar.spec.js @@ -106,8 +106,6 @@ describe('MessageButtonsBar.vue', () => { describe('reply action', () => { test('replies to message', async () => { - const replyAction = jest.fn() - testStoreConfig.modules.quoteReplyStore.actions.addMessageToBeReplied = replyAction store = new Store(testStoreConfig) const wrapper = shallowMount(MessageButtonsBar, { diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index 2a9bc363aa8..4afd3c488ca 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -332,7 +332,7 @@ export default { }, messageToBeReplied() { - const parentId = this.$store.getters.getMessageToBeReplied(this.token) + const parentId = this.chatExtrasStore.getMessageToBeReplied(this.token) return parentId && this.$store.getters.message(this.token, parentId) }, @@ -403,14 +403,14 @@ export default { }, text(newValue) { - this.$store.dispatch('setCurrentMessageInput', { token: this.token, text: newValue }) + this.chatExtrasStore.setCurrentMessageInput({ token: this.token, text: newValue }) }, token: { immediate: true, handler(token) { if (token) { - this.text = this.$store.getters.currentMessageInput(token) + this.text = this.chatExtrasStore.getCurrentMessageInput(token) } else { this.text = '' } @@ -426,7 +426,7 @@ export default { EventBus.$on('upload-start', this.handleUploadSideEffects) EventBus.$on('upload-discard', this.handleUploadSideEffects) EventBus.$on('retry-message', this.handleRetryMessage) - this.text = this.$store.getters.currentMessageInput(this.token) + this.text = this.chatExtrasStore.getCurrentMessageInput(this.token) if (!this.$store.getters.areFileTemplatesInitialised) { this.$store.dispatch('getFileTemplates') @@ -484,7 +484,7 @@ export default { } this.$nextTick(() => { // reset or fill main input in chat view from the store - this.text = this.$store.getters.currentMessageInput(this.token) + this.text = this.chatExtrasStore.getCurrentMessageInput(this.token) // refocus input as the user might want to type further this.focusInput() }) @@ -516,7 +516,7 @@ export default { if (this.upload) { // Clear input content from store - this.$store.dispatch('setCurrentMessageInput', { token: this.token, text: '' }) + this.chatExtrasStore.setCurrentMessageInput({ token: this.token, text: '' }) if (this.$store.getters.getInitialisedUploads(this.$store.getters.currentUploadId).length) { // If dialog contains files to upload, delegate sending @@ -539,7 +539,7 @@ export default { // Scrolls the message list to the last added message EventBus.$emit('smooth-scroll-chat-to-bottom') // Also remove the message to be replied for this conversation - await this.$store.dispatch('removeMessageToBeReplied', this.token) + this.chatExtrasStore.removeMessageToBeReplied(this.token) this.broadcast ? await this.broadcastMessage(temporaryMessage, options) @@ -592,7 +592,7 @@ export default { // Restore the parent/quote message if (temporaryMessage.parent) { - this.$store.dispatch('addMessageToBeReplied', { + this.chatExtrasStore.addMessageToBeReplied({ token: this.token, id: temporaryMessage.parent.id, }) diff --git a/src/components/Quote.vue b/src/components/Quote.vue index 8e3eb56e1e3..587cfd11d8f 100644 --- a/src/components/Quote.vue +++ b/src/components/Quote.vue @@ -76,6 +76,7 @@ import FilePreview from './MessagesList/MessagesGroup/Message/MessagePart/FilePr import { AVATAR } from '../constants.js' import { EventBus } from '../services/EventBus.js' +import { useChatExtrasStore } from '../stores/chatExtras.js' export default { name: 'Quote', @@ -143,7 +144,12 @@ export default { }, setup() { - return { AVATAR } + const chatExtrasStore = useChatExtrasStore() + + return { + AVATAR, + chatExtrasStore, + } }, computed: { @@ -243,12 +249,8 @@ export default { }, }, methods: { - /** - * Stops the quote-reply operation by removing the MessageToBeReplied from - * the quoteReplyStore. - */ handleAbortReply() { - this.$store.dispatch('removeMessageToBeReplied', this.token) + this.chatExtrasStore.removeMessageToBeReplied(this.token) EventBus.$emit('focus-chat-input') }, diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js index a66c7c20e3d..4faeb00c63b 100644 --- a/src/store/conversationsStore.js +++ b/src/store/conversationsStore.js @@ -65,6 +65,7 @@ import { stopCallRecording, } from '../services/recordingService.js' import { talkBroadcastChannel } from '../services/talkBroadcastChannel.js' +import { useChatExtrasStore } from '../stores/chatExtras.js' const DUMMY_CONVERSATION = { token: '', @@ -317,10 +318,12 @@ const actions = { * Delete a conversation from the store. * * @param {object} context default store context; - * @param {object} token the token of the conversation to be deleted; + * @param {string} token the token of the conversation to be deleted; */ deleteConversation(context, token) { // FIXME: rename to deleteConversationsFromStore or a better name + const chatExtrasStore = useChatExtrasStore() + chatExtrasStore.purgeChatExtras(token) context.dispatch('deleteMessages', token) context.commit('deleteConversation', token) }, @@ -431,6 +434,8 @@ const actions = { async clearConversationHistory(context, { token }) { try { const response = await clearConversationHistory(token) + const chatExtrasStore = useChatExtrasStore() + chatExtrasStore.removeMessageToBeReplied(token) context.dispatch('deleteMessages', token) return response } catch (error) { diff --git a/src/store/conversationsStore.spec.js b/src/store/conversationsStore.spec.js index 3aec85de249..958f119e882 100644 --- a/src/store/conversationsStore.spec.js +++ b/src/store/conversationsStore.spec.js @@ -1,6 +1,7 @@ import { createLocalVue } from '@vue/test-utils' import flushPromises from 'flush-promises' import { cloneDeep } from 'lodash' +import { createPinia, setActivePinia } from 'pinia' import Vuex from 'vuex' import { emit } from '@nextcloud/event-bus' @@ -82,6 +83,7 @@ describe('conversationsStore', () => { beforeEach(() => { localVue = createLocalVue() localVue.use(Vuex) + setActivePinia(createPinia()) testConversation = { token: testToken, diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index 25a2fb18da8..82673e8d7c4 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -43,6 +43,7 @@ import { addReactionToMessage, removeReactionFromMessage, } from '../services/messagesService.js' +import { useChatExtrasStore } from '../stores/chatExtras.js' import { useGuestNameStore } from '../stores/guestName.js' import { useSharedItemsStore } from '../stores/sharedItems.js' import CancelableRequest from '../utils/cancelableRequest.js' @@ -617,7 +618,8 @@ const actions = { * @return {object} temporary message */ createTemporaryMessage(context, { text, token, uploadId, index, file, localUrl, isVoiceMessage }) { - const parentId = context.getters.getMessageToBeReplied(token) + const chatExtrasStore = useChatExtrasStore() + const parentId = chatExtrasStore.getMessageToBeReplied(token) const parent = parentId && context.getters.message(token, parentId) const date = new Date() let tempId = 'temp-' + date.getTime() diff --git a/src/store/messagesStore.spec.js b/src/store/messagesStore.spec.js index ad2cedf29f1..4c650b9a0fb 100644 --- a/src/store/messagesStore.spec.js +++ b/src/store/messagesStore.spec.js @@ -25,6 +25,7 @@ import { postNewMessage, postRichObjectToConversation, } from '../services/messagesService.js' +import { useChatExtrasStore } from '../stores/chatExtras.js' import { useGuestNameStore } from '../stores/guestName.js' import { generateOCSErrorResponse, generateOCSResponse } from '../test-helpers.js' import CancelableRequest from '../utils/cancelableRequest.js' @@ -318,10 +319,10 @@ describe('messagesStore', () => { describe('temporary messages', () => { let mockDate - let getMessageToBeRepliedMock let getActorIdMock let getActorTypeMock let getDisplayNameMock + let chatExtraStore beforeEach(() => { mockDate = new Date('2020-01-01 20:00:00') @@ -329,12 +330,11 @@ describe('messagesStore', () => { .mockImplementation(() => mockDate) testStoreConfig = cloneDeep(messagesStore) + chatExtraStore = useChatExtrasStore() - getMessageToBeRepliedMock = jest.fn().mockReturnValue(() => undefined) getActorIdMock = jest.fn().mockReturnValue(() => 'actor-id-1') getActorTypeMock = jest.fn().mockReturnValue(() => ATTENDEE.ACTOR_TYPE.USERS) getDisplayNameMock = jest.fn().mockReturnValue(() => 'actor-display-name-1') - testStoreConfig.getters.getMessageToBeReplied = getMessageToBeRepliedMock testStoreConfig.getters.getActorId = getActorIdMock testStoreConfig.getters.getActorType = getActorTypeMock testStoreConfig.getters.getDisplayName = getDisplayNameMock @@ -353,7 +353,6 @@ describe('messagesStore', () => { localUrl: null, }) - expect(getMessageToBeRepliedMock).toHaveBeenCalled() expect(getActorIdMock).toHaveBeenCalled() expect(getActorTypeMock).toHaveBeenCalled() expect(getDisplayNameMock).toHaveBeenCalled() @@ -384,9 +383,7 @@ describe('messagesStore', () => { } store.dispatch('processMessage', parent) - - getMessageToBeRepliedMock.mockReset() - getMessageToBeRepliedMock.mockReturnValue(() => (123)) + chatExtraStore.addMessageToBeReplied({ token: TOKEN, id: 123 }) const temporaryMessage = await store.dispatch('createTemporaryMessage', { text: 'blah', diff --git a/src/store/quoteReplyStore.js b/src/store/quoteReplyStore.js deleted file mode 100644 index 7c4325bf6c3..00000000000 --- a/src/store/quoteReplyStore.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @copyright Copyright (c) 2019 Marco Ambrosini - * - * @author Marco Ambrosini - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -import Vue from 'vue' - -const state = { - messagesToBeReplied: {}, - - /** - * Cached last message input by conversation token - */ - currentMessageInput: {}, -} - -const getters = { - getMessageToBeReplied: (state) => (token) => { - if (state.messagesToBeReplied[token]) { - return state.messagesToBeReplied[token] - } - }, - - currentMessageInput: (state) => (token) => { - return state.currentMessageInput[token] ?? '' - }, -} - -const mutations = { - /** - * Add a message to be replied to the store. This message is generated when the - * reply button is clicked. - * - * @param {object} state current store state; - * @param {object} message The message to be replied; - * @param {string} message.token The conversation token; - * @param {number} message.id The id of message; - */ - addMessageToBeReplied(state, { token, id }) { - Vue.set(state.messagesToBeReplied, token, id) - }, - /** - * Removes message to be replied from the store for the - * given conversation. - * - * @param {object} state current store state; - * @param {string} token The conversation token - */ - removeMessageToBeReplied(state, token) { - Vue.delete(state.messagesToBeReplied, token) - }, - - /** - * Sets the current message input for a given conversation - * - * @param {object} state Current store state; - * @param {object} data the wrapping object; - * @param {string} data.token The conversation token; - * @param {string} data.text Message text to set or null to clear it; - */ - setCurrentMessageInput(state, { token, text = null }) { - if (text !== null) { - // FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492 - const temp = document.createElement('textarea') - temp.innerHTML = text?.replace(/&/gmi, '&') || '' - const parsedText = temp.value.replace(/&/gmi, '&').replace(/</gmi, '<') - .replace(/>/gmi, '>').replace(/§/gmi, '§') - - Vue.set(state.currentMessageInput, token, parsedText) - } else { - Vue.delete(state.currentMessageInput, token) - } - }, -} - -const actions = { - - /** - * Add a message to be replied to the store. This message is generated when the - * reply button is clicked. - * - * @param {object} context default store context; - * @param {object} messageToBeReplied The message to be replied; - */ - addMessageToBeReplied(context, messageToBeReplied) { - context.commit('addMessageToBeReplied', messageToBeReplied) - }, - - /** - * Remove a message to be replied to the store. This is used either when the message - * has been replied to or the user finally decides to dismiss the reply operation. - * - * @param {object} context default store context; - * @param {object} token The token of the conversation whose message to be replied is - * being removed; - */ - removeMessageToBeReplied(context, token) { - context.commit('removeMessageToBeReplied', token) - }, - - /** - * Clears current messages from a deleted conversation - * - * @param {object} context default store context; - * @param {string} token the token of the conversation to be deleted; - */ - deleteMessages(context, token) { - context.commit('removeMessageToBeReplied', token) - context.commit('setCurrentMessageInput', { token, text: null }) - }, - - /** - * Stores the current message input for a given conversation - * - * @param {object} context default store context; - * @param {object} data the wrapping object; - * @param {string} data.token the token of the conversation to be deleted; - * @param {string} data.text string to set or null to clear it; - */ - setCurrentMessageInput(context, { token, text }) { - context.commit('setCurrentMessageInput', { token, text }) - }, -} - -export default { state, mutations, getters, actions } diff --git a/src/store/quoteReplyStore.spec.js b/src/store/quoteReplyStore.spec.js deleted file mode 100644 index b41b77081db..00000000000 --- a/src/store/quoteReplyStore.spec.js +++ /dev/null @@ -1,79 +0,0 @@ -import { createLocalVue } from '@vue/test-utils' -import { cloneDeep } from 'lodash' -import Vuex from 'vuex' - -import quoteReplyStore from './quoteReplyStore.js' - -describe('quoteReplyStore', () => { - let localVue = null - let store = null - - beforeEach(() => { - localVue = createLocalVue() - localVue.use(Vuex) - - // eslint-disable-next-line import/no-named-as-default-member - store = new Vuex.Store(cloneDeep(quoteReplyStore)) - }) - - afterEach(() => { - jest.clearAllMocks() - }) - - describe('message to be replied to per token', () => { - test('adds message to be replied to', () => { - store.dispatch('addMessageToBeReplied', { token: 'token-1', id: 101 }) - store.dispatch('addMessageToBeReplied', { token: 'token-2', id: 201 }) - - expect(store.getters.getMessageToBeReplied('token-1')).toBe(101) - expect(store.getters.getMessageToBeReplied('token-2')).toBe(201) - }) - - test('override message to be replied to', () => { - store.dispatch('addMessageToBeReplied', { token: 'token-1', id: 101 }) - store.dispatch('addMessageToBeReplied', { token: 'token-1', id: 201 }) - - expect(store.getters.getMessageToBeReplied('token-1')).toBe(201) - }) - - test('removes message to be replied to', () => { - store.dispatch('addMessageToBeReplied', { token: 'token-1', id: 101 }) - store.dispatch('addMessageToBeReplied', { token: 'token-2', id: 201 }) - - store.dispatch('removeMessageToBeReplied', 'token-1') - - expect(store.getters.getMessageToBeReplied('token-1')) - .not.toBeDefined() - expect(store.getters.getMessageToBeReplied('token-2')).toBe(201) - }) - }) - - describe('current input message per token', () => { - test('set current input message', () => { - store.dispatch('setCurrentMessageInput', { token: 'token-1', text: 'message-1' }) - store.dispatch('setCurrentMessageInput', { token: 'token-2', text: 'message-2' }) - - expect(store.getters.currentMessageInput('token-1')) - .toStrictEqual('message-1') - expect(store.getters.currentMessageInput('token-2')) - .toStrictEqual('message-2') - }) - - test('override current input message', () => { - store.dispatch('setCurrentMessageInput', { token: 'token-1', text: 'message-1' }) - store.dispatch('setCurrentMessageInput', { token: 'token-1', text: 'message-2' }) - - expect(store.getters.currentMessageInput('token-1')) - .toStrictEqual('message-2') - }) - - test('removes current input message', () => { - store.dispatch('setCurrentMessageInput', { token: 'token-1', text: 'message-1' }) - - store.dispatch('setCurrentMessageInput', { token: 'token-1', text: null }) - - expect(store.getters.getMessageToBeReplied('token-1')) - .not.toBeDefined() - }) - }) -}) diff --git a/src/store/storeConfig.js b/src/store/storeConfig.js index 3e671616128..e2244c72c3e 100644 --- a/src/store/storeConfig.js +++ b/src/store/storeConfig.js @@ -31,7 +31,6 @@ import messagesStore from './messagesStore.js' import newGroupConversationStore from './newGroupConversationStore.js' import participantsStore from './participantsStore.js' import pollStore from './pollStore.js' -import quoteReplyStore from './quoteReplyStore.js' import reactionsStore from './reactionsStore.js' import sidebarStore from './sidebarStore.js' import soundsStore from './soundsStore.js' @@ -50,7 +49,6 @@ export default { messagesStore, newGroupConversationStore, participantsStore, - quoteReplyStore, sidebarStore, soundsStore, talkHashStore, From 869e44c2b06a4ccda2b32f89d0b8d257450ea78c Mon Sep 17 00:00:00 2001 From: Maksim Sukharev Date: Fri, 8 Dec 2023 19:07:08 +0100 Subject: [PATCH 4/4] fix(store): rename actions Signed-off-by: Maksim Sukharev --- .../MessagesGroup/Message/Message.vue | 2 +- src/components/NewMessage/NewMessage.vue | 22 +++++------ src/components/Quote.vue | 2 +- src/store/conversationsStore.js | 2 +- src/store/messagesStore.js | 2 +- src/store/messagesStore.spec.js | 2 +- src/stores/__tests__/chatExtras.spec.js | 30 +++++++-------- src/stores/chatExtras.js | 38 +++++++++---------- 8 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/components/MessagesList/MessagesGroup/Message/Message.vue b/src/components/MessagesList/MessagesGroup/Message/Message.vue index 0ea28aa3604..0a6db4d4062 100644 --- a/src/components/MessagesList/MessagesGroup/Message/Message.vue +++ b/src/components/MessagesList/MessagesGroup/Message/Message.vue @@ -889,7 +889,7 @@ export default { }, handleReply() { - this.chatExtrasStore.addMessageToBeReplied({ + this.chatExtrasStore.setParentIdToReply({ token: this.token, id: this.id, }) diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index 4afd3c488ca..aea2051d184 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -82,8 +82,8 @@ -
- +
+
{ // reset or fill main input in chat view from the store - this.text = this.chatExtrasStore.getCurrentMessageInput(this.token) + this.text = this.chatExtrasStore.getChatInput(this.token) // refocus input as the user might want to type further this.focusInput() }) @@ -516,7 +516,7 @@ export default { if (this.upload) { // Clear input content from store - this.chatExtrasStore.setCurrentMessageInput({ token: this.token, text: '' }) + this.chatExtrasStore.setChatInput({ token: this.token, text: '' }) if (this.$store.getters.getInitialisedUploads(this.$store.getters.currentUploadId).length) { // If dialog contains files to upload, delegate sending @@ -539,7 +539,7 @@ export default { // Scrolls the message list to the last added message EventBus.$emit('smooth-scroll-chat-to-bottom') // Also remove the message to be replied for this conversation - this.chatExtrasStore.removeMessageToBeReplied(this.token) + this.chatExtrasStore.removeParentIdToReply(this.token) this.broadcast ? await this.broadcastMessage(temporaryMessage, options) @@ -592,7 +592,7 @@ export default { // Restore the parent/quote message if (temporaryMessage.parent) { - this.chatExtrasStore.addMessageToBeReplied({ + this.chatExtrasStore.setParentIdToReply({ token: this.token, id: temporaryMessage.parent.id, }) diff --git a/src/components/Quote.vue b/src/components/Quote.vue index 587cfd11d8f..56a319665e6 100644 --- a/src/components/Quote.vue +++ b/src/components/Quote.vue @@ -250,7 +250,7 @@ export default { }, methods: { handleAbortReply() { - this.chatExtrasStore.removeMessageToBeReplied(this.token) + this.chatExtrasStore.removeParentIdToReply(this.token) EventBus.$emit('focus-chat-input') }, diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js index 4faeb00c63b..6cf2bfcd201 100644 --- a/src/store/conversationsStore.js +++ b/src/store/conversationsStore.js @@ -435,7 +435,7 @@ const actions = { try { const response = await clearConversationHistory(token) const chatExtrasStore = useChatExtrasStore() - chatExtrasStore.removeMessageToBeReplied(token) + chatExtrasStore.removeParentIdToReply(token) context.dispatch('deleteMessages', token) return response } catch (error) { diff --git a/src/store/messagesStore.js b/src/store/messagesStore.js index 82673e8d7c4..b9279e955c0 100644 --- a/src/store/messagesStore.js +++ b/src/store/messagesStore.js @@ -619,7 +619,7 @@ const actions = { */ createTemporaryMessage(context, { text, token, uploadId, index, file, localUrl, isVoiceMessage }) { const chatExtrasStore = useChatExtrasStore() - const parentId = chatExtrasStore.getMessageToBeReplied(token) + const parentId = chatExtrasStore.getParentIdToReply(token) const parent = parentId && context.getters.message(token, parentId) const date = new Date() let tempId = 'temp-' + date.getTime() diff --git a/src/store/messagesStore.spec.js b/src/store/messagesStore.spec.js index 4c650b9a0fb..6194089a71f 100644 --- a/src/store/messagesStore.spec.js +++ b/src/store/messagesStore.spec.js @@ -383,7 +383,7 @@ describe('messagesStore', () => { } store.dispatch('processMessage', parent) - chatExtraStore.addMessageToBeReplied({ token: TOKEN, id: 123 }) + chatExtraStore.setParentIdToReply({ token: TOKEN, id: 123 }) const temporaryMessage = await store.dispatch('createTemporaryMessage', { text: 'blah', diff --git a/src/stores/__tests__/chatExtras.spec.js b/src/stores/__tests__/chatExtras.spec.js index 6a24bf51c95..5fdd61be0a5 100644 --- a/src/stores/__tests__/chatExtras.spec.js +++ b/src/stores/__tests__/chatExtras.spec.js @@ -77,43 +77,43 @@ describe('chatExtrasStore', () => { describe('reply message', () => { it('adds reply message id to the store', () => { // Act - chatExtrasStore.addMessageToBeReplied({ token, id: 101 }) + chatExtrasStore.setParentIdToReply({ token, id: 101 }) // Assert - expect(chatExtrasStore.getMessageToBeReplied(token)).toBe(101) + expect(chatExtrasStore.getParentIdToReply(token)).toBe(101) }) it('clears reply message', () => { // Arrange - chatExtrasStore.addMessageToBeReplied({ token, id: 101 }) + chatExtrasStore.setParentIdToReply({ token, id: 101 }) // Act - chatExtrasStore.removeMessageToBeReplied(token) + chatExtrasStore.removeParentIdToReply(token) // Assert - expect(chatExtrasStore.getMessageToBeReplied(token)).not.toBeDefined() + expect(chatExtrasStore.getParentIdToReply(token)).not.toBeDefined() }) }) describe('current input message', () => { it('sets current input message', () => { // Act - chatExtrasStore.setCurrentMessageInput({ token: 'token-1', text: 'message-1' }) + chatExtrasStore.setChatInput({ token: 'token-1', text: 'message-1' }) // Assert - expect(chatExtrasStore.getCurrentMessageInput('token-1')).toStrictEqual('message-1') + expect(chatExtrasStore.getChatInput('token-1')).toStrictEqual('message-1') }) it('clears current input message', () => { // Arrange - chatExtrasStore.setCurrentMessageInput({ token: 'token-1', text: 'message-1' }) + chatExtrasStore.setChatInput({ token: 'token-1', text: 'message-1' }) // Act - chatExtrasStore.removeCurrentMessageInput('token-1') + chatExtrasStore.removeChatInput('token-1') // Assert - expect(chatExtrasStore.currentMessageInput['token-1']).not.toBeDefined() - expect(chatExtrasStore.getCurrentMessageInput('token-1')).toBe('') + expect(chatExtrasStore.chatInput['token-1']).not.toBeDefined() + expect(chatExtrasStore.getChatInput('token-1')).toBe('') }) }) @@ -124,16 +124,16 @@ describe('chatExtrasStore', () => { getUserAbsence.mockResolvedValueOnce(response) await chatExtrasStore.getUserAbsence({ token: 'token-1', userId }) - chatExtrasStore.addMessageToBeReplied({ token: 'token-1', id: 101 }) - chatExtrasStore.setCurrentMessageInput({ token: 'token-1', text: 'message-1' }) + chatExtrasStore.setParentIdToReply({ token: 'token-1', id: 101 }) + chatExtrasStore.setChatInput({ token: 'token-1', text: 'message-1' }) // Act chatExtrasStore.purgeChatExtras('token-1') // Assert expect(chatExtrasStore.absence['token-1']).not.toBeDefined() - expect(chatExtrasStore.messagesToBeReplied['token-1']).not.toBeDefined() - expect(chatExtrasStore.currentMessageInput['token-1']).not.toBeDefined() + expect(chatExtrasStore.parentToReply['token-1']).not.toBeDefined() + expect(chatExtrasStore.chatInput['token-1']).not.toBeDefined() }) }) }) diff --git a/src/stores/chatExtras.js b/src/stores/chatExtras.js index d9baa07e5a7..bd65e366dce 100644 --- a/src/stores/chatExtras.js +++ b/src/stores/chatExtras.js @@ -33,8 +33,8 @@ import { getUserAbsence } from '../services/participantsService.js' /** * @typedef {object} State * @property {{[key: Token]: object}} absence - The absence status per conversation. - * @property {{[key: Token]: number}} messagesToBeReplied - The parent message id to reply per conversation. - * @property {{[key: Token]: string}} currentMessageInput -The input value per conversation. + * @property {{[key: Token]: number}} parentToReply - The parent message id to reply per conversation. + * @property {{[key: Token]: string}} chatInput -The input value per conversation. */ /** @@ -46,19 +46,19 @@ import { getUserAbsence } from '../services/participantsService.js' export const useChatExtrasStore = defineStore('chatExtras', { state: () => ({ absence: {}, - messagesToBeReplied: {}, - currentMessageInput: {}, + parentToReply: {}, + chatInput: {}, }), getters: { - getMessageToBeReplied: (state) => (token) => { - if (state.messagesToBeReplied[token]) { - return state.messagesToBeReplied[token] + getParentIdToReply: (state) => (token) => { + if (state.parentToReply[token]) { + return state.parentToReply[token] } }, - getCurrentMessageInput: (state) => (token) => { - return state.currentMessageInput[token] ?? '' + getChatInput: (state) => (token) => { + return state.chatInput[token] ?? '' }, }, @@ -104,8 +104,8 @@ export const useChatExtrasStore = defineStore('chatExtras', { * @param {string} payload.token The conversation token * @param {number} payload.id The id of message */ - addMessageToBeReplied({ token, id }) { - Vue.set(this.messagesToBeReplied, token, id) + setParentIdToReply({ token, id }) { + Vue.set(this.parentToReply, token, id) }, /** @@ -114,8 +114,8 @@ export const useChatExtrasStore = defineStore('chatExtras', { * * @param {string} token The conversation token */ - removeMessageToBeReplied(token) { - Vue.delete(this.messagesToBeReplied, token) + removeParentIdToReply(token) { + Vue.delete(this.parentToReply, token) }, /** @@ -125,14 +125,14 @@ export const useChatExtrasStore = defineStore('chatExtras', { * @param {string} payload.token The conversation token * @param {string} payload.text The string to store */ - setCurrentMessageInput({ token, text }) { + setChatInput({ token, text }) { // FIXME upstream: https://github.com/nextcloud-libraries/nextcloud-vue/issues/4492 const temp = document.createElement('textarea') temp.innerHTML = text.replace(/&/gmi, '&') const parsedText = temp.value.replace(/&/gmi, '&').replace(/</gmi, '<') .replace(/>/gmi, '>').replace(/§/gmi, '§') - Vue.set(this.currentMessageInput, token, parsedText) + Vue.set(this.chatInput, token, parsedText) }, /** @@ -140,8 +140,8 @@ export const useChatExtrasStore = defineStore('chatExtras', { * * @param {string} token The conversation token */ - removeCurrentMessageInput(token) { - Vue.delete(this.currentMessageInput, token) + removeChatInput(token) { + Vue.delete(this.chatInput, token) }, /** @@ -150,9 +150,9 @@ export const useChatExtrasStore = defineStore('chatExtras', { * @param {string} token the token of the conversation to be deleted */ purgeChatExtras(token) { - this.removeMessageToBeReplied(token) + this.removeParentIdToReply(token) this.removeUserAbsence(token) - this.removeCurrentMessageInput(token) + this.removeChatInput(token) }, }, })