Merge pull request #2311 from nextcloud/feature/vuejs/conversation-list

Conversation list
This commit is contained in:
Joas Schilling 2019-10-21 12:39:03 +02:00 коммит произвёл GitHub
Родитель d889f64888 06bb91fdfa
Коммит f01df12443
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 341 добавлений и 53 удалений

41
package-lock.json сгенерированный
Просмотреть файл

@ -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)
}
}