diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 819cb4cc48b..56a25854ab9 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -38,6 +38,7 @@
use OCA\Talk\Dashboard\TalkWidget;
use OCA\Talk\Events\ChatEvent;
use OCA\Talk\Events\RoomEvent;
+use OCA\Talk\Deck\DeckPluginLoader;
use OCA\Talk\Files\Listener as FilesListener;
use OCA\Talk\Files\TemplateLoader as FilesTemplateLoader;
use OCA\Talk\Flow\Operation;
@@ -99,6 +100,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(BeforeTemplateRenderedEvent::class, PublicShareAuthTemplateLoader::class);
$context->registerEventListener(\OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent::class, UnifiedSearchCSSLoader::class);
$context->registerEventListener(UserChangedEvent::class, UserDisplayNameListener::class);
+ $context->registerEventListener(\OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent::class, DeckPluginLoader::class);
$context->registerSearchProvider(ConversationSearch::class);
$context->registerSearchProvider(CurrentMessageSearch::class);
diff --git a/lib/Deck/DeckPluginLoader.php b/lib/Deck/DeckPluginLoader.php
new file mode 100644
index 00000000000..7fd27226aeb
--- /dev/null
+++ b/lib/Deck/DeckPluginLoader.php
@@ -0,0 +1,54 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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 .
+ *
+ */
+
+namespace OCA\Talk\Deck;
+
+use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
+use OCP\EventDispatcher\Event;
+use OCP\EventDispatcher\IEventListener;
+use OCP\IRequest;
+use OCP\Util;
+
+class DeckPluginLoader implements IEventListener {
+ /** @var IRequest */
+ private $request;
+
+ public function __construct(IRequest $request) {
+ $this->request = $request;
+ }
+
+ public function handle(Event $event): void {
+ if (!($event instanceof BeforeTemplateRenderedEvent)) {
+ return;
+ }
+
+ if (!$event->isLoggedIn()) {
+ return;
+ }
+
+ if (strpos($this->request->getPathInfo(), '/apps/deck') === 0) {
+ Util::addScript('spreed', 'collections');
+ Util::addScript('spreed', 'deck');
+ }
+ }
+}
diff --git a/src/collections.js b/src/collections.js
index 8d6b9cd7975..27d79afd6c7 100644
--- a/src/collections.js
+++ b/src/collections.js
@@ -49,7 +49,7 @@ import RoomSelector from './views/RoomSelector'
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
- reject(new Error('User canceled resource selection'))
+ reject(new Error('User cancelled resource selection'))
})
ComponentVM.$root.$on('select', (id) => {
resolve(id)
diff --git a/src/deck.js b/src/deck.js
new file mode 100644
index 00000000000..d0f63f047f6
--- /dev/null
+++ b/src/deck.js
@@ -0,0 +1,111 @@
+/*
+ * @copyright Copyright (c) 2020 Vincent Petry
+ *
+ * @author Vincent Petry
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * 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'
+import { generateFilePath, generateUrl } from '@nextcloud/router'
+import { getRequestToken } from '@nextcloud/auth'
+import { translate, translatePlural } from '@nextcloud/l10n'
+import { showSuccess, showError } from '@nextcloud/dialogs'
+import { postRichObjectToConversation } from './services/messagesService'
+import RoomSelector from './views/RoomSelector'
+
+(function(OC, OCA, t, n) {
+ async function postCardToRoom(card, token) {
+ 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 })
+ showSuccess(t('spreed', 'Deck card has been posted to the selected conversation.', {
+ link: targetUrl,
+ }), {
+ 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 ComponentVM = Vue.extend(RoomSelector)
+ const vm = new ComponentVM({
+ el: container,
+ propsData: {
+ dialogTitle: t('spreed', 'Post to conversation'),
+ showPostableOnly: true,
+ },
+ })
+
+ vm.$root.$on('close', () => {
+ vm.$el.remove()
+ vm.$destroy()
+ })
+ vm.$root.$on('select', (token) => {
+ vm.$el.remove()
+ vm.$destroy()
+
+ postCardToRoom(card, 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/')
+
+ 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)
diff --git a/src/main.js b/src/main.js
index c21ed964367..9dec9551f40 100644
--- a/src/main.js
+++ b/src/main.js
@@ -71,7 +71,7 @@ Vue.use(VueObserveVisibility)
Vue.use(VueShortKey, { prevent: ['input', 'textarea', 'div'] })
Vue.use(vOutsideEvents)
-export default new Vue({
+const instance = new Vue({
el: '#content',
store,
router,
@@ -155,3 +155,11 @@ Sidebar.prototype.close = function() {
Object.assign(window.OCA.Files, {
Sidebar: new Sidebar(),
})
+
+// make the instance available to global components that might run on the same page
+if (!window.OCA.Talk) {
+ window.OCA.Talk = {}
+}
+OCA.Talk.instance = instance
+
+export default instance
diff --git a/src/services/messagesService.js b/src/services/messagesService.js
index 02d4af8fcd0..8a2b14951b8 100644
--- a/src/services/messagesService.js
+++ b/src/services/messagesService.js
@@ -23,6 +23,8 @@
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import store from '../store/index'
+import SHA1 from 'crypto-js/sha1'
+import Hex from 'crypto-js/enc-hex'
/**
* Fetches messages that belong to a particular conversation
@@ -114,9 +116,32 @@ const deleteMessage = async function({ token, id }) {
return axios.delete(generateOcsUrl('apps/spreed/api/v1/chat', 2) + token + '/' + id)
}
+/**
+ * Post a rich object to a conversation
+ *
+ * @param {string} token conversation token
+ * @param {string} objectType object type
+ * @param {string} objectId object id
+ * @param {string} metaData JSON metadata of the rich object encoded as string
+ * @param {string} referenceId generated reference id, leave empty to generate it based on the other args
+ */
+const postRichObjectToConversation = async function(token, { objectType, objectId, metaData, referenceId }) {
+ if (!referenceId) {
+ const tempId = 'richobject-' + objectType + '-' + objectId + '-' + token + '-' + (new Date().getTime())
+ referenceId = Hex.stringify(SHA1(tempId))
+ }
+ return axios.post(generateOcsUrl('apps/spreed/api/v1', 2) + `chat/${token}/share`, {
+ objectType,
+ objectId,
+ metaData,
+ referenceId,
+ })
+}
+
export {
fetchMessages,
lookForNewMessages,
postNewMessage,
deleteMessage,
+ postRichObjectToConversation,
}
diff --git a/src/views/RoomSelector.vue b/src/views/RoomSelector.vue
index 7b0842aa685..ec9dca1654f 100644
--- a/src/views/RoomSelector.vue
+++ b/src/views/RoomSelector.vue
@@ -24,9 +24,9 @@