Skip to content

Commit 13aa6ad

Browse files
AntreesyclaudeRikdekker
committed
feat(LeftSidebar): add controls for conversations sort
- allow users to sort conversations by recent activity, alphabetical order, or put group/private chat first. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-Authored-By: Rikdekker <rik@shalution.nl> Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
1 parent 131e9fc commit 13aa6ad

1 file changed

Lines changed: 140 additions & 4 deletions

File tree

src/components/LeftSidebar/LeftSidebar.vue

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
class="filters"
2828
:class="{ 'hidden-visually': isSearching }">
2929
<template #icon>
30-
<IconFilterOutline :size="20" />
30+
<IconFilterCogOutline :size="20" />
3131
</template>
3232
<NcActionCaption :name="t('spreed', 'Filter conversations by')" />
3333

@@ -74,6 +74,74 @@
7474
</template>
7575
{{ t('spreed', 'Clear filters') }}
7676
</NcActionButton>
77+
78+
<template v-if="supportSortOrder">
79+
<NcActionSeparator />
80+
81+
<NcActionCaption :name="SORT_LABELS.SORTBY_HEADER" />
82+
83+
<NcActionButton
84+
closeAfterClick
85+
type="radio"
86+
:value="CONVERSATION.SORT_ORDER.ACTIVITY"
87+
:modelValue="sortOrder"
88+
@update:modelValue="handleSortOrder">
89+
<template #icon>
90+
<IconClockOutline :size="20" />
91+
</template>
92+
{{ SORT_LABELS.SORTBY_ACTIVITY }}
93+
</NcActionButton>
94+
95+
<NcActionButton
96+
closeAfterClick
97+
type="radio"
98+
:value="CONVERSATION.SORT_ORDER.ALPHABETICAL"
99+
:modelValue="sortOrder"
100+
@update:modelValue="handleSortOrder">
101+
<template #icon>
102+
<IconSortAlphabeticalAscending :size="20" />
103+
</template>
104+
{{ SORT_LABELS.SORTBY_ALPHABET }}
105+
</NcActionButton>
106+
107+
<NcActionSeparator />
108+
109+
<NcActionButton
110+
closeAfterClick
111+
type="radio"
112+
:value="CONVERSATION.GROUP_MODE.NONE"
113+
:modelValue="groupMode"
114+
@update:modelValue="handleGroupMode">
115+
<template #icon>
116+
<IconFormatListBulleted :size="20" />
117+
</template>
118+
{{ SORT_LABELS.GROUPBY_NONE }}
119+
</NcActionButton>
120+
121+
<NcActionButton
122+
closeAfterClick
123+
type="radio"
124+
:value="CONVERSATION.GROUP_MODE.PRIVATE_FIRST"
125+
:modelValue="groupMode"
126+
@update:modelValue="handleGroupMode">
127+
<template #icon>
128+
<IconAccountOutline :size="20" />
129+
</template>
130+
{{ SORT_LABELS.GROUPBY_PRIVATE_FIRST }}
131+
</NcActionButton>
132+
133+
<NcActionButton
134+
closeAfterClick
135+
type="radio"
136+
:value="CONVERSATION.GROUP_MODE.GROUP_FIRST"
137+
:modelValue="groupMode"
138+
@update:modelValue="handleGroupMode">
139+
<template #icon>
140+
<IconAccountMultipleOutline :size="20" />
141+
</template>
142+
{{ SORT_LABELS.GROUPBY_GROUP_FIRST }}
143+
</NcActionButton>
144+
</template>
77145
</NcActions>
78146
</TransitionWrapper>
79147

