feat: enhance contact management and vCard support

- Added support for parsing and generating additional vCard properties including GENDER, LOGO, SOUND, LABEL, CALURI, CALADRURI, FBURL, and SOURCE.
- Extended ContactCard interface to include new fields such as gender, media, anniversaries, online services, and personal info.
- Implemented logic to handle multi-part TLDs for domain extraction in avatars.
- Improved avatar component to prioritize contact photos and handle inline images in emails.
- Updated localization files to include new fields and labels for contact details.
- Refactored contact store to expose a method for retrieving contact photos.
- Enhanced unit tests to cover new vCard properties and ensure correct parsing and generation.
This commit is contained in:
Linus Rath
2026-03-12 15:54:09 +01:00
parent 4737f2928b
commit 7fedcb8e58
12 changed files with 2042 additions and 361 deletions
+81 -1
View File
@@ -203,6 +203,47 @@ describe("parseVCard", () => {
expect(result).toHaveLength(1);
expect(result[0].kind).toBe("group");
});
it("parses GENDER, LOGO, SOUND, LABEL, CALURI, CALADRURI, FBURL, SOURCE", () => {
const vcf = [
"BEGIN:VCARD",
"VERSION:4.0",
"FN:Jane Doe",
"GENDER:F;Female",
"LOGO;MEDIATYPE=image/png:https://example.com/logo.png",
"SOUND;MEDIATYPE=audio/ogg:https://example.com/sound.ogg",
"LABEL;TYPE=HOME:123 Main St\\nSpringfield, IL",
"ADR;TYPE=HOME:;;123 Main St;Springfield;IL;62704;US",
"CALURI:https://example.com/calendar/jane",
"CALADRURI:https://example.com/calendar/jane/schedule",
"FBURL:https://example.com/freebusy/jane",
"SOURCE:https://example.com/jane.vcf",
"EMAIL:jane@example.com",
"END:VCARD",
].join("\r\n");
const result = parseVCard(vcf);
expect(result).toHaveLength(1);
const card = result[0];
expect(card.gender).toEqual({ sex: "F", identity: "Female" });
expect(card.media?.m0).toEqual({
kind: "logo",
uri: "https://example.com/logo.png",
mediaType: "image/png",
});
expect(card.media?.m1).toEqual({
kind: "sound",
uri: "https://example.com/sound.ogg",
mediaType: "audio/ogg",
});
expect(card.calendarUri).toBe("https://example.com/calendar/jane");
expect(card.schedulingUri).toBe("https://example.com/calendar/jane/schedule");
expect(card.freeBusyUri).toBe("https://example.com/freebusy/jane");
expect(card.source).toBe("https://example.com/jane.vcf");
// LABEL sets fullAddress on the ADR entry
expect(card.addresses?.a0?.fullAddress).toBe("123 Main St\nSpringfield, IL");
});
});
describe("generateVCard", () => {
@@ -251,7 +292,7 @@ describe("generateVCard", () => {
expect(vcf).toContain("VERSION:3.0");
expect(vcf).toContain("UID:uid-1");
expect(vcf).toContain("KIND:individual");
expect(vcf).toContain("FN:Jane Smith");
expect(vcf).toContain("FN:Dr. Jane Marie Smith PhD");
expect(vcf).toContain("N:Smith;Jane;Marie;Dr.;PhD");
expect(vcf).toContain("NICKNAME:JJ");
expect(vcf).toContain("EMAIL;TYPE=WORK:jane@work.com");
@@ -282,6 +323,45 @@ describe("generateVCard", () => {
expect(lines[lines.length - 1]).toBe("END:VCARD");
});
it("exports GENDER, LOGO, SOUND, GEO, TZ, CALURI, CALADRURI, FBURL, SOURCE", () => {
const contact: ContactCard = {
id: "c-new",
addressBookIds: {},
name: {
components: [{ kind: "given", value: "Jane" }],
isOrdered: true,
},
gender: { sex: "F", identity: "Female" },
media: {
m0: { kind: "logo", uri: "https://example.com/logo.png", mediaType: "image/png" },
m1: { kind: "sound", uri: "https://example.com/sound.ogg", mediaType: "audio/ogg" },
},
addresses: {
a0: {
street: "123 Main St",
locality: "City",
coordinates: "geo:37.386013,-122.082932",
timeZone: "America/Los_Angeles",
},
},
calendarUri: "https://example.com/calendar/jane",
schedulingUri: "https://example.com/calendar/jane/schedule",
freeBusyUri: "https://example.com/freebusy/jane",
source: "https://example.com/jane.vcf",
};
const vcf = generateVCard([contact]);
expect(vcf).toContain("GENDER:F;Female");
expect(vcf).toContain("LOGO;VALUE=URI;MEDIATYPE=image/png:https://example.com/logo.png");
expect(vcf).toContain("SOUND;VALUE=URI;MEDIATYPE=audio/ogg:https://example.com/sound.ogg");
expect(vcf).toContain("GEO:geo:37.386013,-122.082932");
expect(vcf).toContain("TZ:America/Los_Angeles");
expect(vcf).toContain("CALURI:https://example.com/calendar/jane");
expect(vcf).toContain("CALADRURI:https://example.com/calendar/jane/schedule");
expect(vcf).toContain("FBURL:https://example.com/freebusy/jane");
expect(vcf).toContain("SOURCE:https://example.com/jane.vcf");
});
it("encodes special characters in values", () => {
const contact: ContactCard = {
id: "c3",