зеркало из https://github.com/nextcloud/spreed.git
Merge pull request #5110 from nextcloud/enh/5106/share-deck-card-with-conversation
Add sharing deck card with a conversation
This commit is contained in:
Коммит
6f88aa9a91
|
@ -38,6 +38,7 @@ use OCA\Talk\Config;
|
|||
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 @@ class Application extends App implements IBootstrap {
|
|||
$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);
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* @copyright Copyright (c) 2020 Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @author Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
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 <a href="{link}">conversation</a>.', {
|
||||
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)
|
10
src/main.js
10
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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
<Modal @close="close">
|
||||
<div id="modal-inner" class="talk-modal" :class="{ 'icon-loading': loading }">
|
||||
<div id="modal-content">
|
||||
<h2>{{ t('spreed', 'Link to a conversation') }}</h2>
|
||||
<h2>{{ dialogTitle }}</h2>
|
||||
<div id="room-list">
|
||||
<ul v-if="!loading">
|
||||
<ul v-if="!loading && availableRooms.length > 0">
|
||||
<li v-for="room in availableRooms"
|
||||
:key="room.token"
|
||||
:class="{selected: selectedRoom === room.token }"
|
||||
|
@ -38,9 +38,16 @@
|
|||
<span>{{ room.displayName }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-else-if="!loading">
|
||||
{{ t('spreed', 'No conversations found') }}
|
||||
</div>
|
||||
</div>
|
||||
<div id="modal-buttons">
|
||||
<button v-if="!loading" class="primary" @click="select">
|
||||
<button
|
||||
v-if="!loading && availableRooms.length > 0"
|
||||
class="primary"
|
||||
:disabled="!selectedRoom"
|
||||
@click="select">
|
||||
{{ t('spreed', 'Select conversation') }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -53,6 +60,7 @@
|
|||
import Modal from '@nextcloud/vue/dist/Components/Modal'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import { CONVERSATION } from '../constants'
|
||||
import ConversationIcon from '../components/ConversationIcon'
|
||||
|
||||
export default {
|
||||
|
@ -61,31 +69,34 @@ export default {
|
|||
ConversationIcon,
|
||||
Modal,
|
||||
},
|
||||
props: {
|
||||
dialogTitle: {
|
||||
type: String,
|
||||
default: t('spreed', 'Link to a conversation'),
|
||||
},
|
||||
/**
|
||||
* Whether to only show conversations to which
|
||||
* the user can post messages.
|
||||
*/
|
||||
showPostableOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rooms: [],
|
||||
selectedRoom: null,
|
||||
currentRoom: null,
|
||||
loading: true,
|
||||
// TODO: should be included once this is properly available
|
||||
types: {
|
||||
ROOM_TYPE_ONE_TO_ONE: 1,
|
||||
ROOM_TYPE_GROUP: 2,
|
||||
ROOM_TYPE_PUBLIC: 3,
|
||||
ROOM_TYPE_CHANGELOG: 4,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentRoom() {
|
||||
if (OCA.SpreedMe && OCA.SpreedMe.app.activeRoom) {
|
||||
return OCA.SpreedMe.app.activeRoom.get('token')
|
||||
}
|
||||
return null
|
||||
},
|
||||
availableRooms() {
|
||||
return this.rooms.filter((room) => {
|
||||
return room.token !== this.currentRoom
|
||||
&& room.type !== this.types.ROOM_TYPE_CHANGELOG
|
||||
return room.type !== CONVERSATION.TYPE.CHANGELOG
|
||||
&& (!this.currentRoom || this.currentRoom !== room.token)
|
||||
&& (!this.showPostableOnly || room.readOnly === CONVERSATION.STATE.READ_WRITE)
|
||||
&& room.objectType !== 'file'
|
||||
&& room.objectType !== 'share:password'
|
||||
})
|
||||
|
@ -93,6 +104,11 @@ export default {
|
|||
},
|
||||
beforeMount() {
|
||||
this.fetchRooms()
|
||||
|
||||
const $store = OCA.Talk?.instance?.$store
|
||||
if ($store) {
|
||||
this.currentRoom = $store.getters.getToken()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchRooms() {
|
||||
|
@ -138,6 +154,7 @@ export default {
|
|||
#room-list {
|
||||
overflow-y: auto;
|
||||
flex: 0 1 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
li {
|
||||
|
@ -159,6 +176,9 @@ li {
|
|||
& > span {
|
||||
padding: 5px 5px 5px 10px;
|
||||
vertical-align: middle;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ module.exports = {
|
|||
'talk-public-share-sidebar': path.join(__dirname, 'src', 'mainPublicShareSidebar.js'),
|
||||
'flow': path.join(__dirname, 'src', 'flow.js'),
|
||||
'dashboard': path.join(__dirname, 'src', 'dashboard.js'),
|
||||
'deck': path.join(__dirname, 'src', 'deck.js'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, './js'),
|
||||
|
|
Загрузка…
Ссылка в новой задаче