@@ -310,21 +378,25 @@ import { START_LOCATION } from 'vue-router'
310378
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
311379
import NcActionCaption from '@nextcloud/vue/components/NcActionCaption'
312380
import NcActions from '@nextcloud/vue/components/NcActions'
381+
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
313382
import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation'
314383
import NcAppNavigationCaption from '@nextcloud/vue/components/NcAppNavigationCaption'
315384
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
316385
import NcButton from '@nextcloud/vue/components/NcButton'
317386
import NcChip from '@nextcloud/vue/components/NcChip'
318387
import NcCounterBubble from '@nextcloud/vue/components/NcCounterBubble'
319388
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
389+
import IconAccountMultipleOutline from 'vue-material-design-icons/AccountMultipleOutline.vue'
320390
import IconAccountMultiplePlusOutline from 'vue-material-design-icons/AccountMultiplePlusOutline.vue'
391+
import IconAccountOutline from 'vue-material-design-icons/AccountOutline.vue'
321392
import IconArchiveOutline from 'vue-material-design-icons/ArchiveOutline.vue'
322393
import IconArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
323394
import IconAt from 'vue-material-design-icons/At.vue'
324395
import IconCalendarBlankOutline from 'vue-material-design-icons/CalendarBlankOutline.vue'
325396
import IconChatPlusOutline from 'vue-material-design-icons/ChatPlusOutline.vue'
397+
import IconClockOutline from 'vue-material-design-icons/ClockOutline.vue'
326398
import IconCogOutline from 'vue-material-design-icons/CogOutline.vue'
327-
import IconFilterOutline from 'vue-material-design-icons/FilterOutline.vue'
399+
import IconFilterCogOutline from 'vue-material-design-icons/FilterCogOutline.vue'
328400
import IconFilterRemoveOutline from 'vue-material-design-icons/FilterRemoveOutline.vue'
329401
import IconFormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'
330402
import IconForumOutline from 'vue-material-design-icons/ForumOutline.vue'
@@ -334,6 +406,7 @@ import IconMessageOutline from 'vue-material-design-icons/MessageOutline.vue'
334406
import IconNoteEditOutline from 'vue-material-design-icons/NoteEditOutline.vue'
335407
import IconPhoneOutline from 'vue-material-design-icons/PhoneOutline.vue'
336408
import IconPlus from 'vue-material-design-icons/Plus.vue'
409+
import IconSortAlphabeticalAscending from 'vue-material-design-icons/SortAlphabeticalAscending.vue'
337410
import NewConversationDialog from '../NewConversationDialog/NewConversationDialog.vue'
338411
import ThreadItem from '../RightSidebar/Threads/ThreadItem.vue'
339412
import LoadingPlaceholder from '../UIShared/LoadingPlaceholder.vue'
@@ -375,6 +448,7 @@ const canModerateSipDialOut = hasTalkFeature('local', 'sip-support-dialout')
375448
const canNoteToSelf = hasTalkFeature('local', 'note-to-self')
376449
const supportsArchive = hasTalkFeature('local', 'archived-conversations-v2')
377450
const supportThreads = hasTalkFeature('local', 'threads')
451+
const supportSortOrder = getTalkConfig('local', 'conversations', 'sort-order') !== undefined
378452
379453
// TRANSLATORS The main home view
380454
const HOME_BUTTON_LABEL = t('spreed', 'Home')
@@ -384,6 +458,21 @@ const FILTER_LABELS = {
384458
events: t('spreed', 'Meetings'),
385459
default: '',
386460
}
461+
const SORT_LABELS = {
462+
// TRANSLATORS Navigation actions: sort conversations by recent activity / sort alphabetically
463+
SORTBY_HEADER: t('spreed', 'Sort conversations'),
464+
// TRANSLATORS Navigation actions: sort conversations by recent activity / sort alphabetically
465+
SORTBY_ACTIVITY: t('spreed', 'By recent activity'),
466+
// TRANSLATORS Navigation actions: sort conversations by recent activity / sort alphabetically
467+
SORTBY_ALPHABET: t('spreed', 'Alphabetically'),
468+
469+
// TRANSLATORS Navigation actions: arrange list by putting private or group conversations on top
470+
GROUPBY_NONE: t('spreed', 'Do not group by type'),
471+
// TRANSLATORS Navigation actions: arrange list by putting private or group conversations on top
472+
GROUPBY_PRIVATE_FIRST: t('spreed', 'Show private ones first'),
473+
// TRANSLATORS Navigation actions: arrange list by putting private or group conversations on top
474+
GROUPBY_GROUP_FIRST: t('spreed', 'Show group ones first'),
475+
}
387476
388477
let actualizeDataTimeout = null
389478
@@ -407,6 +496,8 @@ export default {
407496
NcActions,
408497
NcActionButton,
409498
NcActionCaption,
499+
NcActionSeparator,
500+
NcEmptyContent,
410501
TransitionWrapper,
411502
ConversationsListVirtual,
412503
SearchConversationsResults,
@@ -415,7 +506,7 @@ export default {
415506
IconAt,
416507
IconMessageBadgeOutline,
417508
IconMessageOutline,
418-
IconFilterOutline,
509+
IconFilterCogOutline,
419510
IconFilterRemoveOutline,
420511
IconArchiveOutline,
421512
IconArrowLeft,
@@ -428,7 +519,10 @@ export default {
428519
IconCogOutline,
429520
IconFormatListBulleted,
430521
IconNoteEditOutline,
431-
NcEmptyContent,
522+
IconSortAlphabeticalAscending,
523+
IconClockOutline,
524+
IconAccountMultipleOutline,
525+
IconAccountOutline,
432526
},
433527
434528
setup() {
@@ -461,11 +555,14 @@ export default {
461555
canNoteToSelf,
462556
supportsArchive,
463557
supportThreads,
558+
supportSortOrder,
464559
showArchived,
465560
showThreadsList,
466561
settingsStore,
562+
CONVERSATION,
467563
HOME_BUTTON_LABEL,
468564
FILTER_LABELS,
565+
SORT_LABELS,
469566
actorStore: useActorStore(),
470567
chatExtrasStore: useChatExtrasStore(),
471568
tokenStore: useTokenStore(),
@@ -502,16 +599,47 @@ export default {
502599
},
503600
504601
computed: {
602+
sortOrder() {
603+
return this.settingsStore.sortOrder
604+
},
605+
606+
groupMode() {
607+
return this.settingsStore.groupMode
608+
},
609+
505610
conversationsList() {
506611
return this.$store.getters.conversationsList
507612
},
508613
509614
sortedConversationsList() {
510615
return this.filteredConversationsList.slice()
511616
.sort((conversation1, conversation2) => {
617+
// Favorites always first
512618
if (conversation1.isFavorite !== conversation2.isFavorite) {
513619
return conversation1.isFavorite ? -1 : 1
514620
}
621+
622+
if (this.groupMode !== CONVERSATION.GROUP_MODE.NONE) {
623+
const isOneToOne1 = [CONVERSATION.TYPE.ONE_TO_ONE, CONVERSATION.TYPE.ONE_TO_ONE_FORMER].includes(conversation1.type)
624+
const isOneToOne2 = [CONVERSATION.TYPE.ONE_TO_ONE, CONVERSATION.TYPE.ONE_TO_ONE_FORMER].includes(conversation2.type)
625+
626+
if (isOneToOne1 !== isOneToOne2) {
627+
if (this.groupMode === CONVERSATION.GROUP_MODE.GROUP_FIRST) {
628+
// Group mode: groups first
629+
return isOneToOne1 ? 1 : -1
630+
} else if (this.groupMode === CONVERSATION.GROUP_MODE.PRIVATE_FIRST) {
631+
// Group mode: private first
632+
return isOneToOne1 ? -1 : 1
633+
}
634+
}
635+
}
636+
637+
// Sort order: by alphabet A->Z
638+
if (this.sortOrder === CONVERSATION.SORT_ORDER.ALPHABETICAL) {
639+
return (conversation1.displayName).localeCompare(conversation2.displayName)
640+
}
641+
642+
// Default (legacy) sort order: by recent activity
515643
return conversation2.lastActivity - conversation1.lastActivity
516644
})
517645
},
@@ -728,6 +856,14 @@ export default {
728856
this.$refs.invitationHandler.showModal()
729857
},
730858
859+
handleSortOrder(newValue) {
860+
this.settingsStore.updateSortOrder(newValue)
861+
},
862+
863+
handleGroupMode(newValue) {
864+
this.settingsStore.updateGroupMode(newValue)
865+
},
866+
731867
handleFilter(filter) {
732868
// Store the active filter
733869
if (filter === null) {

0 commit comments

Comments
 (0)