Skip to content

Commit 5562189

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 dc805a1 commit 5562189

1 file changed

Lines changed: 120 additions & 4 deletions

File tree

‎src/components/LeftSidebar/LeftSidebar.vue‎

Lines changed: 120 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,62 @@
7474
</template>
7575
{{ t('spreed', 'Clear filters') }}
7676
</NcActionButton>
77+
78+
<template v-if="supportSortOrder">
79+
<NcActionSeparator />
80+
81+
<NcActionCaption :name="t('spreed', 'Sort conversations by')" />
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+
{{ t('spreed', 'Recent 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+
{{ t('spreed', 'Alphabetical') }}
105+
</NcActionButton>
106+
107+
<NcActionSeparator />
108+
109+
<NcActionCaption :name="t('spreed', 'Group conversations')" />
110+
111+
<NcActionButton
112+
closeAfterClick
113+
type="checkbox"
114+
:modelValue="groupMode === CONVERSATION.GROUP_MODE.PRIVATE_FIRST"
115+
@click="handleGroupMode(CONVERSATION.GROUP_MODE.PRIVATE_FIRST)">
116+
<template #icon>
117+
<IconAccountOutline :size="20" />
118+
</template>
119+
{{ t('spreed', 'Private first') }}
120+
</NcActionButton>
121+
122+
<NcActionButton
123+
closeAfterClick
124+
type="checkbox"
125+
:modelValue="groupMode === CONVERSATION.GROUP_MODE.GROUP_FIRST"
126+
@click="handleGroupMode(CONVERSATION.GROUP_MODE.GROUP_FIRST)">
127+
<template #icon>
128+
<IconAccountMultipleOutline :size="20" />
129+
</template>
130+
{{ t('spreed', 'Groups first') }}
131+
</NcActionButton>
132+
</template>
77133
</NcActions>
78134
</TransitionWrapper>
79135

@@ -310,21 +366,25 @@ import { START_LOCATION } from 'vue-router'
310366
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
311367
import NcActionCaption from '@nextcloud/vue/components/NcActionCaption'
312368
import NcActions from '@nextcloud/vue/components/NcActions'
369+
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
313370
import NcAppNavigation from '@nextcloud/vue/components/NcAppNavigation'
314371
import NcAppNavigationCaption from '@nextcloud/vue/components/NcAppNavigationCaption'
315372
import NcAppNavigationItem from '@nextcloud/vue/components/NcAppNavigationItem'
316373
import NcButton from '@nextcloud/vue/components/NcButton'
317374
import NcChip from '@nextcloud/vue/components/NcChip'
318375
import NcCounterBubble from '@nextcloud/vue/components/NcCounterBubble'
319376
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
377+
import IconAccountMultipleOutline from 'vue-material-design-icons/AccountMultipleOutline.vue'
320378
import IconAccountMultiplePlusOutline from 'vue-material-design-icons/AccountMultiplePlusOutline.vue'
379+
import IconAccountOutline from 'vue-material-design-icons/AccountOutline.vue'
321380
import IconArchiveOutline from 'vue-material-design-icons/ArchiveOutline.vue'
322381
import IconArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
323382
import IconAt from 'vue-material-design-icons/At.vue'
324383
import IconCalendarBlankOutline from 'vue-material-design-icons/CalendarBlankOutline.vue'
325384
import IconChatPlusOutline from 'vue-material-design-icons/ChatPlusOutline.vue'
385+
import IconClockOutline from 'vue-material-design-icons/ClockOutline.vue'
326386
import IconCogOutline from 'vue-material-design-icons/CogOutline.vue'
327-
import IconFilterOutline from 'vue-material-design-icons/FilterOutline.vue'
387+
import IconFilterCogOutline from 'vue-material-design-icons/FilterCogOutline.vue'
328388
import IconFilterRemoveOutline from 'vue-material-design-icons/FilterRemoveOutline.vue'
329389
import IconFormatListBulleted from 'vue-material-design-icons/FormatListBulleted.vue'
330390
import IconForumOutline from 'vue-material-design-icons/ForumOutline.vue'
@@ -334,6 +394,7 @@ import IconMessageOutline from 'vue-material-design-icons/MessageOutline.vue'
334394
import IconNoteEditOutline from 'vue-material-design-icons/NoteEditOutline.vue'
335395
import IconPhoneOutline from 'vue-material-design-icons/PhoneOutline.vue'
336396
import IconPlus from 'vue-material-design-icons/Plus.vue'
397+
import IconSortAlphabeticalAscending from 'vue-material-design-icons/SortAlphabeticalAscending.vue'
337398
import NewConversationDialog from '../NewConversationDialog/NewConversationDialog.vue'
338399
import ThreadItem from '../RightSidebar/Threads/ThreadItem.vue'
339400
import LoadingPlaceholder from '../UIShared/LoadingPlaceholder.vue'
@@ -375,6 +436,7 @@ const canModerateSipDialOut = hasTalkFeature('local', 'sip-support-dialout')
375436
const canNoteToSelf = hasTalkFeature('local', 'note-to-self')
376437
const supportsArchive = hasTalkFeature('local', 'archived-conversations-v2')
377438
const supportThreads = hasTalkFeature('local', 'threads')
439+
const supportSortOrder = getTalkConfig('local', 'conversations', 'sort-order') !== undefined
378440
379441
// TRANSLATORS The main home view
380442
const HOME_BUTTON_LABEL = t('spreed', 'Home')
@@ -407,6 +469,8 @@ export default {
407469
NcActions,
408470
NcActionButton,
409471
NcActionCaption,
472+
NcActionSeparator,
473+
NcEmptyContent,
410474
TransitionWrapper,
411475
ConversationsListVirtual,
412476
SearchConversationsResults,
@@ -415,7 +479,7 @@ export default {
415479
IconAt,
416480
IconMessageBadgeOutline,
417481
IconMessageOutline,
418-
IconFilterOutline,
482+
IconFilterCogOutline,
419483
IconFilterRemoveOutline,
420484
IconArchiveOutline,
421485
IconArrowLeft,
@@ -428,7 +492,10 @@ export default {
428492
IconCogOutline,
429493
IconFormatListBulleted,
430494
IconNoteEditOutline,
431-
NcEmptyContent,
495+
IconSortAlphabeticalAscending,
496+
IconClockOutline,
497+
IconAccountMultipleOutline,
498+
IconAccountOutline,
432499
},
433500
434501
setup() {
@@ -461,9 +528,11 @@ export default {
461528
canNoteToSelf,
462529
supportsArchive,
463530
supportThreads,
531+
supportSortOrder,
464532
showArchived,
465533
showThreadsList,
466534
settingsStore,
535+
CONVERSATION,
467536
HOME_BUTTON_LABEL,
468537
FILTER_LABELS,
469538
actorStore: useActorStore(),
@@ -502,16 +571,50 @@ export default {
502571
},
503572
504573
computed: {
574+
sortOrder() {
575+
return this.settingsStore.sortOrder
576+
},
577+
578+
groupMode() {
579+
return this.settingsStore.groupMode
580+
},
581+
505582
conversationsList() {
506583
return this.$store.getters.conversationsList
507584
},
508585
509586
sortedConversationsList() {
510587
return this.filteredConversationsList.slice()
511588
.sort((conversation1, conversation2) => {
589+
// Favorites always first
512590
if (conversation1.isFavorite !== conversation2.isFavorite) {
513591
return conversation1.isFavorite ? -1 : 1
514592
}
593+
594+
// Group mode: groups first
595+
if (this.groupMode === CONVERSATION.GROUP_MODE.GROUPS_FIRST) {
596+
const isGroup1 = conversation1.type === CONVERSATION.TYPE.GROUP || conversation1.type === CONVERSATION.TYPE.PUBLIC
597+
const isGroup2 = conversation2.type === CONVERSATION.TYPE.GROUP || conversation2.type === CONVERSATION.TYPE.PUBLIC
598+
if (isGroup1 !== isGroup2) {
599+
return isGroup1 ? -1 : 1
600+
}
601+
}
602+
603+
// Group mode: private first
604+
if (this.groupMode === CONVERSATION.GROUP_MODE.PRIVATE_FIRST) {
605+
const isPrivate1 = conversation1.type === CONVERSATION.TYPE.ONE_TO_ONE || conversation1.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER
606+
const isPrivate2 = conversation2.type === CONVERSATION.TYPE.ONE_TO_ONE || conversation2.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER
607+
if (isPrivate1 !== isPrivate2) {
608+
return isPrivate1 ? -1 : 1
609+
}
610+
}
611+
612+
// Sort order
613+
if (this.sortOrder === CONVERSATION.SORT_ORDER.ALPHABETICAL) {
614+
return (conversation1.displayName || '').localeCompare(conversation2.displayName || '')
615+
}
616+
617+
// Default: activity (most recent first)
515618
return conversation2.lastActivity - conversation1.lastActivity
516619
})
517620
},
@@ -728,6 +831,19 @@ export default {
728831
this.$refs.invitationHandler.showModal()
729832
},
730833
834+
handleSortOrder(newValue) {
835+
this.settingsStore.updateSortOrder(newValue)
836+
},
837+
838+
handleGroupMode(newValue) {
839+
if (newValue === this.groupMode) {
840+
// Reset mode
841+
this.settingsStore.updateGroupMode(CONVERSATION.GROUP_MODE.NONE)
842+
} else {
843+
this.settingsStore.updateGroupMode(newValue)
844+
}
845+
},
846+
731847
handleFilter(filter) {
732848
// Store the active filter
733849
if (filter === null) {

0 commit comments

Comments
 (0)