зеркало из https://github.com/nextcloud/spreed.git
Merge pull request #2311 from nextcloud/feature/vuejs/conversation-list
Conversation list
This commit is contained in:
Коммит
f01df12443
|
@ -2512,6 +2512,16 @@
|
|||
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
|
||||
"dev": true
|
||||
},
|
||||
"clipboard": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz",
|
||||
"integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==",
|
||||
"requires": {
|
||||
"good-listener": "^1.2.2",
|
||||
"select": "^1.1.2",
|
||||
"tiny-emitter": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||
|
@ -3186,6 +3196,11 @@
|
|||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||
"dev": true
|
||||
},
|
||||
"delegate": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
||||
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
|
||||
},
|
||||
"delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
|
@ -5359,6 +5374,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"good-listener": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
|
||||
"requires": {
|
||||
"delegate": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
|
||||
|
@ -9337,6 +9360,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
|
@ -10770,6 +10798,11 @@
|
|||
"setimmediate": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"tiny-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
|
@ -11312,6 +11345,14 @@
|
|||
"resolved": "https://registry.npmjs.org/vue-click-outside/-/vue-click-outside-1.0.7.tgz",
|
||||
"integrity": "sha1-zdKxYF48SUR4TheU6uShKg9wC9Y="
|
||||
},
|
||||
"vue-clipboard2": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-clipboard2/-/vue-clipboard2-0.3.1.tgz",
|
||||
"integrity": "sha512-H5S/agEDj0kXjUb5GP2c0hCzIXWRBygaWLN3NEFsaI9I3uWin778SFEMt8QRXiPG+7anyjqWiw2lqcxWUSfkYg==",
|
||||
"requires": {
|
||||
"clipboard": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"vue-contenteditable-directive": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-contenteditable-directive/-/vue-contenteditable-directive-1.2.0.tgz",
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"nextcloud-vue": "^0.12.3",
|
||||
"nextcloud-vue-collections": "^0.5.6",
|
||||
"vue": "^2.6.10",
|
||||
"vue-clipboard2": "^0.3.1",
|
||||
"vue-contenteditable-directive": "^1.2.0",
|
||||
"vue-fragment": "^1.5.1",
|
||||
"vue-router": "^3.1.3",
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
|
||||
-
|
||||
- @author Joas Schilling <coding@schilljs.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/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<AppContentListItem
|
||||
:title="item.displayName"
|
||||
:to="{ name: 'conversation', params: { token: item.token }}"
|
||||
@click.prevent.exact="joinConversation(item.token)">
|
||||
<ConversationIcon
|
||||
slot="icon"
|
||||
:item="item" />
|
||||
<template slot="subtitle">
|
||||
{{ item.lastMessage.message }}
|
||||
</template>
|
||||
<AppNavigationCounter v-if="item.unreadMessages"
|
||||
slot="counter"
|
||||
:highlighted="true">
|
||||
{{ item.unreadMessages }}
|
||||
</AppNavigationCounter>
|
||||
<template slot="actions">
|
||||
<ActionButton v-if="canFavorite"
|
||||
:icon="iconFavorite"
|
||||
@click.prevent.exact="toggleFavoriteConversation">
|
||||
{{ labelFavorite }}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
icon="icon-clippy"
|
||||
@click.stop.prevent="copyLinkToConversation">
|
||||
{{ t('spreed', 'Copy link') }}
|
||||
</ActionButton>
|
||||
|
||||
<!-- FIXME Should be a real separator -->
|
||||
<ActionText
|
||||
icon="icon-more">
|
||||
------
|
||||
</ActionText>
|
||||
|
||||
<ActionText
|
||||
icon="icon-timezone">
|
||||
{{ t('spreed', 'Chat notifications') }}
|
||||
</ActionText>
|
||||
<ActionButton
|
||||
icon="icon-sound"
|
||||
@click.prevent.exact="setNotificationLevel(1)">
|
||||
{{ t('spreed', 'All messages') }}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
icon="icon-user"
|
||||
@click.prevent.exact="setNotificationLevel(2)">
|
||||
{{ t('spreed', '@-mentions only') }}
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
icon="icon-sound-off"
|
||||
@click.prevent.exact="setNotificationLevel(3)">
|
||||
{{ t('spreed', 'Off') }}
|
||||
</ActionButton>
|
||||
|
||||
<!-- FIXME Should be a real separator -->
|
||||
<ActionText
|
||||
icon="icon-more">
|
||||
------
|
||||
</ActionText>
|
||||
|
||||
<ActionButton v-if="canLeaveConversation"
|
||||
:icon="iconLeaveConversation"
|
||||
@click.prevent.exact="leaveConversation">
|
||||
{{ t('spreed', 'Leave conversation') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-if="canDeleteConversation"
|
||||
icon="icon-delete"
|
||||
@click.prevent.exact="deleteConversation">
|
||||
{{ t('spreed', 'Delete conversation') }}
|
||||
</ActionButton>
|
||||
</template>
|
||||
</AppContentListItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
import ConversationIcon from './../../ConversationIcon'
|
||||
import AppNavigationCounter from 'nextcloud-vue/dist/Components/AppNavigationCounter'
|
||||
import AppContentListItem from './AppContentListItem/AppContentListItem'
|
||||
import ActionButton from 'nextcloud-vue/dist/Components/ActionButton'
|
||||
import ActionText from 'nextcloud-vue/dist/Components/ActionText'
|
||||
import { joinConversation, removeCurrentUserFromConversation } from '../../../services/participantsService'
|
||||
import { deleteConversation, addToFavorites, removeFromFavorites, setNotificationLevel } from '../../../services/conversationsService'
|
||||
import { generateUrl } from 'nextcloud-router'
|
||||
import { CONVERSATION, PARTICIPANT } from '../../../constants'
|
||||
|
||||
export default {
|
||||
name: 'Conversation',
|
||||
components: {
|
||||
ActionButton,
|
||||
ActionText,
|
||||
AppContentListItem,
|
||||
AppNavigationCounter,
|
||||
ConversationIcon
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {
|
||||
token: '',
|
||||
participants: [],
|
||||
participantType: 0,
|
||||
unreadMessages: 0,
|
||||
objectType: '',
|
||||
type: 0,
|
||||
displayName: '',
|
||||
isFavorite: false,
|
||||
notificationLevel: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
linkToConversation() {
|
||||
return window.location.protocol + '//' + window.location.host + generateUrl('/call/' + this.item.token)
|
||||
},
|
||||
canFavorite() {
|
||||
return this.item.participantType !== PARTICIPANT.TYPE.USER_SELF_JOINED
|
||||
},
|
||||
iconFavorite() {
|
||||
return this.item.isFavorite ? 'icon-star-dark' : 'icon-starred'
|
||||
},
|
||||
labelFavorite() {
|
||||
return this.item.isFavorite ? t('spreed', 'Remove from favorites') : t('spreed', 'Add to favorites')
|
||||
},
|
||||
canDeleteConversation() {
|
||||
return this.item.type !== CONVERSATION.TYPE.ONE_TO_ONE && (this.item.participantType === PARTICIPANT.TYPE.OWNER || this.item.participantType === PARTICIPANT.TYPE.MODERATOR)
|
||||
},
|
||||
canLeaveConversation() {
|
||||
return !this.canDeleteConversation || (this.item.type !== CONVERSATION.TYPE.ONE_TO_ONE && Object.keys(this.item.participants).length > 1)
|
||||
},
|
||||
iconLeaveConversation() {
|
||||
if (this.canDeleteConversation) {
|
||||
return 'icon-close'
|
||||
}
|
||||
return 'icon-delete'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async copyLinkToConversation() {
|
||||
try {
|
||||
await this.$copyText(this.linkToConversation)
|
||||
OCP.Toast.success(t('spreed', 'Link to conversation copied to clipboard'))
|
||||
} catch (error) {
|
||||
OCP.Toast.error(t('spreed', 'Link to conversation was not copied to clipboard.'))
|
||||
}
|
||||
},
|
||||
async joinConversation() {
|
||||
await joinConversation(this.item.token)
|
||||
},
|
||||
/**
|
||||
* Deletes the conversation.
|
||||
*/
|
||||
async deleteConversation() {
|
||||
try {
|
||||
await deleteConversation(this.item.token)
|
||||
// If successful, deletes the conversation from the store
|
||||
this.$store.dispatch('deleteConversation', this.item)
|
||||
} catch (error) {
|
||||
console.debug(`error while deleting conversation ${error}`)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Deletes the current user from the conversation.
|
||||
*/
|
||||
async leaveConversation() {
|
||||
try {
|
||||
await removeCurrentUserFromConversation(this.item.token)
|
||||
// If successful, deletes the conversation from the store
|
||||
this.$store.dispatch('deleteConversation', this.item)
|
||||
} catch (error) {
|
||||
console.debug(`error while removing yourself from conversation ${error}`)
|
||||
}
|
||||
},
|
||||
async toggleFavoriteConversation() {
|
||||
if (this.item.isFavorite) {
|
||||
await removeFromFavorites(this.item.token)
|
||||
} else {
|
||||
await addToFavorites(this.item.token)
|
||||
}
|
||||
|
||||
this.item.isFavorite = !this.item.isFavorite
|
||||
},
|
||||
/**
|
||||
* Set the notification level for the conversation
|
||||
* @param {int} level The notification level to set.
|
||||
*/
|
||||
async setNotificationLevel(level) {
|
||||
await setNotificationLevel(this.item.token, level)
|
||||
this.item.notificationLevel = level
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scroller {
|
||||
flex: 1 0;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
|
@ -21,54 +21,21 @@
|
|||
|
||||
<template>
|
||||
<ul class="conversations">
|
||||
<AppContentListItem
|
||||
<Conversation
|
||||
v-for="item of conversationsList"
|
||||
:key="item.id"
|
||||
:to="{ name: 'conversation', params: { token: item.token }}"
|
||||
:title="item.displayName"
|
||||
@click.prevent.exact="joinConversation(item.token)">
|
||||
<ConversationIcon
|
||||
slot="icon"
|
||||
:item="item" />
|
||||
<template slot="subtitle">
|
||||
{{ item.lastMessage.message }}
|
||||
</template>
|
||||
<AppNavigationCounter
|
||||
slot="counter"
|
||||
:highlighted="true">
|
||||
3
|
||||
</AppNavigationCounter>
|
||||
<template slot="actions">
|
||||
<ActionButton
|
||||
icon="icon-edit"
|
||||
@click="alert('Edit')">
|
||||
Edit
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
icon="icon-delete"
|
||||
@click.prevent.exact="deleteConversation(item.token)">
|
||||
{{ t('spreed', 'Leave Conversation') }}
|
||||
</ActionButton>
|
||||
</template>
|
||||
</AppContentListItem>
|
||||
:item="item" />
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConversationIcon from '../../ConversationIcon'
|
||||
import AppNavigationCounter from 'nextcloud-vue/dist/Components/AppNavigationCounter'
|
||||
import AppContentListItem from './AppContentListItem/AppContentListItem'
|
||||
import ActionButton from 'nextcloud-vue/dist/Components/ActionButton'
|
||||
import Conversation from './Conversation'
|
||||
import { fetchConversations } from '../../../services/conversationsService'
|
||||
import { joinConversation, removeCurrentUserFromConversation } from '../../../services/participantsService'
|
||||
|
||||
export default {
|
||||
name: 'ConversationsList',
|
||||
components: {
|
||||
ConversationIcon,
|
||||
AppNavigationCounter,
|
||||
ActionButton,
|
||||
AppContentListItem
|
||||
Conversation
|
||||
},
|
||||
computed: {
|
||||
conversationsList() {
|
||||
|
@ -88,21 +55,10 @@ export default {
|
|||
})
|
||||
},
|
||||
methods: {
|
||||
async joinConversation(token) {
|
||||
await joinConversation(token)
|
||||
},
|
||||
handleInput(payload) {
|
||||
const selectedConversationToken = payload.token
|
||||
this.joinConversation(selectedConversationToken)
|
||||
this.$router.push({ path: `/call/${selectedConversationToken}` })
|
||||
},
|
||||
/**
|
||||
* Deletes the current user from the conversation.
|
||||
* @param {string} token The token of the conversation to be left.
|
||||
*/
|
||||
async deleteConversation(token) {
|
||||
const response = await removeCurrentUserFromConversation(token)
|
||||
console.debug(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import Vuex from 'vuex'
|
|||
import contenteditableDirective from 'vue-contenteditable-directive'
|
||||
import store from './store'
|
||||
import VueRouter from 'vue-router'
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import router from './router/router'
|
||||
import { generateFilePath } from 'nextcloud-router'
|
||||
import { getRequestToken } from 'nextcloud-auth'
|
||||
|
@ -48,6 +49,7 @@ Vue.prototype.OCA = OCA
|
|||
Vue.use(contenteditableDirective)
|
||||
Vue.use(Vuex)
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(VueClipboard)
|
||||
|
||||
export default new Vue({
|
||||
el: '#content',
|
||||
|
|
|
@ -88,9 +88,52 @@ const deleteConversation = async function(token) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a conversation to the favorites
|
||||
* @param {string} token The token of the conversation to be favorites
|
||||
*/
|
||||
const addToFavorites = async function(token) {
|
||||
try {
|
||||
const response = await axios.post(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}/favorite`)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.debug('Error while adding the conversation to favorites: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a conversation from the favorites
|
||||
* @param {string} token The token of the conversation to be removed from favorites
|
||||
*/
|
||||
const removeFromFavorites = async function(token) {
|
||||
try {
|
||||
const response = await axios.delete(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}/favorite`)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.debug('Error while removing the conversation from favorites: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a conversation from the favorites
|
||||
* @param {string} token The token of the conversation to be removed from favorites
|
||||
* @param {int} level The notification level to set.
|
||||
*/
|
||||
const setNotificationLevel = async function(token, level) {
|
||||
try {
|
||||
const response = await axios.post(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}/notify`, { level })
|
||||
return response
|
||||
} catch (error) {
|
||||
console.debug('Error while setting the notification level: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
fetchConversations,
|
||||
searchPossibleConversations,
|
||||
createOneToOneConversation,
|
||||
createGroupConversation,
|
||||
deleteConversation }
|
||||
deleteConversation,
|
||||
addToFavorites,
|
||||
removeFromFavorites,
|
||||
setNotificationLevel }
|
||||
|
|
|
@ -41,7 +41,7 @@ const mutations = {
|
|||
* @param {object} conversation the conversation;
|
||||
*/
|
||||
addConversation(state, conversation) {
|
||||
Vue.set(state.conversations, conversation.id, conversation)
|
||||
Vue.set(state.conversations, conversation.token, conversation)
|
||||
},
|
||||
/**
|
||||
* Creates a key-value pair with conversation id and name
|
||||
|
@ -49,11 +49,20 @@ const mutations = {
|
|||
*
|
||||
* @param {object} state current state object;
|
||||
* @param {object} object destructuring object;
|
||||
* @param {int} object.id conversation id;
|
||||
* @param {string} object.token conversation token;
|
||||
* @param {string} object.displayName conversation name;
|
||||
*/
|
||||
indexConversationName(state, { id, displayName }) {
|
||||
Vue.set(state.conversationsNames, id, displayName)
|
||||
indexConversationName(state, { token, displayName }) {
|
||||
Vue.set(state.conversationsNames, token, displayName)
|
||||
},
|
||||
/**
|
||||
* Deletes a conversation from the store.
|
||||
* @param {object} state current store state;
|
||||
* @param {object} conversation the message;
|
||||
*/
|
||||
deleteConversation(state, conversation) {
|
||||
Vue.delete(state.conversations, conversation.token)
|
||||
Vue.delete(state.conversationsNames, conversation.token)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +76,16 @@ const actions = {
|
|||
addConversation(context, conversation) {
|
||||
context.commit('addConversation', conversation)
|
||||
context.commit('indexConversationName', conversation)
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a object
|
||||
*
|
||||
* @param {object} context default store context;
|
||||
* @param {object} conversation the conversation to be deleted;
|
||||
*/
|
||||
deleteConversation(context, conversation) {
|
||||
context.commit('deleteConversation', conversation)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче