summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaksim Sukharev <antreesy.web@gmail.com>2023-08-03 09:07:46 +0200
committerGitHub <noreply@github.com>2023-08-03 09:07:46 +0200
commitd739457bfca94ed3d049dbcbd9e1f57b0fcbee00 (patch)
tree61804164c8a233ea20acc847a18be2c9168a359d
parentdb952ee33d672335942d83edf483b194ab4f6680 (diff)
parenta89b55124fed73b748111e5064e3fbe9fa7776ea (diff)
Merge pull request #10095 from nextcloud/backport/9955/stable27
[stable27] feat(OpenConversationsList) - Make list of open conversations discoverable and usable
-rw-r--r--src/collections.js2
-rw-r--r--src/components/LeftSidebar/LeftSidebar.spec.js10
-rw-r--r--src/components/LeftSidebar/LeftSidebar.vue65
-rw-r--r--src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue23
-rw-r--r--src/components/LeftSidebar/OpenConversationsList/OpenConversationsList.vue79
-rw-r--r--src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue2
-rw-r--r--src/components/RoomSelector.spec.js (renamed from src/views/RoomSelector.spec.js)18
-rw-r--r--src/components/RoomSelector.vue (renamed from src/views/RoomSelector.vue)55
-rw-r--r--src/deck.js2
-rw-r--r--src/maps.js2
10 files changed, 197 insertions, 61 deletions
diff --git a/src/collections.js b/src/collections.js
index 40f458d29..037381e5d 100644
--- a/src/collections.js
+++ b/src/collections.js
@@ -41,7 +41,7 @@ import Vue from 'vue'
container.id = 'spreed-room-select'
const body = document.getElementById('body-user')
body.appendChild(container)
- const RoomSelector = () => import('./views/RoomSelector.vue')
+ const RoomSelector = () => import('./components/RoomSelector.vue')
const ComponentVM = new Vue({
render: h => h(RoomSelector, {
props: {
diff --git a/src/components/LeftSidebar/LeftSidebar.spec.js b/src/components/LeftSidebar/LeftSidebar.spec.js
index ea73f4faa..ace133521 100644
--- a/src/components/LeftSidebar/LeftSidebar.spec.js
+++ b/src/components/LeftSidebar/LeftSidebar.spec.js
@@ -13,7 +13,7 @@ import router from '../../__mocks__/router.js'
import { searchPossibleConversations, searchListedConversations } from '../../services/conversationsService.js'
import { EventBus } from '../../services/EventBus.js'
import storeConfig from '../../store/storeConfig.js'
-import { findNcListItems } from '../../test-helpers.js'
+import { findNcListItems, findNcActionButton } from '../../test-helpers.js'
jest.mock('@nextcloud/initial-state', () => ({
loadState: jest.fn(),
@@ -709,16 +709,16 @@ describe('LeftSidebar.vue', () => {
loadStateSettings.start_conversations = true
const wrapper = mountComponent()
- const buttonEl = wrapper.findComponent({ name: 'NewGroupConversation' })
- expect(buttonEl.exists()).toBeTruthy()
+ const newConversationbutton = findNcActionButton(wrapper, 'Create a new conversation')
+ expect(newConversationbutton.exists()).toBeTruthy()
})
test('does not show new conversation button if user cannot start conversations', () => {
loadStateSettings.start_conversations = false
const wrapper = mountComponent()
- const buttonEl = wrapper.findComponent({ name: 'NewGroupConversation' })
- expect(buttonEl.exists()).toBeFalsy()
+ const newConversationbutton = findNcActionButton(wrapper, 'Create a new conversation')
+ expect(newConversationbutton.exists()).toBeFalsy()
})
})
diff --git a/src/components/LeftSidebar/LeftSidebar.vue b/src/components/LeftSidebar/LeftSidebar.vue
index 1d1b67a2c..c66964f75 100644
--- a/src/components/LeftSidebar/LeftSidebar.vue
+++ b/src/components/LeftSidebar/LeftSidebar.vue
@@ -35,8 +35,8 @@
@keydown.enter.native="handleEnter"
@abort-search="abortSearch" />
- <!-- Options -->
- <div class="options"
+ <!-- Filters -->
+ <div class="filters"
:class="{'hidden-visually': isFocused}">
<NcActions class="filter-actions"
:primary="isFiltered !== null">
@@ -75,10 +75,36 @@
</NcActions>
</div>
- <!-- New Conversation -->
- <NewGroupConversation v-if="canStartConversations"
- ref="newGroupConversation"
- class="new-conversation__button" />
+ <!-- Actions -->
+ <div class="actions">
+ <NcActions class="conversations-actions">
+ <template #icon>
+ <DotsVertical :size="20" />
+ </template>
+ <NcActionButton v-if="canStartConversations"
+ close-after-click
+ @click="showModalNewConversation">
+ <template #icon>
+ <Plus :size="20" />
+ </template>
+ {{ t('spreed','Create a new conversation') }}
+ </NcActionButton>
+
+ <NcActionButton close-after-click
+ @click="showModalListConversations">
+ <template #icon>
+ <List :size="20" />
+ </template>
+ {{ t('spreed','Join open conversations') }}
+ </NcActionButton>
+ </NcActions>
+ </div>
+
+ <!-- All open conversations list -->
+ <OpenConversationsList ref="openConversationsList" />
+
+ <!-- New Conversation dialog-->
+ <NewGroupConversation ref="newGroupConversation" />
</div>
<template #list>
@@ -96,7 +122,7 @@
<LoadingPlaceholder type="conversations" />
</template>
<Hint v-else-if="noMatchFound"
- :hint="t('spreed', 'No matches')" />
+ :hint="t('spreed', 'No matches found')" />
<template v-if="isSearching">
<template v-if="!listedConversationsLoading && searchResultsListedConversations.length > 0">
<NcAppNavigationCaption :title="t('spreed', 'Open conversations')" />
@@ -182,9 +208,12 @@
import debounce from 'debounce'
import AtIcon from 'vue-material-design-icons/At.vue'
+import DotsVertical from 'vue-material-design-icons/DotsVertical.vue'
import FilterIcon from 'vue-material-design-icons/Filter.vue'
import FilterRemoveIcon from 'vue-material-design-icons/FilterRemove.vue'
+import List from 'vue-material-design-icons/FormatListBulleted.vue'
import MessageBadge from 'vue-material-design-icons/MessageBadge.vue'
+import Plus from 'vue-material-design-icons/Plus.vue'
import { showError } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
@@ -203,6 +232,7 @@ import Hint from '../Hint.vue'
import LoadingPlaceholder from '../LoadingPlaceholder.vue'
import Conversation from './ConversationsList/Conversation.vue'
import NewGroupConversation from './NewGroupConversation/NewGroupConversation.vue'
+import OpenConversationsList from './OpenConversationsList/OpenConversationsList.vue'
import SearchBox from './SearchBox/SearchBox.vue'
import { CONVERSATION } from '../../constants.js'
@@ -225,6 +255,7 @@ export default {
Hint,
SearchBox,
NewGroupConversation,
+ OpenConversationsList,
Conversation,
LoadingPlaceholder,
NcListItem,
@@ -235,6 +266,9 @@ export default {
MessageBadge,
FilterIcon,
FilterRemoveIcon,
+ Plus,
+ List,
+ DotsVertical,
},
mixins: [
@@ -383,6 +417,15 @@ export default {
getFocusableList() {
return this.$el.querySelectorAll('li.acli_wrapper .acli')
},
+
+ showModalNewConversation() {
+ this.$refs.newGroupConversation.showModal()
+ },
+
+ showModalListConversations() {
+ this.$refs.openConversationsList.showModal()
+ },
+
setIsFocused(event) {
if (event.relatedTarget?.className.includes('input-field__clear-button') || this.searchText !== '') {
return
@@ -757,19 +800,19 @@ export default {
}
-.options{
+.filters {
position: absolute;
right : 52px; // New conversation button's width
display: flex;
height: var(--default-clickable-area);
}
-.new-conversation__button{
+.actions {
position: absolute;
- right: 1px;
+ right: 5px;
}
-.filter-actions__button--active{
+.filter-actions__button--active {
background-color: var(--color-primary-element-light);
border-radius: 6px;
:deep(.action-button__longtext){
diff --git a/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue b/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue
index 3a5a02958..2f4e8a446 100644
--- a/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue
+++ b/src/components/LeftSidebar/NewGroupConversation/NewGroupConversation.vue
@@ -21,15 +21,6 @@
<template>
<div class="wrapper">
- <NcButton type="tertiary"
- class="toggle"
- :aria-label="t('spreed', 'Create a new group conversation')"
- :title="t('spreed', 'Create a new group conversation')"
- @click="showModal">
- <template #icon>
- <Plus :size="20" />
- </template>
- </NcButton>
<!-- New group form -->
<NcModal v-if="modal"
:container="container"
@@ -169,8 +160,6 @@
<script>
-import Plus from 'vue-material-design-icons/Plus.vue'
-
import { getCapabilities } from '@nextcloud/capabilities'
import { showError } from '@nextcloud/dialogs'
@@ -218,7 +207,6 @@ export default {
NcModal,
NcPasswordField,
NcTextField,
- Plus,
SetContacts,
},
@@ -305,7 +293,7 @@ export default {
})
},
},
- expose: ['showModalForItem'],
+ expose: ['showModalForItem', 'showModal'],
methods: {
showModal() {
@@ -494,15 +482,6 @@ export default {
margin: 50px auto 0 auto;
}
}
-.toggle {
- height: 44px;
- width: 44px;
- padding: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- margin: 0 var(--default-grid-baseline);
-}
.new-group-conversation {
&__header {
diff --git a/src/components/LeftSidebar/OpenConversationsList/OpenConversationsList.vue b/src/components/LeftSidebar/OpenConversationsList/OpenConversationsList.vue
new file mode 100644
index 000000000..e47e0a78c
--- /dev/null
+++ b/src/components/LeftSidebar/OpenConversationsList/OpenConversationsList.vue
@@ -0,0 +1,79 @@
+<!--
+ - @copyright Copyright (c) 2023
+ -
+ - @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>
+ <RoomSelector v-if="modal"
+ :container="container"
+ list-open-conversations
+ show-postable-only
+ :dialog-title="dialogTitle"
+ @close="closeModal"
+ @select="openConversation" />
+</template>
+
+<script>
+
+import RoomSelector from '../../RoomSelector.vue'
+
+export default {
+
+ name: 'OpenConversationsList',
+
+ components: {
+ RoomSelector,
+ },
+
+ data() {
+ return {
+ modal: false,
+ }
+ },
+
+ computed: {
+ container() {
+ return this.$store.getters.getMainContainerSelector()
+ },
+
+ dialogTitle() {
+ return t('spreed', 'Join open conversations')
+ },
+
+ },
+
+ expose: ['showModal'],
+
+ methods: {
+ showModal() {
+ this.modal = true
+ },
+
+ closeModal() {
+ this.modal = false
+ },
+
+ openConversation(token) {
+ this.$router.push({ name: 'conversation', params: { token } })
+ .catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`))
+ this.closeModal()
+ },
+ },
+
+}
+
+</script>
diff --git a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue
index cdf3b5d2b..e9f1e807f 100644
--- a/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue
+++ b/src/components/MessagesList/MessagesGroup/Message/MessageButtonsBar/Forwarder.vue
@@ -78,7 +78,7 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
-import RoomSelector from '../../../../../views/RoomSelector.vue'
+import RoomSelector from '../../../../RoomSelector.vue'
export default {
name: 'Forwarder',
diff --git a/src/views/RoomSelector.spec.js b/src/components/RoomSelector.spec.js
index e1124d445..a97e47484 100644
--- a/src/views/RoomSelector.spec.js
+++ b/src/components/RoomSelector.spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils'
+import flushPromises from 'flush-promises'
import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
@@ -85,11 +86,12 @@ describe('RoomSelector.vue', () => {
const wrapper = shallowMount(RoomSelector)
expect(axios.get).toHaveBeenCalledWith(
- generateOcsUrl('/apps/spreed/api/v4/room')
+ generateOcsUrl('/apps/spreed/api/v4/room'),
+ { params: { includeStatus: true } }
)
// need to wait for re-render, otherwise the list is not rendered yet
- await wrapper.vm.$nextTick()
+ await flushPromises()
const list = wrapper.findAll('li')
expect(list.length).toBe(3)
@@ -103,13 +105,13 @@ describe('RoomSelector.vue', () => {
showPostableOnly: true,
},
})
-
expect(axios.get).toHaveBeenCalledWith(
- generateOcsUrl('/apps/spreed/api/v4/room')
+ generateOcsUrl('/apps/spreed/api/v4/room'),
+ { params: { includeStatus: true } }
)
// need to wait for re-render, otherwise the list is not rendered yet
- await wrapper.vm.$nextTick()
+ await flushPromises()
const list = wrapper.findAll('li')
expect(list.length).toBe(2)
@@ -120,10 +122,10 @@ describe('RoomSelector.vue', () => {
const wrapper = shallowMount(RoomSelector)
expect(axios.get).toHaveBeenCalledWith(
- generateOcsUrl('/apps/spreed/api/v4/room')
+ generateOcsUrl('/apps/spreed/api/v4/room'),
+ { params: { includeStatus: true } }
)
-
- await wrapper.vm.$nextTick()
+ await flushPromises()
const eventHandler = jest.fn()
wrapper.vm.$root.$on('select', eventHandler)
diff --git a/src/views/RoomSelector.vue b/src/components/RoomSelector.vue
index c392ebf6f..a2b5630e0 100644
--- a/src/views/RoomSelector.vue
+++ b/src/components/RoomSelector.vue
@@ -53,8 +53,13 @@
<span>{{ room.displayName }}</span>
</li>
</ul>
- <div v-else-if="!loading">
- {{ t('spreed', 'No conversations found') }}
+ <div v-else-if="!loading" class="no-match-message">
+ <h2 class="no-match-title">
+ {{ noMatchFoundTitle }}
+ </h2>
+ <p v-if="noMatchFoundSubtitle" class="subtitle">
+ {{ noMatchFoundSubtitle }}
+ </p>
</div>
</div>
<div id="modal-buttons">
@@ -73,16 +78,14 @@
<script>
import Magnify from 'vue-material-design-icons/Magnify.vue'
-import axios from '@nextcloud/axios'
-import { generateOcsUrl } from '@nextcloud/router'
-
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcModal from '@nextcloud/vue/dist/Components/NcModal.js'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
-import ConversationIcon from '../components/ConversationIcon.vue'
+import ConversationIcon from './ConversationIcon.vue'
import { CONVERSATION } from '../constants.js'
+import { searchListedConversations, fetchConversations } from '../services/conversationsService.js'
export default {
name: 'RoomSelector',
@@ -108,6 +111,7 @@ export default {
type: String,
default: '',
},
+
/**
* Whether to only show conversations to which
* the user can post messages.
@@ -116,6 +120,11 @@ export default {
type: Boolean,
default: false,
},
+
+ listOpenConversations: {
+ type: Boolean,
+ default: false,
+ },
},
emits: ['close', 'select'],
data() {
@@ -142,6 +151,18 @@ export default {
return roomsTemp.filter(room => room.displayName.toLowerCase().includes(this.searchText.toLowerCase()))
}
},
+
+ noMatchFoundTitle() {
+ return this.listOpenConversations
+ ? t('spreed', 'No open conversations found')
+ : t('spreed', 'No conversations found')
+ },
+
+ noMatchFoundSubtitle() {
+ return this.listOpenConversations
+ ? t('spreed', 'Either there are no open conversations or you joined all of them.')
+ : t('spreed', 'Check spelling or use complete words.')
+ },
},
beforeMount() {
this.fetchRooms()
@@ -151,11 +172,13 @@ export default {
}
},
methods: {
- fetchRooms() {
- axios.get(generateOcsUrl('/apps/spreed/api/v4/room')).then((response) => {
- this.rooms = response.data.ocs.data.sort(this.sortConversations)
- this.loading = false
- })
+ async fetchRooms() {
+ const response = this.listOpenConversations
+ ? await searchListedConversations({ searchText: '' }, {})
+ : await fetchConversations({})
+
+ this.rooms = response.data.ocs.data.sort(this.sortConversations)
+ this.loading = false
},
sortConversations(conversation1, conversation2) {
if (conversation1.isFavorite !== conversation2.isFavorite) {
@@ -221,6 +244,16 @@ export default {
height: 100%;
}
+.no-match-message{
+ padding: 40px 0;
+ text-align: center;
+
+}
+
+.no-match-title{
+ font-weight: normal;
+}
+
li {
padding: 6px;
border: 1px solid transparent;
diff --git a/src/deck.js b/src/deck.js
index 7a4203c61..720eac7b7 100644
--- a/src/deck.js
+++ b/src/deck.js
@@ -27,7 +27,7 @@ import { showSuccess, showError } from '@nextcloud/dialogs'
import {