From 457400ceeebc4698c8f377752c0c547617b48376 Mon Sep 17 00:00:00 2001 From: Linus Rath Date: Sat, 11 Apr 2026 20:15:49 +0200 Subject: [PATCH] fix: also display contact names stored in name.full #179 --- lib/jmap/types.ts | 6 ++++-- lib/vcard.ts | 2 +- stores/contact-store.ts | 19 ++++++++++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/jmap/types.ts b/lib/jmap/types.ts index 30e3885f..3dc7efbe 100644 --- a/lib/jmap/types.ts +++ b/lib/jmap/types.ts @@ -203,12 +203,14 @@ export interface ContactCard { } export interface ContactName { - components: NameComponent[]; + components?: NameComponent[]; isOrdered?: boolean; + full?: string; + defaultSeparator?: string; } export interface NameComponent { - kind: 'given' | 'surname' | 'prefix' | 'suffix' | 'additional' | 'separator' | 'credential'; + kind: 'given' | 'surname' | 'prefix' | 'suffix' | 'additional' | 'separator' | 'credential' | 'title' | 'middle' | 'given2' | 'surname2' | 'generation'; value: string; } diff --git a/lib/vcard.ts b/lib/vcard.ts index 3b6fa97c..f757c611 100644 --- a/lib/vcard.ts +++ b/lib/vcard.ts @@ -564,7 +564,7 @@ function generateSingleVCard(contact: ContactCard): string { const suffix = components.find(c => c.kind === "suffix")?.value || ""; const additional = components.find(c => c.kind === "additional")?.value || ""; - const fn = [prefix, given, additional, surname, suffix].filter(Boolean).join(" "); + const fn = [prefix, given, additional, surname, suffix].filter(Boolean).join(" ") || contact.name?.full || ""; if (fn) { lines.push(`FN:${encodeValue(fn)}`); lines.push(`N:${encodeValue(surname)};${encodeValue(given)};${encodeValue(additional)};${encodeValue(prefix)};${encodeValue(suffix)}`); diff --git a/stores/contact-store.ts b/stores/contact-store.ts index d516e621..dd844513 100644 --- a/stores/contact-store.ts +++ b/stores/contact-store.ts @@ -5,16 +5,25 @@ import type { IJMAPClient } from '@/lib/jmap/client-interface'; import { generateUUID } from '@/lib/utils'; export function getContactDisplayName(contact: ContactCard): string { - if (contact.name?.components) { - const given = contact.name.components.find(c => c.kind === 'given')?.value || ''; - const surname = contact.name.components.find(c => c.kind === 'surname')?.value || ''; - const full = [given, surname].filter(Boolean).join(' '); - if (full) return full; + if (contact.name) { + // Try given + surname from components first + if (contact.name.components && contact.name.components.length > 0) { + const given = contact.name.components.find(c => c.kind === 'given')?.value || ''; + const surname = contact.name.components.find(c => c.kind === 'surname')?.value || ''; + const full = [given, surname].filter(Boolean).join(' '); + if (full) return full; + } + // Fall back to name.full (RFC 9553 — used by Stalwart and other JMAP servers) + if (contact.name.full) return contact.name.full; } if (contact.nicknames) { const nick = Object.values(contact.nicknames)[0]; if (nick?.name) return nick.name; } + if (contact.organizations) { + const org = Object.values(contact.organizations)[0]; + if (org?.name) return org.name; + } if (contact.emails) { const email = Object.values(contact.emails)[0]; if (email?.address) return email.address;