diff options
-rw-r--r-- | appinfo/info.xml | 2 | ||||
-rw-r--r-- | src/App.vue | 55 | ||||
-rw-r--r-- | src/components/ConversationIcon.vue | 2 | ||||
-rw-r--r-- | src/components/LeftSidebar/CirclesList/CirclesList.vue | 95 | ||||
-rw-r--r-- | src/components/LeftSidebar/ContactsList/ContactsList.vue | 87 | ||||
-rw-r--r-- | src/components/LeftSidebar/ConversationsList/ConversationsList.vue | 4 | ||||
-rw-r--r-- | src/components/LeftSidebar/LeftSidebar.vue | 46 | ||||
-rw-r--r-- | src/components/LeftSidebar/NewGroupConversation/SetContacts/SetContacts.vue | 3 | ||||
-rw-r--r-- | src/components/ParticipantOptionsList.vue (renamed from src/components/LeftSidebar/GroupsList/GroupsList.vue) | 51 | ||||
-rw-r--r-- | src/components/RightSidebar/Participants/CurrentParticipants/CurrentParticipants.vue | 15 | ||||
-rw-r--r-- | src/components/RightSidebar/Participants/ParticipantsTab.vue | 153 | ||||
-rw-r--r-- | src/router/router.js | 9 | ||||
-rw-r--r-- | src/services/conversationsService.js | 31 | ||||
-rw-r--r-- | src/utils/signaling.js | 6 | ||||
-rw-r--r-- | src/views/NotFoundView.vue | 18 |
15 files changed, 283 insertions, 294 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml index 3d90d5927..67f1f83fb 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -16,7 +16,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m ]]></description> - <version>8.0.0-alpha.3</version> + <version>8.0.0-alpha.4</version> <licence>agpl</licence> <author>Daniel Calviño Sánchez</author> diff --git a/src/App.vue b/src/App.vue index e3b155a28..fe55017b1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -174,6 +174,20 @@ export default { document.addEventListener('visibilitychange', this.changeWindowVisibility) this.onResize() + + EventBus.$on('conversationsReceived', (params) => { + if (this.$route.name === 'conversation' + && !this.$store.getters.conversations[this.token]) { + if (!params.singleConversation) { + console.info('Conversations received, but the current conversation is not in the list, trying to get potential public conversation manually') + this.fetchSingleConversation(this.token) + } else { + console.info('Conversation received, but the current conversation is not in the list. Redirecting to /apps/spreed') + this.$router.push('/apps/spreed/not-found') + } + } + }) + /** * Listens to the conversationsReceived globalevent, emitted by the conversationsList * component each time a new batch of conversations is received and processed in @@ -192,13 +206,6 @@ export default { } }) - EventBus.$on('conversationsReceived', () => { - if (this.$route.name === 'conversation' - && !this.$store.getters.conversations[this.token]) { - this.$router.push('/apps/spreed') - } - }) - /** * Global before guard, this is called whenever a navigation is triggered. */ @@ -296,18 +303,28 @@ export default { }, async fetchSingleConversation(token) { - /** Fetches the conversations from the server and then adds them one by one - * to the store. - */ - const response = await fetchConversation(token) - // this.$store.dispatch('purgeConversationsStore') - this.$store.dispatch('addConversation', response.data.ocs.data) - - /** - * Emits a global event that is used in App.vue to update the page title once the - * ( if the current route is a conversation and once the conversations are received) - */ - EventBus.$emit('conversationsReceived') + try { + /** + * Fetches the conversations from the server and then adds them one by one + * to the store. + */ + const response = await fetchConversation(token) + + // this.$store.dispatch('purgeConversationsStore') + this.$store.dispatch('addConversation', response.data.ocs.data) + + /** + * Emits a global event that is used in App.vue to update the page title once the + * ( if the current route is a conversation and once the conversations are received) + */ + EventBus.$emit('conversationsReceived', { + singleConversation: true, + }) + } catch (exception) { + console.info('Conversation received, but the current conversation is not in the list. Redirecting to /apps/spreed') + this.$router.push('/apps/spreed/not-found') + this.$store.dispatch('hideSidebar') + } }, }, } diff --git a/src/components/ConversationIcon.vue b/src/components/ConversationIcon.vue index bff76a7d8..adbc7a508 100644 --- a/src/components/ConversationIcon.vue +++ b/src/components/ConversationIcon.vue @@ -75,6 +75,8 @@ export default { return 'icon-file' } else if (this.item.objectType === 'share:password') { return 'icon-password' + } else if (this.item.objectType === 'emails') { + return 'icon-mail' } else if (this.item.type === CONVERSATION.TYPE.CHANGELOG) { return 'icon-changelog' } else if (this.item.type === CONVERSATION.TYPE.GROUP) { diff --git a/src/components/LeftSidebar/CirclesList/CirclesList.vue b/src/components/LeftSidebar/CirclesList/CirclesList.vue deleted file mode 100644 index 93481d4f2..000000000 --- a/src/components/LeftSidebar/CirclesList/CirclesList.vue +++ /dev/null @@ -1,95 +0,0 @@ -<!-- - - @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> - <ul class="contacts-list"> - <AppContentListItem - v-for="item of circles" - :key="item.id" - :title="item.label" - @click="createAndJoinConversation(item.id)"> - <template v-slot:icon> - <ConversationIcon - :item="iconData" /> - </template> - </AppContentListItem> - </ul> -</template> - -<script> -import ConversationIcon from '../../ConversationIcon' -import AppContentListItem from '../ConversationsList/AppContentListItem/AppContentListItem' -import { EventBus } from '../../../services/EventBus' -import { createGroupConversation } from '../../../services/conversationsService' -import { CONVERSATION } from '../../../constants' - -export default { - name: 'CirclesList', - components: { - ConversationIcon, - AppContentListItem, - }, - props: { - circles: { - type: Array, - required: true, - }, - isLoading: { - type: Boolean, - default: false, - }, - }, - computed: { - iconData() { - return { - type: CONVERSATION.TYPE.GROUP, - } - }, - }, - methods: { - /** - * Create a new conversation with the selected circle. - * @param {string} circleId the ID of the clicked circle. - */ - async createAndJoinConversation(circleId) { - console.debug('circles') - console.debug(circleId) - const response = await createGroupConversation(circleId, 'circles') - const conversation = response.data.ocs.data - this.$store.dispatch('addConversation', conversation) - this.$router.push({ name: 'conversation', params: { token: conversation.token } }).catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`)) - console.debug(response) - EventBus.$emit('resetSearchFilter') - }, - }, -} -</script> - -<style lang="scss" scoped> -.ellipsis { - text-overflow: ellipsis; -} -.contacts-list { - overflow: visible; - display: block; -} - -</style> diff --git a/src/components/LeftSidebar/ContactsList/ContactsList.vue b/src/components/LeftSidebar/ContactsList/ContactsList.vue deleted file mode 100644 index 60c8c448e..000000000 --- a/src/components/LeftSidebar/ContactsList/ContactsList.vue +++ /dev/null @@ -1,87 +0,0 @@ -<!-- - - @copyright Copyright (c) 2019 Marco Ambrosini <marcoambrosini@pm.me> - - - - @author Marco Ambrosini <marcoambrosini@pm.me> - - - - @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> - <ul class="contacts-list"> - <AppContentListItem - v-for="item of contacts" - :key="item.id" - :title="item.label" - @click="createAndJoinConversation(item.id)"> - <Avatar - slot="icon" - :size="44" - :user="item.id" - :display-name="item.label" /> - </AppContentListItem> - </ul> -</template> - -<script> -import AppContentListItem from '../ConversationsList/AppContentListItem/AppContentListItem' -import Avatar from '@nextcloud/vue/dist/Components/Avatar' -import { EventBus } from '../../../services/EventBus' -import { createOneToOneConversation } from '../../../services/conversationsService' - -export default { - name: 'ContactsList', - components: { - Avatar, - AppContentListItem, - }, - props: { - contacts: { - type: Array, - required: true, - }, - isLoading: { - type: Boolean, - default: false, - }, - }, - methods: { - /** - * Create a new conversation with the selected user. - * @param {string} userId the ID of the clicked user. - */ - async createAndJoinConversation(userId) { - console.debug(userId) - const response = await createOneToOneConversation(userId) - const conversation = response.data.ocs.data - this.$store.dispatch('addConversation', conversation) - this.$router.push({ name: 'conversation', params: { token: conversation.token } }).catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`)) - console.debug(response) - EventBus.$emit('resetSearchFilter') - }, - }, -} -</script> - -<style lang="scss" scoped> -.ellipsis { - text-overflow: ellipsis; -} -.contacts-list { - overflow: visible; - display: block; -} - -</style> diff --git a/src/components/LeftSidebar/ConversationsList/ConversationsList.vue b/src/components/LeftSidebar/ConversationsList/ConversationsList.vue index ca470df81..446024676 100644 --- a/src/components/LeftSidebar/ConversationsList/ConversationsList.vue +++ b/src/components/LeftSidebar/ConversationsList/ConversationsList.vue @@ -111,7 +111,9 @@ export default { * Emits a global event that is used in App.vue to update the page title once the * ( if the current route is a conversation and once the conversations are received) */ - EventBus.$emit('conversationsReceived') + EventBus.$emit('conversationsReceived', { + singleConversation: false, + }) }, }, } diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue index 70e397c20..180427a7a 100644 --- a/src/components/LeftSidebar/LeftSidebar.vue +++ b/src/components/LeftSidebar/LeftSidebar.vue @@ -39,7 +39,9 @@ <Caption :title="t('spreed', 'Contacts')" /> <li v-if="searchResultsUsers.length !== 0"> - <ContactsList :contacts="searchResultsUsers" /> + <ParticipantOptionsList + :items="searchResultsUsers" + @click="createAndJoinConversation" /> </li> </template> @@ -47,7 +49,9 @@ <Caption :title="t('spreed', 'Groups')" /> <li v-if="searchResultsGroups.length !== 0"> - <GroupsList :groups="searchResultsGroups" /> + <ParticipantOptionsList + :items="searchResultsGroups" + @click="createAndJoinConversation" /> </li> </template> @@ -55,7 +59,9 @@ <Caption :title="t('spreed', 'Circles')" /> <li v-if="searchResultsCircles.length !== 0"> - <CirclesList :circles="searchResultsCircles" /> + <ParticipantOptionsList + :items="searchResultsCircles" + @click="createAndJoinConversation" /> </li> </template> @@ -71,15 +77,16 @@ <script> import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation' import Caption from '../Caption' -import CirclesList from './CirclesList/CirclesList' -import ContactsList from './ContactsList/ContactsList' import ConversationsList from './ConversationsList/ConversationsList' -import GroupsList from './GroupsList/GroupsList' +import ParticipantOptionsList from '../ParticipantOptionsList' import Hint from '../Hint' import SearchBox from './SearchBox/SearchBox' import debounce from 'debounce' import { EventBus } from '../../services/EventBus' -import { searchPossibleConversations } from '../../services/conversationsService' +import { + createGroupConversation, createOneToOneConversation, + searchPossibleConversations, +} from '../../services/conversationsService' import { CONVERSATION } from '../../constants' import NewGroupConversation from './NewGroupConversation/NewGroupConversation' @@ -90,10 +97,8 @@ export default { components: { AppNavigation, Caption, - ContactsList, - CirclesList, ConversationsList, - GroupsList, + ParticipantOptionsList, Hint, SearchBox, NewGroupConversation, @@ -147,7 +152,7 @@ export default { } else { return t('spreed', 'Groups') } - } else if (!this.searchResultsGroups.length) { + } else { if (this.isCirclesEnabled && !this.searchResultsCircles.length) { return t('spreed', 'Circles') } @@ -187,6 +192,25 @@ export default { this.contactsLoading = false }, + /** + * Create a new conversation with the selected group/user/circle + * @param {Object} item The autocomplete suggestion to start a conversation with + * @param {string} item.id The ID of the target + * @param {string} item.source The source of the target + */ + async createAndJoinConversation(item) { + let response + if (item.source === 'users') { + response = await createOneToOneConversation(item.id) + } else { + response = await createGroupConversation(item.id, item.source) + } + const conversation = response.data.ocs.data + this.$store.dispatch('addConversation', conversation) + this.$router.push({ name: 'conversation', params: { token: conversation.token } }).catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`)) + EventBus.$emit('resetSearchFilter') + }, + hasOneToOneConversationWith(userId) { return !!this.conversationsList.find(conversation => conversation.type === CONVERSATION.TYPE.ONE_TO_ONE && conversation.name === userId) }, diff --git a/src/components/LeftSidebar/NewGroupConversation/SetContacts/SetContacts.vue b/src/components/LeftSidebar/NewGroupConversation/SetContacts/SetContacts.vue index 618d41018..639928130 100644 --- a/src/components/LeftSidebar/NewGroupConversation/SetContacts/SetContacts.vue +++ b/src/components/LeftSidebar/NewGroupConversation/SetContacts/SetContacts.vue @@ -31,7 +31,7 @@ @input="handleInput"> <template v-if="isSearching"> <Caption - :title="t('spreed', `Select participants`)" /> + :title="t('spreed', 'Select participants')" /> <ParticipantsList :add-on-click="false" height="250px" @@ -79,7 +79,6 @@ export default { }, computed: { - isSearching() { return this.searchText !== '' }, diff --git a/src/components/LeftSidebar/GroupsList/GroupsList.vue b/src/components/ParticipantOptionsList.vue index dd56d7320..4a62ddcfc 100644 --- a/src/components/LeftSidebar/GroupsList/GroupsList.vue +++ b/src/components/ParticipantOptionsList.vue @@ -22,33 +22,32 @@ <template> <ul class="contacts-list"> <AppContentListItem - v-for="item of groups" + v-for="item of items" :key="item.id" :title="item.label" - @click="createAndJoinConversation(item.id)"> - <template v-slot:icon> + @click="onClick(item)"> + <template + v-slot:icon> <ConversationIcon - :item="iconData" /> + :item="iconData(item)" /> </template> </AppContentListItem> </ul> </template> <script> -import ConversationIcon from '../../ConversationIcon' -import AppContentListItem from '../ConversationsList/AppContentListItem/AppContentListItem' -import { EventBus } from '../../../services/EventBus' -import { createGroupConversation } from '../../../services/conversationsService' -import { CONVERSATION } from '../../../constants' +import ConversationIcon from './ConversationIcon' +import AppContentListItem from './LeftSidebar/ConversationsList/AppContentListItem/AppContentListItem' +import { CONVERSATION } from '../constants' export default { - name: 'GroupsList', + name: 'ParticipantOptionsList', components: { ConversationIcon, AppContentListItem, }, props: { - groups: { + items: { type: Array, required: true, }, @@ -57,26 +56,25 @@ export default { default: false, }, }, - computed: { - iconData() { + methods: { + // forward click event + onClick(item) { + this.$emit('click', item) + }, + iconData(item) { + if (item.source === 'users') { + return { + type: CONVERSATION.TYPE.ONE_TO_ONE, + displayName: item.label, + name: item.id, + } + } return { type: CONVERSATION.TYPE.GROUP, + objectType: item.source, } }, }, - methods: { - /** - * Create a new conversation with the selected group. - * @param {string} groupId the ID of the clicked group. - */ - async createAndJoinConversation(groupId) { - const response = await createGroupConversation(groupId, 'groups') - const conversation = response.data.ocs.data - this.$store.dispatch('addConversation', conversation) - this.$router.push({ name: 'conversation', params: { token: conversation.token } }).catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`)) - EventBus.$emit('resetSearchFilter') - }, - }, } </script> @@ -88,5 +86,4 @@ export default { overflow: visible; display: block; } - </style> diff --git a/src/components/RightSidebar/Participants/CurrentParticipants/CurrentParticipants.vue b/src/components/RightSidebar/Participants/CurrentParticipants/CurrentParticipants.vue index cd41708cc..c06cdc746 100644 --- a/src/components/RightSidebar/Participants/CurrentParticipants/CurrentParticipants.vue +++ b/src/components/RightSidebar/Participants/CurrentParticipants/CurrentParticipants.vue @@ -35,6 +35,13 @@ export default { ParticipantsList, }, + props: { + searchText: { + type: String, + default: '', + }, + }, + computed: { token() { return this.$store.getters.getToken() @@ -46,7 +53,13 @@ export default { * @returns {array} */ participantsList() { - const participants = this.$store.getters.participantsList(this.token) + let participants = this.$store.getters.participantsList(this.token) + + if (this.searchText !== '') { + const lowerSearchText = this.searchText.toLowerCase() + participants = participants.filter(participant => participant.displayName.toLowerCase().indexOf(lowerSearchText) !== -1 || participant.userId.toLowerCase().indexOf(lowerSearchText) !== -1) + } + return participants.slice().sort(this.sortParticipants) }, }, diff --git a/src/components/RightSidebar/Participants/ParticipantsTab.vue b/src/components/RightSidebar/Participants/ParticipantsTab.vue index 0110192b6..00addc57d 100644 --- a/src/components/RightSidebar/Participants/ParticipantsTab.vue +++ b/src/components/RightSidebar/Participants/ParticipantsTab.vue @@ -26,33 +26,46 @@ v-model="searchText" :placeholder-text="t('spreed', 'Add participants to the conversation')" @input="handleInput" /> - <CurrentParticipants /> + <Caption v-if="isSearching" + :title="t('spreed', 'Participants')" /> + <CurrentParticipants + :search-text="searchText" /> <template v-if="isSearching"> - <Caption - :title="t('spreed', 'Add participants')" /> - <ParticipantsList - v-if="addableUsers.length !== 0" - :items="addableUsers" - @refreshCurrentParticipants="getParticipants" /> - <Hint v-else-if="contactsLoading" :hint="t('spreed', 'Loading')" /> - <Hint v-else :hint="t('spreed', 'No search results')" /> + <template v-if="addableUsers.length !== 0"> + <Caption + :title="t('spreed', 'Add contacts')" /> + <ParticipantOptionsList + :items="addableUsers" + @click="addParticipants" /> + </template> - <Caption - :title="t('spreed', 'Add groups')" /> - <ParticipantsList - v-if="addableGroups.length !== 0" - :items="addableGroups" - @refreshCurrentParticipants="getParticipants" /> - <Hint v-else-if="contactsLoading" :hint="t('spreed', 'Loading')" /> - <Hint v-else :hint="t('spreed', 'No search results')" /> + <template v-if="addableGroups.length !== 0"> + <Caption + :title="t('spreed', 'Add groups')" /> + <ParticipantOptionsList + :items="addableGroups" + @click="addParticipants" /> + </template> + + <template v-if="addableEmails.length !== 0"> + <Caption + :title="t('spreed', 'Add emails')" /> + <ParticipantOptionsList + :items="addableEmails" + @click="addParticipants" /> + </template> + + <template v-if="addableCircles.length !== 0"> + <Caption + :title="t('spreed', 'Add circles')" /> + <ParticipantOptionsList + :items="addableCircles" + @click="addParticipants" /> + </template> - <Caption - :title="t('spreed', 'Add circles')" /> - <ParticipantsList - v-if="addableCircles.length !== 0" - :items="addableCircles" - @refreshCurrentParticipants="getParticipants" /> - <Hint v-else-if="contactsLoading" :hint="t('spreed', 'Loading')" /> + <Caption v-if="sourcesWithoutResults" + :title="sourcesWithoutResultsList" /> + <Hint v-if="contactsLoading" :hint="t('spreed', 'Loading')" /> <Hint v-else :hint="t('spreed', 'No search results')" /> </template> </div> @@ -62,23 +75,26 @@ import Caption from '../../Caption' import CurrentParticipants from './CurrentParticipants/CurrentParticipants' import Hint from '../../Hint' -import ParticipantsList from './ParticipantsList/ParticipantsList' +import ParticipantOptionsList from '../../ParticipantOptionsList' import SearchBox from '../../LeftSidebar/SearchBox/SearchBox' import debounce from 'debounce' import { EventBus } from '../../../services/EventBus' import { CONVERSATION, WEBINAR } from '../../../constants' import { searchPossibleConversations } from '../../../services/conversationsService' -import { fetchParticipants } from '../../../services/participantsService' +import { + addParticipant, + fetchParticipants, +} from '../../../services/participantsService' import isInLobby from '../../../mixins/isInLobby' export default { name: 'ParticipantsTab', components: { CurrentParticipants, + ParticipantOptionsList, SearchBox, Caption, Hint, - ParticipantsList, }, mixins: [ @@ -97,6 +113,7 @@ export default { searchText: '', searchResults: [], contactsLoading: false, + isCirclesEnabled: true, // FIXME } }, @@ -125,6 +142,44 @@ export default { isSearching() { return this.searchText !== '' }, + + sourcesWithoutResults() { + return !this.addableUsers.length + || !this.addableGroups.length + || (this.isCirclesEnabled && !this.addableCircles.length) + }, + + sourcesWithoutResultsList() { + if (!this.addableUsers.length) { + if (!this.addableGroups.length) { + if (this.isCirclesEnabled && !this.addableCircles.length) { + return t('spreed', 'Add contacts, groups or circles') + } else { + return t('spreed', 'Add contacts or groups') + } + } else { + if (this.isCirclesEnabled && !this.addableCircles.length) { + return t('spreed', 'Add contacts or circles') + } else { + return t('spreed', 'Add contacts') + } + } + } else { + if (!this.addableGroups.length) { + if (this.isCirclesEnabled && !this.addableCircles.length) { + return t('spreed', 'Add groups or circles') + } else { + return t('spreed', 'Add groups') + } + } else { + if (this.isCirclesEnabled && !this.addableCircles.length) { + return t('spreed', 'Add circles') + } + } + } + return t('spreed', 'Add other sources') + }, + addableUsers() { if (this.searchResults !== []) { const searchResultUsers = this.searchResults.filter(item => item.source === 'users') @@ -148,6 +203,12 @@ export default { } return [] }, + addableEmails() { + if (this.searchResults !== []) { + return this.searchResults.filter((item) => item.source === 'emails') + } + return [] + }, addableCircles() { if (this.searchResults !== []) { return this.searchResults.filter((item) => item.source === 'circles') @@ -157,9 +218,8 @@ export default { }, beforeMount() { - this.getParticipants() - EventBus.$on('routeChange', this.onRouteChange) + EventBus.$on('joinedConversation', this.onJoinedConversation) // FIXME this works only temporary until signaling is fixed to be only on the calls // Then we have to search for another solution. Maybe the room list which we update @@ -169,15 +229,22 @@ export default { beforeDestroy() { EventBus.$off('routeChange', this.onRouteChange) + EventBus.$off('joinedConversation', this.onJoinedConversation) EventBus.$off('Signaling::participantListChanged', this.getParticipants) }, methods: { /** - * If the route changes, the search filter is reset and we get participants again + * If the route changes, the search filter is reset */ onRouteChange() { this.searchText = '' + }, + + /** + * If the conversation has been joined, we get the participants + */ + onJoinedConversation() { this.$nextTick(() => { this.getParticipants() }) @@ -200,16 +267,32 @@ export default { async fetchSearchResults() { try { - const response = await searchPossibleConversations(this.searchText) + const response = await searchPossibleConversations(this.searchText, this.token) this.searchResults = response.data.ocs.data this.getParticipants() this.contactsLoading = false - } catch (exeption) { - console.error(exeption) + } catch (exception) { + console.error(exception) OCP.Toast.error(t('spreed', 'An error occurred while performing the search')) } }, + /** + * Add the selected group/user/circle to the conversation + * @param {Object} item The autocomplete suggestion to start a conversation with + * @param {string} item.id The ID of the target + * @param {string} item.source The source of the target + */ + async addParticipants(item) { + try { + await addParticipant(this.token, item.id, item.source) + this.searchText = '' + this.getParticipants() + } catch (exception) { + console.debug(exception) + } + }, + async getParticipants() { if (this.token === '' || this.isInLobby) { return @@ -227,8 +310,8 @@ export default { participant, }) }) - } catch (exeption) { - console.error(exeption) + } catch (exception) { + console.error(exception) OCP.Toast.error(t('spreed', 'An error occurred while fetching the participants')) } }, diff --git a/src/router/router.js b/src/router/router.js index ee7f194c3..c67a23e5b 100644 --- a/src/router/router.js +++ b/src/router/router.js @@ -23,8 +23,9 @@ import Vue from 'vue' import Router from 'vue-router' import { generateUrl } from '@nextcloud/router' -import WelcomeView from '../views/WelcomeView.vue' import MainView from '../views/MainView.vue' +import NotFoundView from '../views/NotFoundView.vue' +import WelcomeView from '../views/WelcomeView.vue' Vue.use(Router) @@ -42,6 +43,12 @@ export default new Router({ props: true, }, { + path: '/apps/spreed/not-found', + name: 'notfound', + component: NotFoundView, + props: true, + }, + { path: '/call/:token', name: 'conversation', component: MainView, diff --git a/src/services/conversationsService.js b/src/services/conversationsService.js index b3b7a044a..c9fa47644 100644 --- a/src/services/conversationsService.js +++ b/src/services/conversationsService.js @@ -41,22 +41,35 @@ const fetchConversations = async function() { * @param {string} token The token of the conversation to be fetched. */ const fetchConversation = async function(token) { - try { - const response = await axios.get(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}`) - return response - } catch (error) { - console.debug('Error while fetching a conversation: ', error) - } + const response = await axios.get(generateOcsUrl('apps/spreed/api/v1', 2) + `room/${token}`) + return response } /** * Fetch possible conversations * @param {string} searchText The string that will be used in the search query. + * @param {string} [token] The token of the conversation (if any) */ -const searchPossibleConversations = async function(searchText) { +const searchPossibleConversations = async function(searchText, token) { + token = token || 'new' + const shareTypes = [ + SHARE.TYPE.USER, + SHARE.TYPE.GROUP, + SHARE.TYPE.CIRCLE, + ] + if (token !== 'new') { + shareTypes.push(SHARE.TYPE.EMAIL) + } + try { - const response = await axios.get(generateOcsUrl('core/autocomplete', 2) + `get` + `?format=json` + `&search=${searchText}` + `&itemType=call` + `&itemId=new` + `&shareTypes[]=${SHARE.TYPE.USER}&shareTypes[]=${SHARE.TYPE.GROUP}&shareTypes[]=${SHARE.TYPE.CIRCLE}`) - return response + return await axios.get(generateOcsUrl('core/autocomplete', 2) + `get`, { + params: { + search: searchText, + itemType: 'call', + itemId: token, + shareTypes, + }, + }) } catch (error) { console.debug('Error while searching possible conversations: ', error) } diff --git a/src/utils/signaling.js b/src/utils/signaling.js index ee2b4d11c..a468483f3 100644 --- a/src/utils/signaling.js +++ b/src/utils/signaling.js @@ -215,11 +215,7 @@ Signaling.Base.prototype.joinRoom = function(token, password) { this._joinRoomSuccess(token, result.ocs.data.sessionId) }.bind(this), error: function(result) { - reject(new Error()) - if (result.status === 404 || result.status === 503) { - // Room not found or maintenance mode - OC.redirect(OC.generateUrl('apps/spreed')) - } + reject(result) if (result.status === 403) { // This should not happen anymore since we ask for the password before diff --git a/src/views/NotFoundView.vue b/src/views/NotFoundView.vue new file mode 100644 index 000000000..6f1478f26 --- /dev/null +++ b/src/views/NotFoundView.vue @@ -0,0 +1,18 @@ +<template> + <div id="emptycontent"> + <div id="emptycontent-icon" class="icon-search" /> + <h2>{{ t('spreed', 'The conversation does not exist') }}</h2> + <p class="emptycontent-additional"> + {{ t('spreed','Join a conversation or start a new one!') }} + </p> + </div> +</template> + +<script> +export default { + name: 'NotFoundView', +} +</script> + +<style lang="scss" scoped> +</style> |