diff --git a/src/collections.js b/src/collections.js index 0be2e318331..28c90820715 100644 --- a/src/collections.js +++ b/src/collections.js @@ -3,55 +3,28 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import Vue from 'vue' +import { t } from '@nextcloud/l10n' -// eslint-disable-next-line no-unexpected-multiline -(function(OCP, OC) { +import { requestRoomSelection } from './utils/requestRoomSelection.js' - // eslint-disable-next-line - __webpack_nonce__ = btoa(OC.requestToken) - // eslint-disable-next-line - __webpack_public_path__ = OC.linkTo('spreed', 'js/') +// eslint-disable-next-line +__webpack_nonce__ = btoa(OC.requestToken) +// eslint-disable-next-line +__webpack_public_path__ = OC.linkTo('spreed', 'js/') - Vue.prototype.t = t - Vue.prototype.n = n - Vue.prototype.OC = OC - - OCP.Collaboration.registerType('room', { - action: () => { - return new Promise((resolve, reject) => { - const container = document.createElement('div') - 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.$root.$on('close', () => { - ComponentVM.$el.remove() - ComponentVM.$destroy() - reject(new Error('User cancelled resource selection')) - }) - ComponentVM.$root.$on('select', ({ token }) => { - resolve(token) - ComponentVM.$el.remove() - ComponentVM.$destroy() - }) - }) - }, - typeString: t('spreed', 'Link to a conversation'), - typeIconClass: 'icon-talk', - }) -})(window.OCP, window.OC) +window.OCP.Collaboration.registerType('room', { + action: async () => { + const conversation = await requestRoomSelection('spreed-room-select', { + // 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, + }) + if (!conversation) { + throw new Error('User cancelled resource selection') + } + return conversation.token + }, + typeString: t('spreed', 'Link to a conversation'), + typeIconClass: 'icon-talk', +}) diff --git a/src/deck.js b/src/deck.js index 33bc3c43e49..73bf57aac00 100644 --- a/src/deck.js +++ b/src/deck.js @@ -4,109 +4,81 @@ */ import escapeHtml from 'escape-html' -import Vue from 'vue' import { getRequestToken } from '@nextcloud/auth' import { showSuccess, showError } from '@nextcloud/dialogs' -import { translate, translatePlural } from '@nextcloud/l10n' +import { t } from '@nextcloud/l10n' import { generateFilePath, generateUrl } from '@nextcloud/router' import { postRichObjectToConversation } from './services/messagesService.ts' +import { requestRoomSelection } from './utils/requestRoomSelection.js' import '@nextcloud/dialogs/style.css' -(function(OC, OCA, t, n) { - /** - * @param {object} card The card object given by the deck app - * @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, displayName }) { - try { - const response = await postRichObjectToConversation(token, { - objectType: 'deck-card', - objectId: card.id, - metaData: JSON.stringify(card), - }) +/** + * @param {object} card The card object given by the deck app + * @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, displayName }) { + try { + 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 }) + 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(displayName)} ↗`), - { - isHTML: true, - }) - } catch (exception) { - console.error('Error posting deck card to conversation', exception, exception.response?.status) - if (exception.response?.status === 403) { - showError(t('spreed', 'No permission to post messages in this conversation')) - } else { - showError(t('spreed', 'An error occurred while posting deck card to conversation')) - } + showSuccess(t('spreed', 'Deck card has been posted to {conversation}') + .replace(/\{conversation}/g, `${escapeHtml(displayName)} ↗`), + { + isHTML: true, + }) + } catch (exception) { + console.error('Error posting deck card to conversation', exception, exception.response?.status) + if (exception.response?.status === 403) { + showError(t('spreed', 'No permission to post messages in this conversation')) + } else { + showError(t('spreed', 'An error occurred while posting deck card to conversation')) } } +} - /** - * - */ - function init() { - if (!OCA.Deck) { - return - } - - OCA.Deck.registerCardAction({ - label: t('spreed', 'Post to a conversation'), - icon: 'icon-talk', - callback: (card) => { - const container = document.createElement('div') - container.id = 'spreed-post-card-to-room-select' - const body = document.getElementById('body-user') - body.appendChild(container) - - const RoomSelector = () => import('./components/RoomSelector.vue') - const vm = new Vue({ - el: container, - 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', (conversation) => { - vm.$el.remove() - vm.$destroy() - - postCardToRoom(card, conversation) - }) - }, - }) +/** + * + */ +function init() { + if (!window.OCA.Deck) { + return } - // CSP config for webpack dynamic chunk loading - // eslint-disable-next-line - __webpack_nonce__ = btoa(getRequestToken()) - - // Correct the root of the app for chunk loading - // OC.linkTo matches the apps folders - // OC.generateUrl ensure the index.php (or not) - // We do not want the index.php since we're loading files - // eslint-disable-next-line - __webpack_public_path__ = generateFilePath('spreed', '', 'js/') - - Vue.prototype.t = translate - Vue.prototype.n = translatePlural - Vue.prototype.OC = OC - Vue.prototype.OCA = OCA - - document.addEventListener('DOMContentLoaded', init) - -})(window.OC, window.OCA, t, n) + window.OCA.Deck.registerCardAction({ + label: t('spreed', 'Post to a conversation'), + icon: 'icon-talk', + callback: async (card) => { + const conversation = await requestRoomSelection('spreed-post-card-to-room-select', { + dialogTitle: t('spreed', 'Post to conversation'), + showPostableOnly: true, + }) + if (conversation) { + postCardToRoom(card, conversation) + } + }, + }) +} + +// CSP config for webpack dynamic chunk loading +// eslint-disable-next-line +__webpack_nonce__ = btoa(getRequestToken()) + +// Correct the root of the app for chunk loading +// OC.linkTo matches the apps folders +// OC.generateUrl ensure the index.php (or not) +// We do not want the index.php since we're loading files +// eslint-disable-next-line +__webpack_public_path__ = generateFilePath('spreed', '', 'js/') + +document.addEventListener('DOMContentLoaded', init) diff --git a/src/maps.js b/src/maps.js index 178e00ddfaa..cf268e4786b 100644 --- a/src/maps.js +++ b/src/maps.js @@ -4,108 +4,79 @@ */ import escapeHtml from 'escape-html' -import Vue from 'vue' import { getRequestToken } from '@nextcloud/auth' import { showSuccess, showError } from '@nextcloud/dialogs' -import { translate, translatePlural } from '@nextcloud/l10n' +import { t } from '@nextcloud/l10n' import { generateFilePath, generateUrl } from '@nextcloud/router' import { postRichObjectToConversation } from './services/messagesService.ts' +import { requestRoomSelection } from './utils/requestRoomSelection.js' import '@nextcloud/dialogs/style.css' -(function(OC, OCA, t, n) { - /** - * @param {object} location Geo location object - * @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, displayName }) { - try { - const response = await postRichObjectToConversation(token, { - objectType: 'geo-location', - objectId: location.id, - metaData: JSON.stringify(location), - }) - const messageId = response.data.ocs.data.id - const targetUrl = generateUrl('/call/{token}#message_{messageId}', { token, messageId }) +/** + * @param {object} location Geo location object + * @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, displayName }) { + try { + const response = await postRichObjectToConversation(token, { + objectType: 'geo-location', + objectId: location.id, + metaData: JSON.stringify(location), + }) + const messageId = response.data.ocs.data.id + const targetUrl = generateUrl('/call/{token}#message_{messageId}', { token, messageId }) - showSuccess(t('spreed', 'Location has been posted to {conversation}') - .replace(/\{conversation}/g, `${escapeHtml(displayName)} ↗`), - { - isHTML: true, - }) - } catch (exception) { - console.error('Error posting location to conversation', exception, exception.response?.status) - if (exception.response?.status === 403) { - showError(t('spreed', 'No permission to post messages in this conversation')) - } else { - showError(t('spreed', 'An error occurred while posting location to conversation')) - } + showSuccess(t('spreed', 'Location has been posted to {conversation}') + .replace(/\{conversation}/g, `${escapeHtml(displayName)} ↗`), + { + isHTML: true, + }) + } catch (exception) { + console.error('Error posting location to conversation', exception, exception.response?.status) + if (exception.response?.status === 403) { + showError(t('spreed', 'No permission to post messages in this conversation')) + } else { + showError(t('spreed', 'An error occurred while posting location to conversation')) } } +} - /** - * Initialise the maps action - */ - function init() { - if (!OCA.Maps?.registerMapsAction) { - return - } - - OCA.Maps.registerMapsAction({ - label: t('spreed', 'Share to a conversation'), - icon: 'icon-talk', - callback: (location) => { - const container = document.createElement('div') - container.id = 'spreed-post-location-to-room-select' - const body = document.getElementById('body-user') - body.appendChild(container) - - const RoomSelector = () => import('./components/RoomSelector.vue') - const vm = new Vue({ - el: container, - 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', (conversation) => { - vm.$el.remove() - vm.$destroy() - - postLocationToRoom(location, conversation) - }) - }, - }) +/** + * Initialise the maps action + */ +function init() { + if (!window.OCA.Maps?.registerMapsAction) { + return } - // CSP config for webpack dynamic chunk loading - // eslint-disable-next-line - __webpack_nonce__ = btoa(getRequestToken()) + window.OCA.Maps.registerMapsAction({ + label: t('spreed', 'Share to a conversation'), + icon: 'icon-talk', + callback: async (location) => { + const conversation = await requestRoomSelection('spreed-post-location-to-room-select', { + dialogTitle: t('spreed', 'Share to conversation'), + showPostableOnly: true, + }) - // Correct the root of the app for chunk loading - // OC.linkTo matches the apps folders - // OC.generateUrl ensure the index.php (or not) - // We do not want the index.php since we're loading files - // eslint-disable-next-line - __webpack_public_path__ = generateFilePath('spreed', '', 'js/') + postLocationToRoom(location, conversation) + }, + }) +} - Vue.prototype.t = translate - Vue.prototype.n = translatePlural - Vue.prototype.OC = OC - Vue.prototype.OCA = OCA +// CSP config for webpack dynamic chunk loading +// eslint-disable-next-line +__webpack_nonce__ = btoa(getRequestToken()) - document.addEventListener('DOMContentLoaded', init) +// Correct the root of the app for chunk loading +// OC.linkTo matches the apps folders +// OC.generateUrl ensure the index.php (or not) +// We do not want the index.php since we're loading files +// eslint-disable-next-line +__webpack_public_path__ = generateFilePath('spreed', '', 'js/') -})(window.OC, window.OCA, t, n) +document.addEventListener('DOMContentLoaded', init) diff --git a/src/search.js b/src/search.js index 3c6d092124f..06c0f865889 100644 --- a/src/search.js +++ b/src/search.js @@ -3,82 +3,54 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import Vue from 'vue' - import { getRequestToken } from '@nextcloud/auth' import { emit } from '@nextcloud/event-bus' -import { translate, translatePlural } from '@nextcloud/l10n' +import { t } from '@nextcloud/l10n' import { generateFilePath, imagePath } from '@nextcloud/router' -import '@nextcloud/dialogs/style.css' - -(function(OC, OCA, t, n) { +import { requestRoomSelection } from './utils/requestRoomSelection.js' - /** - * - */ - function init() { - if (!OCA.UnifiedSearch) { - return - } - console.debug('Initializing unified search plugin-filters from talk') - OCA.UnifiedSearch.registerFilterAction({ - id: 'talk-message', - appId: 'spreed', - label: t('spreed', 'In conversation'), - icon: imagePath('spreed', 'app.svg'), - callback: () => { - const container = document.createElement('div') - container.id = 'spreed-unified-search-conversation-select' - const body = document.getElementById('body-user') - body.appendChild(container) - - const RoomSelector = () => import('./components/RoomSelector.vue') - const vm = new Vue({ - el: container, - render: h => h(RoomSelector, { - props: { - dialogTitle: t('spreed', 'Select conversation'), - isPlugin: true, - }, - }), - }) - - vm.$root.$on('close', () => { - vm.$el.remove() - vm.$destroy() - }) - vm.$root.$on('select', (conversation) => { - vm.$el.remove() - vm.$destroy() +import '@nextcloud/dialogs/style.css' - emit('nextcloud:unified-search:add-filter', { - id: 'talk-message', - payload: conversation, - filterUpdateText: t('spreed', 'Search in conversation: {conversation}', { conversation: conversation.displayName }), - filterParams: { conversation: conversation.token } - }) - }) - }, - }) +/** + * + */ +function init() { + if (!window.OCA.UnifiedSearch) { + return } - - // CSP config for webpack dynamic chunk loading - // eslint-disable-next-line - __webpack_nonce__ = btoa(getRequestToken()) - - // Correct the root of the app for chunk loading - // OC.linkTo matches the apps folders - // OC.generateUrl ensure the index.php (or not) - // We do not want the index.php since we're loading files - // eslint-disable-next-line - __webpack_public_path__ = generateFilePath('spreed', '', 'js/') - - Vue.prototype.t = translate - Vue.prototype.n = translatePlural - Vue.prototype.OC = OC - Vue.prototype.OCA = OCA - - document.addEventListener('DOMContentLoaded', init) - -})(window.OC, window.OCA, t, n) + console.debug('Initializing unified search plugin-filters from talk') + window.OCA.UnifiedSearch.registerFilterAction({ + id: 'talk-message', + appId: 'spreed', + label: t('spreed', 'In conversation'), + icon: imagePath('spreed', 'app.svg'), + callback: async () => { + const conversation = await requestRoomSelection('spreed-unified-search-conversation-select', { + dialogTitle: t('spreed', 'Select conversation'), + }) + + if (conversation) { + emit('nextcloud:unified-search:add-filter', { + id: 'talk-message', + payload: conversation, + filterUpdateText: t('spreed', 'Search in conversation: {conversation}', { conversation: conversation.displayName }), + filterParams: { conversation: conversation.token } + }) + } + }, + }) +} + +// CSP config for webpack dynamic chunk loading +// eslint-disable-next-line +__webpack_nonce__ = btoa(getRequestToken()) + +// Correct the root of the app for chunk loading +// OC.linkTo matches the apps folders +// OC.generateUrl ensure the index.php (or not) +// We do not want the index.php since we're loading files +// eslint-disable-next-line +__webpack_public_path__ = generateFilePath('spreed', '', 'js/') + +document.addEventListener('DOMContentLoaded', init) diff --git a/src/utils/requestRoomSelection.js b/src/utils/requestRoomSelection.js new file mode 100644 index 00000000000..1b459e5156c --- /dev/null +++ b/src/utils/requestRoomSelection.js @@ -0,0 +1,52 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import Vue, { defineAsyncComponent } from 'vue' + +import { t, n } from '@nextcloud/l10n' + +Vue.prototype.t = t +Vue.prototype.n = n +Vue.prototype.OC = window.OC +Vue.prototype.OCA = window.OCA +Vue.prototype.OCP = window.OCP + +/** + * + * @param {string} containerId - id of the container to append the RoomSelector component to + * @param {object} roomSelectorProps - props data to pass to RoomSelector component + * @return {Promise} - resolves with the conversation of the selected room or null if canceled + */ +export function requestRoomSelection(containerId, roomSelectorProps) { + return new Promise((resolve, reject) => { + const container = document.createElement('div') + container.id = containerId + const body = document.getElementById('body-user') + body.appendChild(container) + + const RoomSelector = defineAsyncComponent(() => import('../components/RoomSelector.vue')) + + const vm = new Vue({ + render: h => h(RoomSelector, { + props: { + isPlugin: true, + ...roomSelectorProps, + }, + }), + }).$mount(container) + + vm.$root.$on('close', () => { + container.remove() + vm.$destroy() + reject(new Error('User cancelled resource selection')) + }) + + vm.$root.$on('select', (conversation) => { + container.remove() + vm.$destroy() + resolve(conversation) + }) + }) +}