Skip to content

Commit e7faeba

Browse files
authored
Merge pull request #11583 from nextcloud/update-room-selector
Improve room selector modal
2 parents d0c5096 + 8879269 commit e7faeba

9 files changed

+606
-310
lines changed

src/components/LeftSidebar/ConversationsList/Conversation.spec.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ describe('Conversation.vue', () => {
4545
testStoreConfig = cloneDeep(storeConfig)
4646
messagesMock = jest.fn().mockReturnValue({})
4747
testStoreConfig.modules.messagesStore.getters.messages = () => messagesMock
48-
testStoreConfig.modules.actorStore.getters.getUserId = () => jest.fn().mockReturnValue('user-id-self')
4948
store = new Vuex.Store(testStoreConfig)
5049

5150
// common defaults
5251
item = {
5352
token: TOKEN,
5453
actorId: 'actor-id-1',
54+
actorType: ATTENDEE.ACTOR_TYPE.USERS,
5555
participants: [
5656
],
5757
participantType: PARTICIPANT.TYPE.USER,
@@ -155,7 +155,7 @@ describe('Conversation.vue', () => {
155155
})
156156

157157
test('displays own last chat message with "You" as author', () => {
158-
item.lastMessage.actorId = 'user-id-self'
158+
item.lastMessage.actorId = 'actor-id-1'
159159

160160
testConversationLabel(item, 'You: hello')
161161
})
@@ -174,7 +174,7 @@ describe('Conversation.vue', () => {
174174

175175
test('displays own last message with "You" author in one to one conversations', () => {
176176
item.type = CONVERSATION.TYPE.ONE_TO_ONE
177-
item.lastMessage.actorId = 'user-id-self'
177+
item.lastMessage.actorId = 'actor-id-1'
178178

179179
testConversationLabel(item, 'You: hello')
180180
})

src/components/LeftSidebar/ConversationsList/Conversation.vue

+13-105
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138

139139
<script>
140140

141+
import { toRefs } from 'vue'
141142
import { Fragment } from 'vue-frag'
142143
import { isNavigationFailure, NavigationFailureType } from 'vue-router'
143144

@@ -162,7 +163,8 @@ import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
162163

163164
import ConversationIcon from './../../ConversationIcon.vue'
164165

165-
import { CONVERSATION, PARTICIPANT, ATTENDEE } from '../../../constants.js'
166+
import { useConversationInfo } from '../../../composables/useConversationInfo.js'
167+
import { CONVERSATION, PARTICIPANT } from '../../../constants.js'
166168
import { copyConversationLinkToClipboard } from '../../../services/urlService.js'
167169

168170
export default {
@@ -215,6 +217,16 @@ export default {
215217

216218
emits: ['click'],
217219

220+
setup(props) {
221+
const { item, isSearchResult } = toRefs(props)
222+
const { counterType, conversationInformation } = useConversationInfo({ item, isSearchResult })
223+
224+
return {
225+
counterType,
226+
conversationInformation,
227+
}
228+
},
229+
218230
data() {
219231
return {
220232
isDialogOpen: false,
@@ -226,18 +238,6 @@ export default {
226238
return this.$store.getters.getMainContainerSelector()
227239
},
228240

229-
counterType() {
230-
if (this.item.unreadMentionDirect || (this.item.unreadMessages !== 0 && (
231-
this.item.type === CONVERSATION.TYPE.ONE_TO_ONE || this.item.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER
232-
))) {
233-
return 'highlighted'
234-
} else if (this.item.unreadMention) {
235-
return 'outlined'
236-
} else {
237-
return ''
238-
}
239-
},
240-
241241
canFavorite() {
242242
return this.item.participantType !== PARTICIPANT.TYPE.USER_SELF_JOINED
243243
},
@@ -266,105 +266,13 @@ export default {
266266
return this.item.canLeaveConversation
267267
},
268268

269-
conversationInformation() {
270-
// temporary item while joining
271-
if (!this.isSearchResult && !this.item.actorId) {
272-
return t('spreed', 'Joining conversation …')
273-
}
274-
275-
if (!Object.keys(this.lastChatMessage).length) {
276-
return ''
277-
}
278-
279-
if (this.shortLastChatMessageAuthor === '') {
280-
return this.simpleLastChatMessage
281-
}
282-
283-
if (this.lastChatMessage.actorId === this.$store.getters.getUserId()) {
284-
return t('spreed', 'You: {lastMessage}', {
285-
lastMessage: this.simpleLastChatMessage,
286-
}, undefined, {
287-
escape: false,
288-
sanitize: false,
289-
})
290-
}
291-
292-
if (this.item.type === CONVERSATION.TYPE.ONE_TO_ONE
293-
|| this.item.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER
294-
|| this.item.type === CONVERSATION.TYPE.CHANGELOG) {
295-
return this.simpleLastChatMessage
296-
}
297-
298-
return t('spreed', '{actor}: {lastMessage}', {
299-
actor: this.shortLastChatMessageAuthor,
300-
lastMessage: this.simpleLastChatMessage,
301-
}, undefined, {
302-
escape: false,
303-
sanitize: false,
304-
})
305-
},
306-
307-
// Get the last message for this conversation from the message store instead
308-
// of the conversations store. The message store is updated immediately,
309-
// while the conversations store is refreshed every 30 seconds. This allows
310-
// to display message previews in this component as soon as new messages are
311-
// received by the server.
312-
lastChatMessage() {
313-
return this.item.lastMessage
314-
},
315-
316269
dialogMessage() {
317270
return t('spreed', 'Do you really want to delete "{displayName}"?', this.item, undefined, {
318271
escape: false,
319272
sanitize: false,
320273
})
321274
},
322275

323-
/**
324-
* This is a simplified version of the last chat message.
325-
* Parameters are parsed without markup (just replaced with the name),
326-
* e.g. no avatars on mentions.
327-
*
328-
* @return {string} A simple message to show below the conversation name
329-
*/
330-
simpleLastChatMessage() {
331-
if (!Object.keys(this.lastChatMessage).length) {
332-
return ''
333-
}
334-
335-
const params = this.lastChatMessage.messageParameters
336-
let subtitle = this.lastChatMessage.message.trim()
337-
338-
// We don't really use rich objects in the subtitle, instead we fall back to the name of the item
339-
Object.keys(params).forEach((parameterKey) => {
340-
subtitle = subtitle.replace('{' + parameterKey + '}', params[parameterKey].name)
341-
})
342-
343-
return subtitle
344-
},
345-
346-
/**
347-
* @return {string} Part of the name until the first space
348-
*/
349-
shortLastChatMessageAuthor() {
350-
if (!Object.keys(this.lastChatMessage).length
351-
|| this.lastChatMessage.systemMessage.length) {
352-
return ''
353-
}
354-
355-
let author = this.lastChatMessage.actorDisplayName.trim()
356-
const spacePosition = author.indexOf(' ')
357-
if (spacePosition !== -1) {
358-
author = author.substring(0, spacePosition)
359-
}
360-
361-
if (author.length === 0 && this.lastChatMessage.actorType === ATTENDEE.ACTOR_TYPE.GUESTS) {
362-
return t('spreed', 'Guest')
363-
}
364-
365-
return author
366-
},
367-
368276
to() {
369277
return this.item?.token
370278
? {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<!--
2+
- @copyright Copyright (c) 2024 Maksim Sukharev <antreesy.web@gmail.com>
3+
-
4+
- @author Joas Schilling <[email protected]>
5+
- @author Maksim Sukharev <[email protected]>
6+
-
7+
- @license AGPL-3.0-or-later
8+
-
9+
- This program is free software: you can redistribute it and/or modify
10+
- it under the terms of the GNU Affero General Public License as
11+
- published by the Free Software Foundation, either version 3 of the
12+
- License, or (at your option) any later version.
13+
-
14+
- This program is distributed in the hope that it will be useful,
15+
- but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
- GNU Affero General Public License for more details.
18+
-
19+
- You should have received a copy of the GNU Affero General Public License
20+
- along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
-->
22+
23+
<template>
24+
<NcListItem :key="item.token"
25+
:name="item.displayName"
26+
:title="item.displayName"
27+
:active="item.token === selectedRoom"
28+
:bold="exposeMessages && !!item.unreadMessages"
29+
:counter-number="exposeMessages ? item.unreadMessages : 0"
30+
:counter-type="counterType"
31+
@click="onClick">
32+
<template #icon>
33+
<ConversationIcon :item="item" :hide-favorite="!item?.attendeeId" :hide-call="!item?.attendeeId" />
34+
</template>
35+
<template v-if="conversationInformation" #subname>
36+
{{ conversationInformation }}
37+
</template>
38+
</NcListItem>
39+
</template>
40+
41+
<script>
42+
import { inject, toRefs } from 'vue'
43+
44+
import NcListItem from '@nextcloud/vue/dist/Components/NcListItem.js'
45+
46+
import ConversationIcon from './../../ConversationIcon.vue'
47+
48+
import { useConversationInfo } from '../../../composables/useConversationInfo.js'
49+
50+
export default {
51+
name: 'ConversationSearchResult',
52+
53+
components: {
54+
ConversationIcon,
55+
NcListItem,
56+
},
57+
58+
props: {
59+
exposeMessages: {
60+
type: Boolean,
61+
default: false,
62+
},
63+
item: {
64+
type: Object,
65+
default() {
66+
return {
67+
token: '',
68+
participants: [],
69+
participantType: 0,
70+
unreadMessages: 0,
71+
unreadMention: false,
72+
objectType: '',
73+
type: 0,
74+
displayName: '',
75+
isFavorite: false,
76+
notificationLevel: 0,
77+
lastMessage: {},
78+
}
79+
},
80+
},
81+
},
82+
83+
emits: ['click'],
84+
85+
setup(props) {
86+
const { item, exposeMessages } = toRefs(props)
87+
const selectedRoom = inject('selectedRoom', null)
88+
const { counterType, conversationInformation } = useConversationInfo({ item, exposeMessages })
89+
90+
return {
91+
selectedRoom,
92+
counterType,
93+
conversationInformation,
94+
}
95+
},
96+
97+
methods: {
98+
onClick() {
99+
this.$emit('click', this.item)
100+
},
101+
},
102+
}
103+
</script>

src/components/LeftSidebar/ConversationsListVirtual.vue src/components/LeftSidebar/ConversationsList/ConversationsListVirtual.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@
3737
<script>
3838
import { RecycleScroller } from 'vue-virtual-scroller'
3939

40-
import Conversation from './ConversationsList/Conversation.vue'
41-
import LoadingPlaceholder from '../LoadingPlaceholder.vue'
40+
import Conversation from './Conversation.vue'
41+
import LoadingPlaceholder from '../../LoadingPlaceholder.vue'
4242

4343
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
4444

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<!--
2+
- @copyright Copyright (c) 2024 Maksim Sukharev <antreesy.web@gmail.com>
3+
-
4+
- @author Grigorii Shartsev <[email protected]>
5+
- @author Maksim Sukharev <[email protected]>
6+
-
7+
- @license AGPL-3.0-or-later
8+
-
9+
- This program is free software: you can redistribute it and/or modify
10+
- it under the terms of the GNU Affero General Public License as
11+
- published by the Free Software Foundation, either version 3 of the
12+
- License, or (at your option) any later version.
13+
-
14+
- This program is distributed in the hope that it will be useful,
15+
- but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
- GNU Affero General Public License for more details.
18+
-
19+
- You should have received a copy of the GNU Affero General Public License
20+
- along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
-->
22+
23+
<template>
24+
<RecycleScroller ref="scroller"
25+
item-tag="ul"
26+
:items="conversations"
27+
:item-size="CONVERSATION_ITEM_SIZE"
28+
key-field="token">
29+
<template #default="{ item }">
30+
<ConversationSearchResult :item="item" :expose-messages="exposeMessages" @click="onClick" />
31+
</template>
32+
<template #after>
33+
<LoadingPlaceholder v-if="loading" type="conversations" />
34+
</template>
35+
</RecycleScroller>
36+
</template>
37+
38+
<script>
39+
import { RecycleScroller } from 'vue-virtual-scroller'
40+
41+
import ConversationSearchResult from './ConversationSearchResult.vue'
42+
import LoadingPlaceholder from '../../LoadingPlaceholder.vue'
43+
44+
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
45+
46+
const CONVERSATION_ITEM_SIZE = 66
47+
48+
export default {
49+
name: 'ConversationsSearchListVirtual',
50+
51+
components: {
52+
LoadingPlaceholder,
53+
ConversationSearchResult,
54+
RecycleScroller,
55+
},
56+
57+
props: {
58+
conversations: {
59+
type: Array,
60+
required: true,
61+
},
62+
exposeMessages: {
63+
type: Boolean,
64+
default: false,
65+
},
66+
loading: {
67+
type: Boolean,
68+
default: false,
69+
},
70+
},
71+
72+
emits: ['select'],
73+
74+
setup() {
75+
return {
76+
CONVERSATION_ITEM_SIZE,
77+
}
78+
},
79+
80+
methods: {
81+
onClick(item) {
82+
this.$emit('select', item)
83+
},
84+
},
85+
}
86+
</script>

0 commit comments

Comments
 (0)