feat(chat): add sidebar search for conversations
This commit is contained in:
@@ -13,6 +13,7 @@ const activeRoomKey = ref(requestedRoom())
|
||||
const loading = ref(true), roomLoading = ref(false), sending = ref(false), creating = ref(false)
|
||||
const showRoomForm = ref(false), messagesViewport = ref(null)
|
||||
const hiddenRoomKeys = ref([]), showHiddenRooms = ref(false), preferenceBusy = ref(''), preferenceError = ref('')
|
||||
const roomSearch = ref('')
|
||||
const preferencesReady = ref(false)
|
||||
const roomForm = reactive({ name: '', key: '', topic: '' })
|
||||
const session = computed(() => `${auth.user?.id || auth.user?.user_id || ''}:${auth.activeTenant || ''}`)
|
||||
@@ -42,8 +43,17 @@ const decoratedRooms = computed(() => [
|
||||
])
|
||||
const visibleRooms = computed(() => decoratedRooms.value.filter(room => !hiddenRoomKeys.value.includes(room.key)))
|
||||
const hiddenRooms = computed(() => decoratedRooms.value.filter(room => hiddenRoomKeys.value.includes(room.key)))
|
||||
const normalizeRoomSearch = value => String(value || '').toLowerCase().normalize('NFKD').replace(/[\u0300-\u036f]/g, '').replace(/ß/g, 'ss')
|
||||
const roomSearchTerms = computed(() => normalizeRoomSearch(roomSearch.value).trim().split(/\s+/).filter(Boolean))
|
||||
function matchesRoomSearch(room) {
|
||||
const text = normalizeRoomSearch([room.name, room.topic, room.projectNumber, room.email, room.ownerName].filter(value => value != null).join(' '))
|
||||
return roomSearchTerms.value.every(term => text.includes(term))
|
||||
}
|
||||
// Filter only the sidebar; the open conversation and its draft keep their selection.
|
||||
const filteredRooms = computed(() => visibleRooms.value.filter(matchesRoomSearch))
|
||||
const filteredHiddenRooms = computed(() => hiddenRooms.value.filter(matchesRoomSearch))
|
||||
const groupedRooms = computed(() => ['Räume', 'Projekte', 'Direktnachrichten', 'Zusammenarbeit']
|
||||
.map(label => ({ label, rooms: visibleRooms.value.filter(room => room.group === label) }))
|
||||
.map(label => ({ label, rooms: filteredRooms.value.filter(room => room.group === label) }))
|
||||
.filter(group => group.rooms.length))
|
||||
const activeRoom = computed(() => visibleRooms.value.find(room => room.key === activeRoomKey.value) || null)
|
||||
const isCollaboration = computed(() => activeRoom.value?.source === 'collaboration')
|
||||
@@ -246,6 +256,7 @@ async function initialize() {
|
||||
baseRooms.value = []; projectRooms.value = []; directRooms.value = []; collaborationRooms.value = []; invitations.value = []
|
||||
unreadRooms.value = {}; invitationBusy.value = ''; collaborationError.value = ''; listError.value = ''; creating.value = false
|
||||
hiddenRoomKeys.value = []; showHiddenRooms.value = false; preferenceBusy.value = ''; preferenceError.value = ''; preferencesReady.value = false
|
||||
roomSearch.value = ''
|
||||
showRoomForm.value = false; Object.assign(roomForm, { name: '', key: '', topic: '' })
|
||||
activeRoomKey.value = requestedRoom(); loading.value = true
|
||||
await Promise.all([refreshLists(), loadRoomPreferences(scope)])
|
||||
@@ -291,6 +302,12 @@ onBeforeUnmount(() => {
|
||||
<UButton icon="i-heroicons-plus" color="neutral" variant="ghost" size="sm" @click="showRoomForm = !showRoomForm" />
|
||||
</div>
|
||||
|
||||
<UInput v-model="roomSearch" icon="i-heroicons-magnifying-glass" placeholder="Chats suchen …" aria-label="Chats suchen" class="mb-4 w-full" @keydown.esc="roomSearch = ''">
|
||||
<template v-if="roomSearch" #trailing>
|
||||
<UButton icon="i-heroicons-x-mark" color="neutral" variant="link" size="xs" aria-label="Chatsuche löschen" @click="roomSearch = ''" />
|
||||
</template>
|
||||
</UInput>
|
||||
|
||||
<form v-if="showRoomForm" class="mb-4 space-y-2 rounded-lg bg-gray-50 p-3" @submit.prevent="createRoom">
|
||||
<UInput v-model="roomForm.name" placeholder="Raumname" autofocus />
|
||||
<UInput v-model="roomForm.key" :placeholder="roomKeyPreview || 'raumschluessel'" />
|
||||
@@ -343,13 +360,14 @@ onBeforeUnmount(() => {
|
||||
<UButton icon="i-heroicons-eye-slash" color="neutral" variant="ghost" size="xs" class="mr-1 shrink-0" :aria-label="`${room.name} ausblenden`" title="Für mich ausblenden" :disabled="!preferencesReady || !!preferenceBusy" @click="setRoomHidden(room, true)" />
|
||||
</div>
|
||||
</section>
|
||||
<p v-if="!visibleRooms.length" class="px-2 text-sm text-gray-500">Keine sichtbaren Räume. Du kannst ausgeblendete Räume unten wieder einblenden.</p>
|
||||
<section v-if="hiddenRooms.length" aria-label="Ausgeblendete Räume">
|
||||
<p v-if="roomSearchTerms.length && !filteredRooms.length && !filteredHiddenRooms.length" role="status" class="px-2 text-sm text-gray-500">Keine Chats für diese Suche gefunden.</p>
|
||||
<p v-else-if="!roomSearchTerms.length && !visibleRooms.length" class="px-2 text-sm text-gray-500">Keine sichtbaren Räume. Du kannst ausgeblendete Räume unten wieder einblenden.</p>
|
||||
<section v-if="filteredHiddenRooms.length" aria-label="Ausgeblendete Räume">
|
||||
<button type="button" class="flex w-full items-center justify-between px-2 py-2 text-left text-xs font-semibold text-gray-500" :aria-expanded="showHiddenRooms" @click="showHiddenRooms = !showHiddenRooms">
|
||||
<span>Ausgeblendete Räume ({{ hiddenRooms.length }})</span><UIcon :name="showHiddenRooms ? 'i-heroicons-chevron-up' : 'i-heroicons-chevron-down'" />
|
||||
<span>Ausgeblendete Räume ({{ filteredHiddenRooms.length }})</span><UIcon :name="showHiddenRooms ? 'i-heroicons-chevron-up' : 'i-heroicons-chevron-down'" />
|
||||
</button>
|
||||
<div v-if="showHiddenRooms" class="space-y-1">
|
||||
<div v-for="room in hiddenRooms" :key="room.key" class="flex items-center gap-2 px-2 py-1">
|
||||
<div v-for="room in filteredHiddenRooms" :key="room.key" class="flex items-center gap-2 px-2 py-1">
|
||||
<span class="min-w-0 flex-1 truncate text-sm text-gray-500">{{ room.name }}</span>
|
||||
<UButton icon="i-heroicons-eye" variant="ghost" color="neutral" size="xs" :aria-label="`${room.name} einblenden`" :disabled="!!preferenceBusy" @click="setRoomHidden(room, false)">Einblenden</UButton>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user