Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/app/contacts-app/contact.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,41 @@ END:VCARD`);
expect(sut.addresses.length).toBe(2);
});

it('can display custom x-prefixed field types from Android vcards', () => {
const sut = Contact.fromVcard(null, `BEGIN:VCARD
VERSION:3.0
PRODID:-//Sabre//Sabre VObject 4.2.0//EN
UID:f205d9d6-f5cb-4625-be9f-54557a6c3bf0
FN:Name Surname
N:Surname;Name;;;
GROUP1.TEL;TYPE=x-starý,PREF:+1234567890
TEL;TYPE=cell:+0987654321
GROUP1.X-ABLABEL:Starý
GROUP2.X-ABLABEL:Obsolete
EMAIL;TYPE=PREF:name@domain.com
GROUP2.EMAIL;TYPE=x-obsolete:name@anotherdomain.com
END:VCARD`);

expect(sut.phones[0].types).toEqual(['starý', 'pref']);
expect(sut.phones[1].types).toEqual(['cell']);
expect(sut.emails[0].types).toEqual(['pref']);
expect(sut.emails[1].types).toEqual(['obsolete']);
});

it('can save custom field types with x-prefixes in vcards', () => {
const sut = new Contact({});

sut.phones = [
{ types: ['cell'], value: '+0987654321' },
{ types: ['starý'], value: '+1234567890' },
];

const vcard = sut.vcard();

expect(vcard).toContain('TEL;TYPE=cell:+0987654321');
expect(vcard).toContain('TEL;TYPE=x-starý:+1234567890');
});

it('contact with exceptionally empty name shows up as email', () => {
/* eslint-disable no-trailing-spaces */
const sut = Contact.fromVcard(null, `BEGIN:VCARD
Expand Down
64 changes: 61 additions & 3 deletions src/app/contacts-app/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,52 @@ export class Contact {
component: ICAL.Component;
url: string;

private static readonly standardPropertyTypes = new Set([
'acquaintance',
'agent',
'bbs',
'car',
'cell',
'child',
'co-resident',
'co-worker',
'colleague',
'contact',
'crush',
'date',
'dom',
'emergency',
'fax',
'friend',
'home',
'internet',
'intl',
'isdn',
'kin',
'me',
'met',
'modem',
'msg',
'muse',
'neighbor',
'pager',
'parcel',
'parent',
'pcs',
'personal',
'postal',
'pref',
'sibling',
'spouse',
'sweetheart',
'text',
'textphone',
'video',
'voice',
'work',
'x400',
]);

private static preprocessVcf(vcf: string): string {
// since ical.js can't parse these
// (not until https://github.com/mozilla-comm/ical.js/pull/442/files is merged anyway)
Expand All @@ -150,6 +196,18 @@ export class Contact {
return vcf.replace(group, '');
}

private static displayPropertyType(type: string): string {
return type.toLowerCase().replace(/^x-/, '');
}

private static serializePropertyType(type: string): string {
const lowerType = type.toLowerCase();
if (lowerType.startsWith('x-') || Contact.standardPropertyTypes.has(lowerType)) {
return lowerType;
}
return 'x-' + lowerType;
}

// may throw ICAL.ParserError if input is not a valid vcf
static fromVcf(vcf: string): Contact[] {
let cards = ICAL.parse(this.preprocessVcf(vcf));
Expand Down Expand Up @@ -233,7 +291,7 @@ export class Contact {
for (const e of values) {
const prop = this.component.addPropertyWithValue(name, e.value);
if (e.types.length > 0) {
prop.setParameter('type', e.types);
prop.setParameter('type', e.types.map(Contact.serializePropertyType));
}
}
}
Expand All @@ -245,7 +303,7 @@ export class Contact {
} else if (typeof types === 'string') {
types = [types];
}
return types.map(t => t.toLowerCase());
return types.map(Contact.displayPropertyType);
}

private normalizeStringProperty(p: ICAL.Property): StringValueWithTypes {
Expand Down Expand Up @@ -433,7 +491,7 @@ export class Contact {
for (const e of values) {
const prop = this.component.addPropertyWithValue('adr', e.value.values);
if (e.types.length > 0) {
prop.setParameter('type', e.types);
prop.setParameter('type', e.types.map(Contact.serializePropertyType));
}
}
}
Expand Down