diff --git a/src/collections.js b/src/collections.js index 037381e5dc0..67aacfb1fab 100644 --- a/src/collections.js +++ b/src/collections.js @@ -41,25 +41,28 @@ import Vue from 'vue' container.id = 'spreed-room-select' const body = document.getElementById('body-user') body.appendChild(container) + const RoomSelector = () => import('./components/RoomSelector.vue') const ComponentVM = new Vue({ + el: container, render: h => h(RoomSelector, { props: { // Even if it is used from Talk the Collections menu is // independently loaded, so the properties that depend // on the store need to be explicitly injected. container: window.store ? window.store.getters.getMainContainerSelector() : undefined, + isPlugin: true, }, }), }) - ComponentVM.$mount(container) + ComponentVM.$root.$on('close', () => { ComponentVM.$el.remove() ComponentVM.$destroy() reject(new Error('User cancelled resource selection')) }) - ComponentVM.$root.$on('select', (id) => { - resolve(id) + ComponentVM.$root.$on('select', ({ token }) => { + resolve(token) ComponentVM.$el.remove() ComponentVM.$destroy() }) diff --git a/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue b/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue index bec14b91f78..9e70bbb1d68 100644 --- a/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue +++ b/src/components/LeftSidebar/ConversationsList/ConversationSearchResult.vue @@ -24,7 +24,7 @@ console.debug(`Error while pushing the new conversation's route: ${err}`)) this.closeModal() diff --git a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageForwarder.vue b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageForwarder.vue index 09ebdbd28e9..a9120215f36 100644 --- a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageForwarder.vue +++ b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/MessageForwarder.vue @@ -59,6 +59,7 @@ import Check from 'vue-material-design-icons/Check.vue' import { showError } from '@nextcloud/dialogs' +import { generateUrl } from '@nextcloud/router' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js' @@ -93,6 +94,7 @@ export default { data() { return { selectedConversationToken: null, + selectedConversationName: null, showForwardedConfirmation: false, forwardedMessageID: '', } @@ -110,16 +112,12 @@ export default { dialogSubtitle() { return t('spreed', 'Choose a conversation to forward the selected message.') }, - - selectedConversationName() { - return this.$store.getters?.conversation(this.selectedConversationToken).displayName - }, - }, methods: { - async setSelectedConversationToken(token) { - this.selectedConversationToken = token + async setSelectedConversationToken(conversation) { + this.selectedConversationToken = conversation.token + this.selectedConversationName = conversation.displayName try { const response = await this.$store.dispatch('forwardMessage', { targetToken: this.selectedConversationToken, @@ -134,17 +132,26 @@ export default { }, openConversation() { + if (window.location.href.includes('/apps/files')) { + // Native redirect to Talk from Files sidebar + const url = generateUrl('/call/{token}#message_{messageId}', { + token: this.selectedConversationToken, + messageId: this.forwardedMessageID, + }) + window.open(url, '_blank').focus() + } else { + this.$router.push({ + name: 'conversation', + hash: `#message_${this.forwardedMessageID}`, + params: { + token: `${this.selectedConversationToken}`, + }, + }).catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`)) + } - this.$router.push({ - name: 'conversation', - hash: `#message_${this.forwardedMessageID}`, - params: { - token: `${this.selectedConversationToken}`, - }, - }) - .catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`)) this.showForwardedConfirmation = false this.forwardedMessageID = '' + this.$emit('close') }, handleClose() { diff --git a/src/components/RoomSelector.spec.js b/src/components/RoomSelector.spec.js index 9b62877fb8a..1cba279e56a 100644 --- a/src/components/RoomSelector.spec.js +++ b/src/components/RoomSelector.spec.js @@ -218,7 +218,7 @@ describe('RoomSelector', () => { // Arrange const wrapper = await mountRoomSelector() const eventHandler = jest.fn() - wrapper.vm.$root.$on('select', eventHandler) + wrapper.vm.$on('select', eventHandler) // Act: click on second item, then click 'Select conversation' const list = wrapper.findComponent({ name: 'ConversationsSearchListVirtual' }) @@ -229,13 +229,27 @@ describe('RoomSelector', () => { await wrapper.findComponent(NcButton).vm.$emit('click') // Assert - expect(eventHandler).toHaveBeenCalledWith('token-3') + expect(eventHandler).toHaveBeenCalledWith(conversations[0]) }) it('emits close event', async () => { // Arrange const wrapper = await mountRoomSelector() const eventHandler = jest.fn() + wrapper.vm.$on('close', eventHandler) + + // Act: close modal + const modal = wrapper.findComponent({ name: 'NcModal' }) + await modal.vm.$emit('close') + + // Assert + expect(eventHandler).toHaveBeenCalled() + }) + + it('emits close event on $root as plugin', async () => { + // Arrange + const wrapper = await mountRoomSelector({ isPlugin: true }) + const eventHandler = jest.fn() wrapper.vm.$root.$on('close', eventHandler) // Act: close modal diff --git a/src/components/RoomSelector.vue b/src/components/RoomSelector.vue index bf99c58f516..243e66a468e 100644 --- a/src/components/RoomSelector.vue +++ b/src/components/RoomSelector.vue @@ -124,6 +124,14 @@ export default { type: Boolean, default: false, }, + + /** + * Whether component is used as plugin and should emit on $root. + */ + isPlugin: { + type: Boolean, + default: false, + }, }, emits: ['close', 'select'], @@ -201,19 +209,23 @@ export default { }, close() { - // FIXME: should not emit on $root but on itself - this.$root.$emit('close') - this.$emit('close') + if (this.isPlugin) { + this.$root.$emit('close') + } else { + this.$emit('close') + } }, onSelect(item) { - this.selectedRoom = item.token + this.selectedRoom = item }, onSubmit() { - // FIXME: should not emit on $root but on itself - this.$root.$emit('select', this.selectedRoom) - this.$emit('select', this.selectedRoom) + if (this.isPlugin) { + this.$root.$emit('select', this.selectedRoom) + } else { + this.$emit('select', this.selectedRoom) + } }, }, } diff --git a/src/deck.js b/src/deck.js index 5c333f5e331..3b348643c98 100644 --- a/src/deck.js +++ b/src/deck.js @@ -28,9 +28,6 @@ import { showSuccess, showError } from '@nextcloud/dialogs' import { translate, translatePlural } from '@nextcloud/l10n' import { generateFilePath, generateUrl } from '@nextcloud/router' -import RoomSelector from './components/RoomSelector.vue' - -import { fetchConversation } from './services/conversationsService.js' import { postRichObjectToConversation } from './services/messagesService.js' import '@nextcloud/dialogs/style.css' @@ -38,25 +35,23 @@ import '@nextcloud/dialogs/style.css' (function(OC, OCA, t, n) { /** * @param {object} card The card object given by the deck app - * @param {string} token The conversation to post to + * @param {object} conversation The conversation object given by the RoomSelector + * @param {string} conversation.token The conversation token + * @param {string} conversation.displayName The conversation display name */ - async function postCardToRoom(card, token) { + async function postCardToRoom(card, { token, displayName }) { try { - const [responsePostCard, responseGetConversation] = await Promise.allSettled([ - postRichObjectToConversation(token, { - objectType: 'deck-card', - objectId: card.id, - metaData: JSON.stringify(card), - }), - fetchConversation(token), - ]) - - const messageId = responsePostCard.value.data.ocs.data.id - const conversation = responseGetConversation.value.data.ocs.data.displayName + const response = await postRichObjectToConversation(token, { + objectType: 'deck-card', + objectId: card.id, + metaData: JSON.stringify(card), + }) + + const messageId = response.data.ocs.data.id const targetUrl = generateUrl('/call/{token}#message_{messageId}', { token, messageId }) showSuccess(t('spreed', 'Deck card has been posted to {conversation}') - .replace(/\{conversation}/g, `${escapeHtml(conversation)} ↗`), + .replace(/\{conversation}/g, `${escapeHtml(displayName)} ↗`), { isHTML: true, }) @@ -87,24 +82,27 @@ import '@nextcloud/dialogs/style.css' const body = document.getElementById('body-user') body.appendChild(container) - const ComponentVM = Vue.extend(RoomSelector) - const vm = new ComponentVM({ + const RoomSelector = () => import('./components/RoomSelector.vue') + const vm = new Vue({ el: container, - propsData: { - dialogTitle: t('spreed', 'Post to conversation'), - showPostableOnly: true, - }, + render: h => h(RoomSelector, { + props: { + dialogTitle: t('spreed', 'Post to conversation'), + showPostableOnly: true, + isPlugin: true, + }, + }), }) vm.$root.$on('close', () => { vm.$el.remove() vm.$destroy() }) - vm.$root.$on('select', (token) => { + vm.$root.$on('select', (conversation) => { vm.$el.remove() vm.$destroy() - postCardToRoom(card, token) + postCardToRoom(card, conversation) }) }, }) diff --git a/src/maps.js b/src/maps.js index 77df93fbb03..847892f5c38 100644 --- a/src/maps.js +++ b/src/maps.js @@ -20,6 +20,7 @@ * */ +import escapeHtml from 'escape-html' import Vue from 'vue' import { getRequestToken } from '@nextcloud/auth' @@ -27,8 +28,6 @@ import { showSuccess, showError } from '@nextcloud/dialogs' import { translate, translatePlural } from '@nextcloud/l10n' import { generateFilePath, generateUrl } from '@nextcloud/router' -import RoomSelector from './components/RoomSelector.vue' - import { postRichObjectToConversation } from './services/messagesService.js' import '@nextcloud/dialogs/style.css' @@ -36,10 +35,11 @@ import '@nextcloud/dialogs/style.css' (function(OC, OCA, t, n) { /** * @param {object} location Geo location object - * @param {string} token Conversation token to be posted to - * @return {Promise} + * @param {object} conversation The conversation object given by the RoomSelector + * @param {string} conversation.token The conversation token + * @param {string} conversation.displayName The conversation display name */ - async function postLocationToRoom(location, token) { + async function postLocationToRoom(location, { token, displayName }) { try { const response = await postRichObjectToConversation(token, { objectType: 'geo-location', @@ -48,9 +48,10 @@ import '@nextcloud/dialogs/style.css' }) const messageId = response.data.ocs.data.id const targetUrl = generateUrl('/call/{token}#message_{messageId}', { token, messageId }) - showSuccess(t('spreed', 'Location has been posted to the selected conversation', { - link: targetUrl, - }), { + + showSuccess(t('spreed', 'Location has been posted to {conversation}') + .replace(/\{conversation}/g, `${escapeHtml(displayName)} ↗`), + { isHTML: true, }) } catch (exception) { @@ -80,24 +81,27 @@ import '@nextcloud/dialogs/style.css' const body = document.getElementById('body-user') body.appendChild(container) - const ComponentVM = Vue.extend(RoomSelector) - const vm = new ComponentVM({ + const RoomSelector = () => import('./components/RoomSelector.vue') + const vm = new Vue({ el: container, - propsData: { - dialogTitle: t('spreed', 'Share to conversation'), - showPostableOnly: true, - }, + render: h => h(RoomSelector, { + props: { + dialogTitle: t('spreed', 'Share to conversation'), + showPostableOnly: true, + isPlugin: true, + }, + }), }) vm.$root.$on('close', () => { vm.$el.remove() vm.$destroy() }) - vm.$root.$on('select', (token) => { + vm.$root.$on('select', (conversation) => { vm.$el.remove() vm.$destroy() - postLocationToRoom(location, token) + postLocationToRoom(location, conversation) }) }, })