refactor: message properties

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-06-16 13:36:11 -04:00
parent 60cfefcfee
commit 7df98223f8
7 changed files with 181 additions and 184 deletions
+96 -39
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { ServiceObject } from '@KTXM/MailManager/models/service'
import { ServiceAddressObject } from '@KTXM/MailManager/models/address'
type AuxiliaryTab = 'addresses' | 'messages' | 'sync'
type DeleteMode = 'soft' | 'hard'
@@ -16,8 +17,8 @@ const emit = defineEmits<{
const activeTab = ref<AuxiliaryTab>('addresses')
const deleteMode = ref<DeleteMode>('soft')
const deleteDestination = ref('Trash')
const primaryAddress = ref('')
const secondaryAddresses = ref('')
const primaryAddress = ref<ServiceAddressObject>(new ServiceAddressObject())
const secondaryAddresses = ref<ServiceAddressObject[]>([])
const settingGroups = [
{
@@ -61,10 +62,6 @@ const destinationHint = computed(() => {
return 'Mailbox identifier or well-known role target, for example Trash.'
})
const secondaryAddressesHint = computed(() => {
return 'Use one address per line. Commas are also accepted.'
})
watch(
() => props.service,
service => {
@@ -86,17 +83,17 @@ watch(
}
if (sameAuxiliary(nextService.auxiliary ?? {}, nextAuxiliary)) {
if (sameAddresses(nextService, primaryAddress.value, secondaryAddresses.value)) {
if (sameAddresses(nextService)) {
return
}
}
nextService.primaryAddress = normalizePrimaryAddress(primaryAddress.value)
nextService.secondaryAddresses = normalizeSecondaryAddresses(secondaryAddresses.value)
nextService.primaryAddress = primaryAddress.value.empty ? null : primaryAddress.value
nextService.secondaryAddresses = dedupeAddresses(secondaryAddresses.value)
nextService.auxiliary = nextAuxiliary
emit('update:service', nextService)
},
{ immediate: true }
{ deep: true, immediate: true }
)
function syncFromService(service?: ServiceObject) {
@@ -105,8 +102,8 @@ function syncFromService(service?: ServiceObject) {
deleteDestination.value = typeof auxiliary.deleteDestination === 'string' && auxiliary.deleteDestination.length > 0
? auxiliary.deleteDestination
: 'Trash'
primaryAddress.value = service?.primaryAddress ?? ''
secondaryAddresses.value = (service?.secondaryAddresses ?? []).join('\n')
primaryAddress.value = service?.primaryAddress ?? new ServiceAddressObject()
secondaryAddresses.value = service?.secondaryAddresses ?? []
}
function normalizeDeleteDestination(value: string): string {
@@ -114,16 +111,23 @@ function normalizeDeleteDestination(value: string): string {
return trimmedValue.length > 0 ? trimmedValue : 'Trash'
}
function normalizePrimaryAddress(value: string): string | null {
const trimmedValue = value.trim()
return trimmedValue.length > 0 ? trimmedValue : null
function dedupeAddresses(entries: ServiceAddressObject[]): ServiceAddressObject[] {
const populated = entries.filter(entry => !entry.empty)
return populated.filter((entry, index) =>
populated.findIndex(candidate => candidate.matches(entry.address)) === index)
}
function normalizeSecondaryAddresses(value: string): string[] {
return value
.split(/\r?\n|,/)
.map(entry => entry.trim())
.filter((entry, index, entries) => entry.length > 0 && entries.indexOf(entry) === index)
function addSecondaryAddress() {
secondaryAddresses.value.push(new ServiceAddressObject())
}
function removeSecondaryAddress(index: number) {
secondaryAddresses.value.splice(index, 1)
}
function validAddress(value: string): boolean | string {
const trimmedValue = value.trim()
return trimmedValue.length === 0 || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedValue) || 'Invalid email address'
}
function sameAuxiliary(current: Record<string, any>, next: Record<string, any>): boolean {
@@ -131,9 +135,19 @@ function sameAuxiliary(current: Record<string, any>, next: Record<string, any>):
&& (current.deleteDestination ?? undefined) === (next.deleteDestination ?? undefined)
}
function sameAddresses(service: ServiceObject, nextPrimaryAddress: string, nextSecondaryAddresses: string): boolean {
return (service.primaryAddress ?? null) === normalizePrimaryAddress(nextPrimaryAddress)
&& JSON.stringify(service.secondaryAddresses) === JSON.stringify(normalizeSecondaryAddresses(nextSecondaryAddresses))
function sameAddresses(service: ServiceObject): boolean {
const nextPrimary = primaryAddress.value.empty ? null : primaryAddress.value
const nextSecondary = dedupeAddresses(secondaryAddresses.value)
return sameAddress(service.primaryAddress, nextPrimary)
&& service.secondaryAddresses.length === nextSecondary.length
&& service.secondaryAddresses.every((entry, index) => sameAddress(entry, nextSecondary[index]))
}
function sameAddress(current: ServiceAddressObject | null, next: ServiceAddressObject | null): boolean {
if (current === null || next === null) {
return current === next
}
return current.equals(next)
}
</script>
@@ -165,23 +179,66 @@ function sameAddresses(service: ServiceObject, nextPrimaryAddress: string, nextS
Configure the primary mailbox identity and any additional sender aliases exposed by this service.
</p>
<v-text-field
v-model="primaryAddress"
label="Primary Address"
variant="outlined"
prepend-inner-icon="mdi-email-outline"
class="mb-4"
/>
<div class="text-subtitle-2 mb-2">Primary Address</div>
<div class="d-flex ga-2 mb-6">
<v-text-field
v-model="primaryAddress.label"
label="Display Name"
variant="outlined"
density="compact"
hide-details="auto"
/>
<v-text-field
v-model="primaryAddress.address"
label="Email Address"
variant="outlined"
density="compact"
prepend-inner-icon="mdi-email-outline"
:rules="[validAddress]"
hide-details="auto"
/>
</div>
<v-textarea
v-model="secondaryAddresses"
label="Secondary Addresses"
variant="outlined"
prepend-inner-icon="mdi-email-multiple-outline"
rows="4"
:hint="secondaryAddressesHint"
persistent-hint
/>
<div class="text-subtitle-2 mb-2">Secondary Addresses</div>
<div
v-for="(entry, index) in secondaryAddresses"
:key="index"
class="d-flex ga-2 mb-2"
>
<v-text-field
v-model="entry.label"
label="Display Name"
variant="outlined"
density="compact"
hide-details="auto"
/>
<v-text-field
v-model="entry.address"
label="Email Address"
variant="outlined"
density="compact"
prepend-inner-icon="mdi-email-multiple-outline"
:rules="[validAddress]"
hide-details="auto"
/>
<v-btn
icon
size="small"
variant="text"
@click="removeSecondaryAddress(index)"
>
<v-icon>mdi-delete-outline</v-icon>
<v-tooltip activator="parent" location="bottom">Remove Alias</v-tooltip>
</v-btn>
</div>
<v-btn
variant="tonal"
prepend-icon="mdi-plus"
@click="addSecondaryAddress"
>
Add Alias
</v-btn>
</div>
</v-window-item>