diff options
author | Maksim Sukharev <antreesy.web@gmail.com> | 2024-11-17 15:34:46 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-17 15:34:46 +0100 |
commit | 71f9d6c06d3c51429da60271ba610e2307acce1a (patch) | |
tree | b0ca61813b64999b3388e67efbd6d5394b448b30 | |
parent | aade00d5b4ec870696486e6174c001607e6c57d0 (diff) | |
parent | 3071d4dab62ebc974b296197bc68e84a0ed5100f (diff) |
Merge pull request #13784 from nextcloud/fix/noid/temp-message-utilmain
-rw-r--r-- | src/components/NewMessage/NewMessage.vue | 11 | ||||
-rw-r--r-- | src/components/NewMessage/NewMessageAudioRecorder.vue | 2 | ||||
-rw-r--r-- | src/components/NewMessage/NewMessageUploadEditor.vue | 4 | ||||
-rw-r--r-- | src/composables/useTemporaryMessage.ts | 37 | ||||
-rw-r--r-- | src/store/fileUploadStore.js | 21 | ||||
-rw-r--r-- | src/store/fileUploadStore.spec.js | 71 | ||||
-rw-r--r-- | src/store/messagesStore.js | 60 | ||||
-rw-r--r-- | src/store/messagesStore.spec.js | 243 | ||||
-rw-r--r-- | src/utils/__tests__/prepareTemporaryMessage.spec.js | 124 | ||||
-rw-r--r-- | src/utils/prepareTemporaryMessage.ts | 95 |
10 files changed, 343 insertions, 325 deletions
diff --git a/src/components/NewMessage/NewMessage.vue b/src/components/NewMessage/NewMessage.vue index a8b85b48f9..4268937afc 100644 --- a/src/components/NewMessage/NewMessage.vue +++ b/src/components/NewMessage/NewMessage.vue @@ -205,6 +205,7 @@ import PollDraftHandler from '../PollViewer/PollDraftHandler.vue' import Quote from '../Quote.vue' import { useChatMentions } from '../../composables/useChatMentions.ts' +import { useTemporaryMessage } from '../../composables/useTemporaryMessage.ts' import { CONVERSATION, PARTICIPANT, PRIVACY } from '../../constants.js' import BrowserStorage from '../../services/BrowserStorage.js' import { getTalkConfig, hasTalkFeature } from '../../services/CapabilitiesManager.ts' @@ -293,6 +294,7 @@ export default { const { token } = toRefs(props) const supportTypingStatus = getTalkConfig(token.value, 'chat', 'typing-privacy') !== undefined const { autoComplete, userData } = useChatMentions(token) + const { createTemporaryMessage } = useTemporaryMessage() return { breakoutRoomsStore: useBreakoutRoomsStore(), @@ -301,6 +303,7 @@ export default { supportTypingStatus, autoComplete, userData, + createTemporaryMessage, } }, @@ -676,8 +679,8 @@ export default { } if (this.hasText) { - const temporaryMessage = await this.$store.dispatch('createTemporaryMessage', { - text: this.text.trim(), + const temporaryMessage = this.createTemporaryMessage({ + message: this.text.trim(), token: this.token, }) this.text = '' @@ -835,7 +838,7 @@ export default { * @param {boolean} rename whether to rename the files * @param {boolean} isVoiceMessage indicates whether the file is a voice message */ - async handleFiles(files, rename = false, isVoiceMessage = false) { + handleFiles(files, rename = false, isVoiceMessage = false) { if (!this.canUploadFiles) { showWarning(t('spreed', 'File upload is not available in this conversation')) return @@ -843,7 +846,7 @@ export default { // Create a unique id for the upload operation const uploadId = this.currentUploadId ?? new Date().getTime() // Uploads and shares the files - await this.$store.dispatch('initialiseUpload', { files, token: this.token, uploadId, rename, isVoiceMessage }) + this.$store.dispatch('initialiseUpload', { files, token: this.token, uploadId, rename, isVoiceMessage }) }, /** diff --git a/src/components/NewMessage/NewMessageAudioRecorder.vue b/src/components/NewMessage/NewMessageAudioRecorder.vue index b78f5f4400..c13eb0e7c9 100644 --- a/src/components/NewMessage/NewMessageAudioRecorder.vue +++ b/src/components/NewMessage/NewMessageAudioRecorder.vue @@ -246,7 +246,7 @@ export default { // Generate file name const fileName = this.generateFileName() // Convert blob to file - const audioFile = new File([this.blob], fileName) + const audioFile = new File([this.blob], fileName, { type: 'audio/wav' }) this.$emit('audio-file', audioFile) this.$emit('recording', false) } diff --git a/src/components/NewMessage/NewMessageUploadEditor.vue b/src/components/NewMessage/NewMessageUploadEditor.vue index 2da4e82a4a..295e92ec87 100644 --- a/src/components/NewMessage/NewMessageUploadEditor.vue +++ b/src/components/NewMessage/NewMessageUploadEditor.vue @@ -201,9 +201,9 @@ export default { this.$refs.fileUploadInput.click() }, - async handleFileInput(event) { + handleFileInput(event) { const files = Object.values(event.target.files) - await this.$store.dispatch('initialiseUpload', { files, token: this.token, uploadId: this.currentUploadId }) + this.$store.dispatch('initialiseUpload', { files, token: this.token, uploadId: this.currentUploadId }) this.$refs.fileUploadInput.value = null }, diff --git a/src/composables/useTemporaryMessage.ts b/src/composables/useTemporaryMessage.ts new file mode 100644 index 0000000000..26260e6fc7 --- /dev/null +++ b/src/composables/useTemporaryMessage.ts @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { useStore } from './useStore.js' +import { useChatExtrasStore } from '../stores/chatExtras.js' +import { prepareTemporaryMessage } from '../utils/prepareTemporaryMessage.ts' +import type { PrepareTemporaryMessagePayload } from '../utils/prepareTemporaryMessage.ts' + +/** + * Composable to generate temporary messages using defined in store information + * @param context Vuex Store (to be used inside Vuex modules) + */ +export function useTemporaryMessage(context: unknown) { + const store = context ?? useStore() + const chatExtrasStore = useChatExtrasStore() + + /** + * @param payload payload for generating a temporary message + */ + function createTemporaryMessage(payload: PrepareTemporaryMessagePayload) { + const parentId = chatExtrasStore.getParentIdToReply(payload.token) + + return prepareTemporaryMessage({ + ...payload, + actorId: store.getters.getActorId(), + actorType: store.getters.getActorType(), + actorDisplayName: store.getters.getDisplayName(), + parent: parentId && store.getters.message(payload.token, parentId), + }) + } + + return { + createTemporaryMessage, + } +} diff --git a/src/store/fileUploadStore.js b/src/store/fileUploadStore.js index 2eee36f9ea..754d65cd37 100644 --- a/src/store/fileUploadStore.js +++ b/src/store/fileUploadStore.js @@ -11,6 +11,7 @@ import { t } from '@nextcloud/l10n' import moment from '@nextcloud/moment' import { getUploader } from '@nextcloud/upload' +import { useTemporaryMessage } from '../composables/useTemporaryMessage.ts' import { SHARED_ITEM } from '../constants.js' import { getDavClient } from '../services/DavClient.js' import { EventBus } from '../services/EventBus.ts' @@ -227,9 +228,10 @@ const actions = { * @param {boolean} data.rename whether to rename the files (usually after pasting) * @param {boolean} data.isVoiceMessage whether the file is a voice recording */ - async initialiseUpload({ commit, dispatch }, { uploadId, token, files, rename = false, isVoiceMessage }) { + initialiseUpload(context, { uploadId, token, files, rename = false, isVoiceMessage }) { // Set last upload id - commit('setCurrentUploadId', uploadId) + context.commit('setCurrentUploadId', uploadId) + const { createTemporaryMessage } = useTemporaryMessage(context) for (let i = 0; i < files.length; i++) { const file = files[i] @@ -249,11 +251,17 @@ const actions = { const date = new Date() const index = 'temp_' + date.getTime() + Math.random() // Create temporary message for the file and add it to the message list - const temporaryMessage = await dispatch('createTemporaryMessage', { - text: '{file}', token, uploadId, index, file, localUrl, isVoiceMessage, + const temporaryMessage = createTemporaryMessage({ + message: '{file}', + token, + uploadId, + index, + file, + localUrl, + messageType: isVoiceMessage ? 'voice-message' : 'comment', }) console.debug('temporarymessage: ', temporaryMessage, 'uploadId', uploadId) - commit('addFileToBeUploaded', { file, temporaryMessage, localUrl, token }) + context.commit('addFileToBeUploaded', { file, temporaryMessage, localUrl, token }) } }, @@ -442,7 +450,8 @@ const actions = { const [index, shareableFile] = share const { id, messageType, parent, referenceId } = shareableFile.temporaryMessage || {} - const metadata = JSON.stringify(Object.assign({ messageType }, + const metadata = JSON.stringify(Object.assign( + messageType !== 'comment' ? { messageType } : {}, caption && index === lastIndex ? { caption } : {}, options?.silent ? { silent: options.silent } : {}, parent ? { replyTo: parent.id } : {}, diff --git a/src/store/fileUploadStore.spec.js b/src/store/fileUploadStore.spec.js index 6dcf8fb192..37e75ab833 100644 --- a/src/store/fileUploadStore.spec.js +++ b/src/store/fileUploadStore.spec.js @@ -44,31 +44,11 @@ describe('fileUploadStore', () => { let mockedActions = null beforeEach(() => { - let temporaryMessageCount = 0 - localVue = createLocalVue() localVue.use(Vuex) setActivePinia(createPinia()) mockedActions = { - createTemporaryMessage: jest.fn() - .mockImplementation((context, { file, index, uploadId, localUrl, token }) => { - temporaryMessageCount += 1 - return { - id: temporaryMessageCount, - referenceId: 'reference-id-' + temporaryMessageCount, - token, - messageParameters: { - file: { - uploadId, - index, - token, - localUrl, - file, - }, - }, - } - }), addTemporaryMessage: jest.fn(), markTemporaryMessageAsFailed: jest.fn(), } @@ -78,6 +58,9 @@ describe('fileUploadStore', () => { storeConfig = cloneDeep(fileUploadStore) storeConfig.actions = Object.assign(storeConfig.actions, mockedActions) storeConfig.getters.getUserId = jest.fn().mockReturnValue(() => 'current-user') + storeConfig.getters.getActorId = jest.fn().mockReturnValue(() => 'current-user') + storeConfig.getters.getActorType = jest.fn().mockReturnValue(() => 'users') + storeConfig.getters.getDisplayName = jest.fn().mockReturnValue(() => 'Current User') }) afterEach(() => { @@ -103,7 +86,7 @@ describe('fileUploadStore', () => { restoreConsole() }) - test('initialises upload for given files', async () => { + test('initialises upload for given files', () => { const files = [ { name: 'pngimage.png', @@ -126,7 +109,7 @@ describe('fileUploadStore', () => { ] const localUrls = ['local-url:pngimage.png', 'local-url:jpgimage.jpg', undefined] - await store.dispatch('initialiseUpload', { + store.dispatch('initialiseUpload', { uploadId: 'upload-id1', token: 'XXTOKENXX', files, @@ -135,10 +118,16 @@ describe('fileUploadStore', () => { const uploads = store.getters.getInitialisedUploads('upload-id1') expect(uploads).toHaveLength(files.length) - for (const index in files) { - expect(mockedActions.createTemporaryMessage.mock.calls[index][1]).toMatchObject({ - text: '{file}', + for (const index in uploads) { + expect(uploads[index][1].temporaryMessage).toMatchObject({ + message: '{file}', token: 'XXTOKENXX', + }) + expect(uploads[index][1].temporaryMessage.messageParameters.file).toMatchObject({ + type: 'file', + mimetype: files[index].type, + id: uploads[index][1].temporaryMessage.id, + name: files[index].name, uploadId: 'upload-id1', index: expect.anything(), file: files[index], @@ -155,7 +144,7 @@ describe('fileUploadStore', () => { lastModified: Date.UTC(2021, 3, 27, 15, 30, 0), } - await store.dispatch('initialiseUpload', { + store.dispatch('initialiseUpload', { uploadId: 'upload-id1', token: 'XXTOKENXX', files: [file], @@ -164,6 +153,7 @@ describe('fileUploadStore', () => { expect(store.getters.currentUploadId).toBe('upload-id1') const uniqueFileName = '/Talk/' + file.name + 'uniq' + const referenceId = store.getters.getUploadsArray('upload-id1')[0][1].temporaryMessage.referenceId findUniquePath.mockResolvedValueOnce({ uniquePath: uniqueFileName, suffix: 1 }) uploadMock.mockResolvedValue() shareFile.mockResolvedValue() @@ -177,7 +167,7 @@ describe('fileUploadStore', () => { expect(uploadMock).toHaveBeenCalledWith(uniqueFileName, file) expect(shareFile).toHaveBeenCalledTimes(1) - expect(shareFile).toHaveBeenCalledWith(uniqueFileName, 'XXTOKENXX', 'reference-id-1', '{"caption":"text-caption","silent":true}') + expect(shareFile).toHaveBeenCalledWith(uniqueFileName, 'XXTOKENXX', referenceId, '{"caption":"text-caption","silent":true}') expect(mockedActions.addTemporaryMessage).toHaveBeenCalledTimes(1) expect(store.getters.currentUploadId).not.toBeDefined() @@ -198,7 +188,7 @@ describe('fileUploadStore', () => { } const files = [file1, file2] - await store.dispatch('initialiseUpload', { + store.dispatch('initialiseUpload', { uploadId: 'upload-id1', token: 'XXTOKENXX', files, @@ -221,10 +211,11 @@ describe('fileUploadStore', () => { expect(findUniquePath).toHaveBeenNthCalledWith(+index + 1, client, '/files/current-user', '/Talk/' + files[index].name, undefined) expect(uploadMock).toHaveBeenNthCalledWith(+index + 1, `/Talk/${files[index].name}uniq`, files[index]) } + const referenceIds = store.getters.getUploadsArray('upload-id1').map(entry => entry[1].temporaryMessage.referenceId) expect(shareFile).toHaveBeenCalledTimes(2) - expect(shareFile).toHaveBeenNthCalledWith(1, '/Talk/' + files[0].name + 'uniq', 'XXTOKENXX', 'reference-id-1', '{}') - expect(shareFile).toHaveBeenNthCalledWith(2, '/Talk/' + files[1].name + 'uniq', 'XXTOKENXX', 'reference-id-2', '{"caption":"text-caption"}') + expect(shareFile).toHaveBeenNthCalledWith(1, '/Talk/' + files[0].name + 'uniq', 'XXTOKENXX', referenceIds[0], '{}') + expect(shareFile).toHaveBeenNthCalledWith(2, '/Talk/' + files[1].name + 'uniq', 'XXTOKENXX', referenceIds[1], '{"caption":"text-caption"}') expect(mockedActions.addTemporaryMessage).toHaveBeenCalledTimes(2) expect(store.getters.currentUploadId).not.toBeDefined() @@ -240,7 +231,7 @@ describe('fileUploadStore', () => { }, ] - await store.dispatch('initialiseUpload', { + store.dispatch('initialiseUpload', { uploadId: 'upload-id1', token: 'XXTOKENXX', files, @@ -262,7 +253,7 @@ describe('fileUploadStore', () => { expect(mockedActions.markTemporaryMessageAsFailed).toHaveBeenCalledTimes(1) expect(mockedActions.markTemporaryMessageAsFailed).toHaveBeenCalledWith(expect.anything(), { token: 'XXTOKENXX', - id: 1, + id: store.getters.getUploadsArray('upload-id1')[0][1].temporaryMessage.id, uploadId: 'upload-id1', reason: 'failed-upload' }) @@ -280,7 +271,7 @@ describe('fileUploadStore', () => { }, ] - await store.dispatch('initialiseUpload', { + store.dispatch('initialiseUpload', { uploadId: 'upload-id1', token: 'XXTOKENXX', files, @@ -303,7 +294,7 @@ describe('fileUploadStore', () => { expect(mockedActions.markTemporaryMessageAsFailed).toHaveBeenCalledTimes(1) expect(mockedActions.markTemporaryMessageAsFailed).toHaveBeenCalledWith(expect.anything(), { token: 'XXTOKENXX', - id: 1, + id: store.getters.getUploadsArray('upload-id1')[0][1].temporaryMessage.id, uploadId: 'upload-id1', reason: 'failed-share' }) @@ -327,14 +318,14 @@ describe('fileUploadStore', () => { }, ] - await store.dispatch('initialiseUpload', { + store.dispatch('initialiseUpload', { uploadId: 'upload-id1', token: 'XXTOKENXX', files, }) - // temporary message mock uses incremental id - await store.dispatch('removeFileFromSelection', 2) |