feat: recipient search

Signed-off-by: Sebastian <krupinski01@gmail.com>
This commit is contained in:
2026-06-22 22:54:02 -04:00
parent 949bbfa538
commit b419af09eb
8 changed files with 294 additions and 26 deletions
+73 -12
View File
@@ -1,8 +1,11 @@
<script setup lang="ts">
import { useRecipientLookupStore } from '@/stores/mailRecipientsStore'
import type { MessageAddressInterface } from '@MailManager/types/message'
interface Props {
to: string[]
cc: string[]
bcc: string[]
to: MessageAddressInterface[]
cc: MessageAddressInterface[]
bcc: MessageAddressInterface[]
subject: string
showCc: boolean
showBcc: boolean
@@ -10,14 +13,34 @@ interface Props {
defineProps<Props>()
defineEmits<{
'update:to': [value: string[]]
'update:cc': [value: string[]]
'update:bcc': [value: string[]]
const emit = defineEmits<{
'update:to': [value: MessageAddressInterface[]]
'update:cc': [value: MessageAddressInterface[]]
'update:bcc': [value: MessageAddressInterface[]]
'update:subject': [value: string]
'toggle:cc': []
'toggle:bcc': []
}>()
const recipientLookup = useRecipientLookupStore()
/**
* Normalize combobox entries: typed strings become bare address objects;
* picked suggestions are already MessageAddressInterface and pass through.
*/
function normalize(entries: (MessageAddressInterface | string)[]): MessageAddressInterface[] {
return entries.map(entry =>
typeof entry === 'string' ? { address: entry } : entry,
)
}
function chipLabel(item: MessageAddressInterface): string {
return item.label ? `${item.label} <${item.address}>` : item.address
}
function itemTitle(item: MessageAddressInterface): string {
return item.label || item.address
}
</script>
<template>
@@ -28,11 +51,23 @@ defineEmits<{
chips
multiple
closable-chips
return-object
no-filter
variant="outlined"
density="compact"
class="mb-2"
@update:model-value="$emit('update:to', $event)"
:items="recipientLookup.suggestions"
:loading="recipientLookup.searching"
:item-title="itemTitle"
@update:search="recipientLookup.search($event)"
@update:model-value="emit('update:to', normalize($event))"
>
<template #chip="{ item, props: chipProps }">
<v-chip v-bind="chipProps">{{ chipLabel(item) }}</v-chip>
</template>
<template #item="{ item, props: itemProps }">
<v-list-item v-bind="itemProps" :subtitle="item.label ? item.address : undefined" />
</template>
<template #append-inner>
<v-btn
size="x-small"
@@ -59,11 +94,24 @@ defineEmits<{
chips
multiple
closable-chips
return-object
no-filter
variant="outlined"
density="compact"
class="mb-2"
@update:model-value="$emit('update:cc', $event)"
/>
:items="recipientLookup.suggestions"
:loading="recipientLookup.searching"
:item-title="itemTitle"
@update:search="recipientLookup.search($event)"
@update:model-value="emit('update:cc', normalize($event))"
>
<template #chip="{ item, props: chipProps }">
<v-chip v-bind="chipProps">{{ chipLabel(item) }}</v-chip>
</template>
<template #item="{ item, props: itemProps }">
<v-list-item v-bind="itemProps" :subtitle="item.label ? item.address : undefined" />
</template>
</v-combobox>
<v-combobox
v-if="showBcc"
@@ -72,11 +120,24 @@ defineEmits<{
chips
multiple
closable-chips
return-object
no-filter
variant="outlined"
density="compact"
class="mb-2"
@update:model-value="$emit('update:bcc', $event)"
/>
:items="recipientLookup.suggestions"
:loading="recipientLookup.searching"
:item-title="itemTitle"
@update:search="recipientLookup.search($event)"
@update:model-value="emit('update:bcc', normalize($event))"
>
<template #chip="{ item, props: chipProps }">
<v-chip v-bind="chipProps">{{ chipLabel(item) }}</v-chip>
</template>
<template #item="{ item, props: itemProps }">
<v-list-item v-bind="itemProps" :subtitle="item.label ? item.address : undefined" />
</template>
</v-combobox>
<v-text-field
:model-value="subject"