From 0f76dc6fcfee7747b1a458b39a7c0b0d717032a0 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 27 Apr 2022 16:41:04 +0300 Subject: [PATCH 1/2] feat(params): add encoding params to support photo v3 --- CHANGELOG.md | 20 +- README.md | 58 ++- examples/export.ts | 64 +-- src/formatter/formatter.spec.ts | 776 ++++++++++++++++++-------------- src/formatter/formatter.ts | 241 ++++++---- src/vcard/vcard.ts | 43 +- 6 files changed, 699 insertions(+), 503 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc220bf..8fd8308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,20 @@ -`0.1.0` 2020-03-05 ---------------------------------------- +## `1.1.1` + +- Add encoding to list of params to support PHOTO V3 and more + +## `1.1.0` + +- Add nickname + +## `0.1.0` 2020-03-05 + - Add url field to vcard and formatter - Version bump -`0.0.5` 2019-01-19 ---------------------------------------- +## `0.0.5` 2019-01-19 + - `Fixed` gitignore preventing js files from being published -`0.0.4` 2019-01-19 ---------------------------------------- +## `0.0.4` 2019-01-19 + - `Added` photo field diff --git a/README.md b/README.md index 71f2d22..8f98a0e 100644 --- a/README.md +++ b/README.md @@ -45,91 +45,113 @@ Examples are written with Typescript in mind but you should be able to use it in _Note: IParams refers to a javascript object containing parameters used in certain vCard properties. Some methods support this, some don't. Refer to the RFC aforementioned or source/examples of this project for more details._ - ### `setFullName(fullName: string)` + Set the FN property of the vcard. This is mandatory and unique. If it doesn't exist, one will be created from name components from the N property. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- + ### `addFirstName(firstName: string): VCard` + ### `addMiddleName(middleName: string): VCard` + ### `addLastName(lastName: string): VCard` + ### `addPrefixName(pre: string): VCard` + ### `addSuffixName(suf: string): VCard` + Add a first name, middle name, last name, honorific prefix or honorific suffix respectively in the N property. The N property is unique if it exists. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- + ### `addNickname(nickname: string, params?: IParams): VCard` + Add a nickname to a NICKNAME property. ---------------------------------------------------------------------------------------------------------------------------------------------------- -### `addPhoto(uri: string, params?: IParams): VCard` -Add a photo to a PHOTO property. `uri` can be either a link to a site where the photo is hosted or a base64 data representation. +--- ---------------------------------------------------------------------------------------------------------------------------------------------------- +### `addPhoto(data: string, params?: IParams): VCard` + +Add a photo to a PHOTO property. `data` can be either a link to a site where the photo is hosted or a base64 data representation. + +--- ### `addAddress(street: string, locality: string, region: string, postCode: string, country: string, params?: IParams): VCard` + Add an address as an ADR property. Fields not available should be null or undefined. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `addPhone(number: string, params?: IParams): VCard` + Add a phone number to a TEL property. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `addEmail(email: string, params?: IParams): VCard` + Add an email to an EMAIL property. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `addTitle(title: string, params?: IParams): VCard` + Add a title in the list of the TITLE property. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `addRole(role: string, params?: IParams): VCard` + Add a role in the list of the ROLE property. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `addOrganization(organization: string, organizationUnits: string[], params?: IParams): VCard` + Add an organization to an ORG property. Organization refers to the main name of the company and organizationUnits to second or more unit names. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `addNotes(notes: string, params?: IParams): VCard` + Add a notes entry in a NOTE property. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `addUrl(url: string, params?: IParams): VCard` + Add a url entry in a URL property ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `setRevision(rev: string, params?: IParams): VCard` + Set the revision for this vcard. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `setUID(uid: string, params?: IParams): VCard` + Set the user id for this vcard. ---------------------------------------------------------------------------------------------------------------------------------------------------- +--- ### `toString(forceV3 = false): string` + Takes a VCard object as created above and formats it into a string. Note that a forceV3 argument is included, which if true, sets the VERSION property to 3.0 . This doesn't mean that this plugin supports 3.0 vcards or earlier, it's just a workaround to get your simple vcards read by older parsers found in various devices. Care should be taken using this as the card might not be readable by 3.0 or older parsers. - ## Not yet supported The following vCard properties are not yet included but might be in the future. + ``` SOURCE, KIND, XML, BDAY, ANNIVERSARY, GENDER, IMPP, LANG, TZ, GEO, LOGO, MEMBER, RELATED, CATEGORIES, PRODID, SOUND, CLIENTPIDMAP, KEY, FBURL, CALADRURI, CALURI ``` + ## Contribute Feel free to submit PRs or open issues with improvements or bug fixes. diff --git a/examples/export.ts b/examples/export.ts index 9c3fd25..9b5e0a5 100644 --- a/examples/export.ts +++ b/examples/export.ts @@ -12,25 +12,31 @@ import { Formatter, VCard } from "../src/index"; const vcard = new VCard(); vcard - .addFirstName('John') - .addLastName('Doe') - .addLastName('Foo') - .addPrefixName('Dr.') - .addEmail('jdoe@smithsonian.com') - .addEmail('doesupports@smithsonian.com') - .setUID('urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6') - .setRevision('1') - .addNotes('Jdoe\'s personal notes') - .addNickname('Jonny') - .addPhone('0-123456', { type: 'home', value: 'text'}) - .addPhone('tel:123-456-789', { type: 'work', pref: '1', value: 'uri' }) - .addTitle('Chief support officer') - .addOrganization('Smithsonian Inc.', ['North America']) - .addOrganization('Jdoe co.', []) - .addAddress('123 High Str.', '', '', 'AB-123', 'USA', { type: 'home', label: 'Doe Residence, 123 High Str., AB-123, US'}) - .addRole('Support manager') - .addUrl('www.smithsonian.com'); - + .addFirstName("John") + .addLastName("Doe") + .addLastName("Foo") + .addPrefixName("Dr.") + .addEmail("jdoe@smithsonian.com") + .addEmail("doesupports@smithsonian.com") + .setUID("urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6") + .setRevision("1") + .addNotes("Jdoe's personal notes") + .addNickname("Jonny") + .addPhone("0-123456", { type: "home", value: "text" }) + .addPhone("tel:123-456-789", { type: "work", pref: "1", value: "uri" }) + .addTitle("Chief support officer") + .addOrganization("Smithsonian Inc.", ["North America"]) + .addOrganization("Jdoe co.", []) + .addAddress("123 High Str.", "", "", "AB-123", "USA", { + type: "home", + label: "Doe Residence, 123 High Str., AB-123, US", + }) + .addRole("Support manager") + .addUrl("www.smithsonian.com") + .addPhoto( + "MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcNAQEEBQAwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bmljYXRpb25zIENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbi", + { type: "image/jpeg", encoding: "b" } + ); // set the path const path = "test.vcard"; @@ -38,24 +44,24 @@ const path = "test.vcard"; // let it do it's thang Export(vcard, path); - /**** HELPER ****/ -function Export(vcard: VCard, filepath: string, ext = '.vcard') { +function Export(vcard: VCard, filepath: string, ext = ".vcard") { const formatter = new Formatter(); const vcardString = formatter.format(vcard.toJSON()); - if(!vcardString){ - console.error('Empty string returned. Please check that your vcard is well defined'); + if (!vcardString) { + console.error( + "Empty string returned. Please check that your vcard is well defined" + ); return; } - if ((filepath.indexOf('.vcard') !== -1) || (filepath.indexOf('.vcf'))) - writeFile(filepath, vcardString, errorHandler ); - else - writeFile(filepath + ext, vcardString, errorHandler); + if (filepath.indexOf(".vcard") !== -1 || filepath.indexOf(".vcf")) + writeFile(filepath, vcardString, errorHandler); + else writeFile(filepath + ext, vcardString, errorHandler); } -function errorHandler(err){ +function errorHandler(err) { if (err) throw err; - console.log('file written successfully'); + console.log("file written successfully"); } diff --git a/src/formatter/formatter.spec.ts b/src/formatter/formatter.spec.ts index ce21bc4..6963749 100644 --- a/src/formatter/formatter.spec.ts +++ b/src/formatter/formatter.spec.ts @@ -1,584 +1,666 @@ import { VCard } from "../vcard/vcard"; import { Formatter } from "./formatter"; -describe('Formatter', () => { - it('prints a VCard with fullName', () => { +describe("Formatter", () => { + it("prints a VCard with fullName", () => { let sut = new Formatter(); - let vcard: any = new VCard({ name: { fullNames: ['John K. Doe']}}); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John K. Doe\r\n' + - 'END:VCARD'); + let vcard: any = new VCard({ name: { fullNames: ["John K. Doe"] } }); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + "VERSION:4.0\r\n" + "FN:John K. Doe\r\n" + "END:VCARD" + ); }); - it('formats a VCard with both fullName and name components', () => { + it("formats a VCard with both fullName and name components", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John K. Doe'], - firstNames: ['John'], - middleNames: ['K.', 'M.'], - lastNames: ['Doe', 'Smith'], - honorificsPre: ['Dr.'], - honorificsSuf: ['Esq.'] - } + fullNames: ["John K. Doe"], + firstNames: ["John"], + middleNames: ["K.", "M."], + lastNames: ["Doe", "Smith"], + honorificsPre: ["Dr."], + honorificsSuf: ["Esq."], + }, }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John K. Doe\r\n' + - 'N:Doe,Smith;John;K.,M.;Dr.;Esq.\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John K. Doe\r\n" + + "N:Doe,Smith;John;K.,M.;Dr.;Esq.\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with both fullName and nickname components', () => { + it("formats a VCard with both fullName and nickname components", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John K. Doe'], - firstNames: ['John'], - middleNames: ['K.', 'M.'], - lastNames: ['Doe', 'Smith'], - honorificsPre: ['Dr.'], - honorificsSuf: ['Esq.'] + fullNames: ["John K. Doe"], + firstNames: ["John"], + middleNames: ["K.", "M."], + lastNames: ["Doe", "Smith"], + honorificsPre: ["Dr."], + honorificsSuf: ["Esq."], }, - nicknames: [{value: 'Jonny'}] + nicknames: [{ value: "Jonny" }], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John K. Doe\r\n' + - 'N:Doe,Smith;John;K.,M.;Dr.;Esq.\r\n' + - 'NICKNAME:Jonny\r\n' + - 'END:VCARD'); - }) + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John K. Doe\r\n" + + "N:Doe,Smith;John;K.,M.;Dr.;Esq.\r\n" + + "NICKNAME:Jonny\r\n" + + "END:VCARD" + ); + }); - it('formats a VCard with fullName from first name components if not provided', () => { + it("formats a VCard with fullName from first name components if not provided", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - firstNames: ['John', 'Jack'], - middleNames: ['K.', 'M.'], - lastNames: ['Doe', 'Smith'], - honorificsSuf: ['Esq.', 'Esq2.'] - } + firstNames: ["John", "Jack"], + middleNames: ["K.", "M."], + lastNames: ["Doe", "Smith"], + honorificsSuf: ["Esq.", "Esq2."], + }, }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John K. Doe Esq.\r\n' + - 'N:Doe,Smith;John,Jack;K.,M.;;Esq.,Esq2.\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John K. Doe Esq.\r\n" + + "N:Doe,Smith;John,Jack;K.,M.;;Esq.,Esq2.\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a base64 photo', () => { + it("formats a VCard with a base64 photo", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - photos: [{ - value: 'http://www.example.com/pub/photos/jqpublic.gif' - }] + photos: [ + { + value: "http://www.example.com/pub/photos/jqpublic.gif", + }, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'PHOTO:http\://www.example.com/pub/photos/jqpublic.gif\r\n' + - 'END:VCARD'); - }) + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "PHOTO:http://www.example.com/pub/photos/jqpublic.gif\r\n" + + "END:VCARD" + ); + }); - it('formats a VCard with a photo url', () => { + it("formats a VCard with a photo url", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - photos: [{ - value: 'data:image/jpeg;base64,MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhv' - }] + photos: [ + { + value: "data:image/jpeg;base64,MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhv", + }, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'PHOTO:data:image/jpeg\;base64,MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhv\r\n' + - 'END:VCARD'); - }) + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "PHOTO:data:image/jpeg;base64,MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhv\r\n" + + "END:VCARD" + ); + }); - it('formats a VCard with an address', () => { + it("formats a VCard with an address", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - addresses: [{ - street: 'someStreet', - locality: 'someLocality', - region: 'someRegion', - postCode: 'somePostCode', - country: 'someCountry' - }] + addresses: [ + { + street: "someStreet", + locality: "someLocality", + region: "someRegion", + postCode: "somePostCode", + country: "someCountry", + }, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'ADR:;;someStreet;someLocality;someRegion;somePostCode;someCountry\r\n'+ - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "ADR:;;someStreet;someLocality;someRegion;somePostCode;someCountry\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with addresses in a more complicated scenario', () => { + it("formats a VCard with addresses in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, addresses: [ { - street: 'someStreet', - locality: 'someLocality', - region: 'someRegion', - country: 'someCountry' + street: "someStreet", + locality: "someLocality", + region: "someRegion", + country: "someCountry", }, { - street: 'otherStreet', - region: 'otherRegion', - params: { type: 'HOME'} + street: "otherStreet", + region: "otherRegion", + params: { type: "HOME" }, }, - {} - ] + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'ADR:;;someStreet;someLocality;someRegion;;someCountry\r\n' + - 'ADR;TYPE=HOME:;;otherStreet;;otherRegion;;\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "ADR:;;someStreet;someLocality;someRegion;;someCountry\r\n" + + "ADR;TYPE=HOME:;;otherStreet;;otherRegion;;\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a phone', () => { + it("formats a VCard with a phone", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - phones: [{ - value: '+10 012345', - params: { value: 'text' } - }] + phones: [ + { + value: "+10 012345", + params: { value: "text" }, + }, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'TEL;VALUE=text:+10 012345\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "TEL;VALUE=text:+10 012345\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with phones in a more complicated scenario', () => { + it("formats a VCard with phones in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, phones: [ { - value: '+10 012345', params: { value: 'text', pref: '1', type: 'voice,home' } + value: "+10 012345", + params: { value: "text", pref: "1", type: "voice,home" }, }, { - value: 'tel:+1-555-555-5555;ext=5555', params: { value: 'uri' } + value: "tel:+1-555-555-5555;ext=5555", + params: { value: "uri" }, }, - {} - ] + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'TEL;VALUE=text;PREF=1;TYPE="voice,home":+10 012345\r\n' + - 'TEL;VALUE=uri:tel:+1-555-555-5555;ext=5555\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + 'TEL;VALUE=text;PREF=1;TYPE="voice,home":+10 012345\r\n' + + "TEL;VALUE=uri:tel:+1-555-555-5555;ext=5555\r\n" + + "END:VCARD" + ); }); - it('formats a VCard an email', () => { + it("formats a VCard an email", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, emails: [ { - value: 'jdoe@smithsonian.com', - params: { type: 'work', pref: '1' } - } - ] + value: "jdoe@smithsonian.com", + params: { type: "work", pref: "1" }, + }, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'EMAIL;PREF=1;TYPE=work:jdoe@smithsonian.com\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "EMAIL;PREF=1;TYPE=work:jdoe@smithsonian.com\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with emails in a more complicated scenario', () => { + it("formats a VCard with emails in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, emails: [ - { value: 'jdoe@smithsoni\nan.com', params: { type: 'work', pref: '1' } }, - { value: 'jdo,e2@smith sonian.com', params: { type: null } }, - {} - ] + { + value: "jdoe@smithsoni\nan.com", + params: { type: "work", pref: "1" }, + }, + { value: "jdo,e2@smith sonian.com", params: { type: null } }, + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'EMAIL;PREF=1;TYPE=work:jdoe@smithsoni\\nan.com\r\n' + - 'EMAIL:jdo\,e2@smith sonian.com\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "EMAIL;PREF=1;TYPE=work:jdoe@smithsoni\\nan.com\r\n" + + "EMAIL:jdo,e2@smith sonian.com\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a job title', () => { + it("formats a VCard with a job title", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - titles: [{ value: 'Chief Officer' } ] + titles: [{ value: "Chief Officer" }], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'TITLE:Chief Officer\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "TITLE:Chief Officer\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with titles in a more complicated scenario', () => { + it("formats a VCard with titles in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, titles: [ - { value: 'Chief, officer\n', params: { pid: '1' } }, - { value: 'Father of 3', params: {pid: '2', altId: '3'} }, - {} - ] + { value: "Chief, officer\n", params: { pid: "1" } }, + { value: "Father of 3", params: { pid: "2", altId: "3" } }, + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'TITLE;PID=1:Chief\, officer\\n\r\n' + - 'TITLE;ALTID=3;PID=2:Father of 3\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "TITLE;PID=1:Chief, officer\\n\r\n" + + "TITLE;ALTID=3;PID=2:Father of 3\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a job role', () => { + it("formats a VCard with a job role", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - roles: [ { value: 'Project leader' } ] + roles: [{ value: "Project leader" }], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'ROLE:Project leader\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "ROLE:Project leader\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with roles in a more complicated scenario', () => { + it("formats a VCard with roles in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, roles: [ - { value: 'Project, Lead;-er\n', params: { pid: '1' } }, - { value: '\n\nFounder', params: {pid: '2', altId: '3'} }, - {} - ] + { value: "Project, Lead;-er\n", params: { pid: "1" } }, + { value: "\n\nFounder", params: { pid: "2", altId: "3" } }, + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'ROLE;PID=1:Project\, Lead\;-er\\n\r\n' + - 'ROLE;ALTID=3;PID=2:\\n\\nFounder\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "ROLE;PID=1:Project, Lead;-er\\n\r\n" + + "ROLE;ALTID=3;PID=2:\\n\\nFounder\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with an organization', () => { + it("formats a VCard with an organization", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - organizations: [{ values: ['Covve Ltd.'] } ] + organizations: [{ values: ["Covve Ltd."] }], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'ORG:Covve Ltd.\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "ORG:Covve Ltd.\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with roles in a more complicated scenario', () => { + it("formats a VCard with roles in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, organizations: [ - { values: ['Covve Ltd.', 'North American Division\nUSA'] }, - { values: ['Greatworks', 'Lumber Company', 'Inc.\n'], params: { type: 'main' }}, - {} - ] + { values: ["Covve Ltd.", "North American Division\nUSA"] }, + { + values: ["Greatworks", "Lumber Company", "Inc.\n"], + params: { type: "main" }, + }, + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'ORG:Covve Ltd.;North American Division\\nUSA\r\n' + - 'ORG;TYPE=main:Greatworks;Lumber Company;Inc.\\n\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "ORG:Covve Ltd.;North American Division\\nUSA\r\n" + + "ORG;TYPE=main:Greatworks;Lumber Company;Inc.\\n\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a note entry', () => { + it("formats a VCard with a note entry", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - notes: [{ value: 'Something noted' } ] + notes: [{ value: "Something noted" }], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'NOTE:Something noted\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "NOTE:Something noted\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with notes in a more complicated scenario', () => { + it("formats a VCard with notes in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, notes: [ - { value: 'Something noted\nwith many\nlines, of text', params: { language: 'En' } }, - { value: '\nAnother note' }, - {} - ] + { + value: "Something noted\nwith many\nlines, of text", + params: { language: "En" }, + }, + { value: "\nAnother note" }, + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'NOTE;LANGUAGE=En:Something noted\\nwith many\\nlines\, of text\r\n' + - 'NOTE:\\nAnother note\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "NOTE;LANGUAGE=En:Something noted\\nwith many\\nlines, of text\r\n" + + "NOTE:\\nAnother note\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a url entry', () => { + it("formats a VCard with a url entry", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - url: [{ value: 'https://www.covve.com' } ] + url: [{ value: "https://www.covve.com" }], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'URL:https://www.covve.com\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "URL:https://www.covve.com\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with urls in a more complicated scenario', () => { + it("formats a VCard with urls in a more complicated scenario", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, url: [ - { value: 'http://covve.com', params: { mediatype: 'text/plain' } }, - { value: '\ncovve.com\n\n' }, - {} - ] + { value: "http://covve.com", params: { mediatype: "text/plain" } }, + { value: "\ncovve.com\n\n" }, + {}, + ], }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'URL;MEDIATYPE=text/plain:http://covve.com\r\n' + - 'URL:\\ncovve.com\\n\\n\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "URL;MEDIATYPE=text/plain:http://covve.com\r\n" + + "URL:\\ncovve.com\\n\\n\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a revision', () => { + it("formats a VCard with a revision", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - revision: { value: 'f81d4fae-7dec-11d0-a765-00a0c91e6bf6' } + revision: { value: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" }, }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'REV:f81d4fae-7dec-11d0-a765-00a0c91e6bf6\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "REV:f81d4fae-7dec-11d0-a765-00a0c91e6bf6\r\n" + + "END:VCARD" + ); }); - it('formats a VCard with a uid', () => { + it("formats a VCard with a uid", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'] + fullNames: ["John"], }, - uid: { value: 'urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6' } + uid: { value: "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6" }, }); - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:John\r\n' + - 'UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6\r\n' + - 'END:VCARD'); + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:John\r\n" + + "UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6\r\n" + + "END:VCARD" + ); }); - describe('params', () => { - it('formats all params', () => { + describe("params", () => { + it("formats all params", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'], + fullNames: ["John"], params: { - label: 'someLabel', - language: 'someLanguage', - value: 'someValue', - pref: 'somePref', - altId: 'someAltId', - pid: 'somePid', - type: 'someType', - mediatype: 'someMediaType', - calscale: 'someCalScale', - sortAs: 'someSortAs', - geo: 'someGeo', - timezone: 'someTimeZone' - } - } + label: "someLabel", + language: "someLanguage", + value: "someValue", + pref: "somePref", + altId: "someAltId", + pid: "somePid", + type: "someType", + mediatype: "someMediaType", + calscale: "someCalScale", + sortAs: "someSortAs", + geo: "someGeo", + timezone: "someTimeZone", + encoding: "b", + }, + }, }); - const expectedParamString = ';LABEL=someLabel;LANGUAGE=someLanguage;VALUE=someValue;PREF=somePref;ALTID=someAltId;' + - 'PID=somePid;TYPE=someType;MEDIATYPE=someMediaType;CALSCALE=someCalScale;' + - 'SORT-AS=someSortAs;GEO=someGeo;TZ=someTimeZone'; - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN' + expectedParamString + ':John\r\n' + - 'END:VCARD'); + const expectedParamString = + ";LABEL=someLabel;LANGUAGE=someLanguage;VALUE=someValue;PREF=somePref;ALTID=someAltId;" + + "PID=somePid;TYPE=someType;MEDIATYPE=someMediaType;CALSCALE=someCalScale;" + + "SORT-AS=someSortAs;GEO=someGeo;TZ=someTimeZone;ENCODING=b"; + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN" + + expectedParamString + + ":John\r\n" + + "END:VCARD" + ); }); - it('sanitizes params', () => { + it("sanitizes params", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - fullNames: ['John'], + fullNames: ["John"], params: { - label: 'some\nla\nbel', - language: 'some:Language', + label: "some\nla\nbel", + language: "some:Language", value: '"someValue"', - pref: 'some:Pref', - altId: 'some,AltId', - pid: 'somePid;', - type: 'someType;;', - mediatype: 'some:MediaType', - calscale: 'someC,,alScale', - sortAs: ',someSortAs', - geo: ':someGeo', - timezone: 'so:me"Ti"meZone' - } - } + pref: "some:Pref", + altId: "some,AltId", + pid: "somePid;", + type: "someType;;", + mediatype: "some:MediaType", + calscale: "someC,,alScale", + sortAs: ",someSortAs", + geo: ":someGeo", + timezone: 'so:me"Ti"meZone', + encoding: 'b:"tM"', + }, + }, }); - const expectedParamString = ';LABEL=some\\nla\\nbel;LANGUAGE="some:Language";VALUE=someValue;PREF="some:Pref";ALTID="some,AltId";' + - 'PID="somePid;";TYPE="someType;;";MEDIATYPE="some:MediaType";CALSCALE="someC,,alScale";' + - 'SORT-AS=",someSortAs";GEO=":someGeo";TZ="so:meTimeZone"'; - expect(sut.format(vcard.toJSON())).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN' + expectedParamString + ':John\r\n' + - 'END:VCARD'); + const expectedParamString = + ';LABEL=some\\nla\\nbel;LANGUAGE="some:Language";VALUE=someValue;PREF="some:Pref";ALTID="some,AltId";' + + 'PID="somePid;";TYPE="someType;;";MEDIATYPE="some:MediaType";CALSCALE="someC,,alScale";' + + 'SORT-AS=",someSortAs";GEO=":someGeo";TZ="so:meTimeZone";ENCODING="b:tM"'; + expect(sut.format(vcard.toJSON())).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN" + + expectedParamString + + ":John\r\n" + + "END:VCARD" + ); }); - describe('complete examples', () => { - it('construct a vcard then format it', () => { + describe("complete examples", () => { + it("construct a vcard then format it", () => { let sut = new Formatter(); let vcard: any = new VCard({ name: { - firstNames: ['John'], - lastNames: ['Doe', 'Foo'], - honorificsPre: ['Dr.'], + firstNames: ["John"], + lastNames: ["Doe", "Foo"], + honorificsPre: ["Dr."], }, emails: [ - { value: 'jdoe@smithsonian.com' }, - { value: 'doesupports@smithsonian.com' } - ], - uid: { value : 'urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6' }, - revision: { value: '1' }, - notes: [ - { value: 'Jdoe\'s personal notes'} + { value: "jdoe@smithsonian.com" }, + { value: "doesupports@smithsonian.com" }, ], + uid: { value: "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6" }, + revision: { value: "1" }, + notes: [{ value: "Jdoe's personal notes" }], phones: [ - { value: '0-123456', params: { type: 'home', value: 'text' } }, - { value: 'tel:123-456-789', params: { type: 'work', pref: '1', value: 'uri' } } + { value: "0-123456", params: { type: "home", value: "text" } }, + { + value: "tel:123-456-789", + params: { type: "work", pref: "1", value: "uri" }, + }, ], - titles: [{ value: 'Chief support officer' }], + titles: [{ value: "Chief support officer" }], organizations: [ - { values: ['Smithsonian Inc.', 'North America'] }, - { values: ['Jdoe co.'] } + { values: ["Smithsonian Inc.", "North America"] }, + { values: ["Jdoe co."] }, ], addresses: [ { - street: '123 High Str.', - country: 'USA', - postCode: 'AB-123', - params: { type: 'home', label: 'Doe Residence, 123 High Str., AB-123, US' } - } + street: "123 High Str.", + country: "USA", + postCode: "AB-123", + params: { + type: "home", + label: "Doe Residence, 123 High Str., AB-123, US", + }, + }, ], - roles: [{ value: 'Support manager' }] + roles: [{ value: "Support manager" }], }); const result = sut.format(vcard.toJSON()); - expect(result).toEqual('BEGIN:VCARD\r\n' + - 'VERSION:4.0\r\n' + - 'FN:Dr. John Doe\r\n' + - 'N:Doe,Foo;John;;Dr.;\r\n' + - 'ADR;LABEL="Doe Residence, 123 High Str., AB-123, US";TYPE=home:;;123 High Str.;;;AB-123;USA\r\n' + - 'TEL;VALUE=text;TYPE=home:0-123456\r\n' + - 'TEL;VALUE=uri;PREF=1;TYPE=work:tel:123-456-789\r\n' + - 'EMAIL:jdoe@smithsonian.com\r\n' + - 'EMAIL:doesupports@smithsonian.com\r\n' + - 'TITLE:Chief support officer\r\n' + - 'ROLE:Support manager\r\n' + - 'ORG:Smithsonian Inc.;North America\r\n' + - 'ORG:Jdoe co.\r\n' + - 'NOTE:Jdoe\'s personal notes\r\n' + - 'REV:1\r\n' + - 'UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6\r\n' + - 'END:VCARD'); + expect(result).toEqual( + "BEGIN:VCARD\r\n" + + "VERSION:4.0\r\n" + + "FN:Dr. John Doe\r\n" + + "N:Doe,Foo;John;;Dr.;\r\n" + + 'ADR;LABEL="Doe Residence, 123 High Str., AB-123, US";TYPE=home:;;123 High Str.;;;AB-123;USA\r\n' + + "TEL;VALUE=text;TYPE=home:0-123456\r\n" + + "TEL;VALUE=uri;PREF=1;TYPE=work:tel:123-456-789\r\n" + + "EMAIL:jdoe@smithsonian.com\r\n" + + "EMAIL:doesupports@smithsonian.com\r\n" + + "TITLE:Chief support officer\r\n" + + "ROLE:Support manager\r\n" + + "ORG:Smithsonian Inc.;North America\r\n" + + "ORG:Jdoe co.\r\n" + + "NOTE:Jdoe's personal notes\r\n" + + "REV:1\r\n" + + "UID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6\r\n" + + "END:VCARD" + ); }); }); }); diff --git a/src/formatter/formatter.ts b/src/formatter/formatter.ts index 895efb3..d42d498 100644 --- a/src/formatter/formatter.ts +++ b/src/formatter/formatter.ts @@ -1,16 +1,15 @@ // TODO: line folding -import { IName, IParams, ISingleValueProperty, IVCard } from '../vcard/vcard'; +import { IName, IParams, ISingleValueProperty, IVCard } from "../vcard/vcard"; import isEmpty = require("lodash.isempty"); -const NEWLINE = '\r\n'; -const BEGIN_TOKEN = 'BEGIN:VCARD'; -const VERSION_TOKEN_V4 = 'VERSION:4.0'; -const VERSION_TOKEN_V3 = 'VERSION:3.0'; -const END_TOKEN = 'END:VCARD'; +const NEWLINE = "\r\n"; +const BEGIN_TOKEN = "BEGIN:VCARD"; +const VERSION_TOKEN_V4 = "VERSION:4.0"; +const VERSION_TOKEN_V3 = "VERSION:3.0"; +const END_TOKEN = "END:VCARD"; export class Formatter { - /** * Glues together vcard fields into a string * @@ -35,10 +34,14 @@ export class Formatter { ...this.getUrl(vCard), this.getRevision(vCard), this.getUID(vCard), - END_TOKEN + END_TOKEN, ]; return lines - .reduce((accumulator, current) => accumulator + current + (current && NEWLINE), '').trim(); + .reduce( + (accumulator, current) => accumulator + current + (current && NEWLINE), + "" + ) + .trim(); } /** @@ -47,22 +50,41 @@ export class Formatter { */ private getFullName(vCard: IVCard): string { let name = vCard.name; - if(!name || this.checkIfNameExists(name)) - throw new Error('tried to format a vcard that had no name entry while name is mandatory'); + if (!name || this.checkIfNameExists(name)) + throw new Error( + "tried to format a vcard that had no name entry while name is mandatory" + ); if (name.fullNames && name.fullNames.length) - return 'FN' + this.getFormattedParams(name.params) + ':' + this.e(name.fullNames[0]); - else if(name) { + return ( + "FN" + + this.getFormattedParams(name.params) + + ":" + + this.e(name.fullNames[0]) + ); + else if (name) { // construct from fields // TODO: rewrite this it looks terrible - return 'FN:' + this.e( - ((name.honorificsPre && !!name.honorificsPre.length) ? name.honorificsPre[0] + ' ' : '' ) + - ((name.firstNames && !!name.firstNames.length) ? name.firstNames[0] + ' ' : '' ) + - ((name.middleNames && !!name.middleNames.length) ? name.middleNames[0] + ' ' : '' ) + - ((name.lastNames && !!name.lastNames.length) ? name.lastNames[0] + ' ' : '' ) + - ((name.honorificsSuf && !!name.honorificsSuf.length) ? name.honorificsSuf[0] + ' ' : '' ) - ).trim(); - } else - return ''; + return ( + "FN:" + + this.e( + (name.honorificsPre && !!name.honorificsPre.length + ? name.honorificsPre[0] + " " + : "") + + (name.firstNames && !!name.firstNames.length + ? name.firstNames[0] + " " + : "") + + (name.middleNames && !!name.middleNames.length + ? name.middleNames[0] + " " + : "") + + (name.lastNames && !!name.lastNames.length + ? name.lastNames[0] + " " + : "") + + (name.honorificsSuf && !!name.honorificsSuf.length + ? name.honorificsSuf[0] + " " + : "") + ).trim() + ); + } else return ""; } /** @@ -70,31 +92,35 @@ export class Formatter { */ private getNameComponents(vCard: IVCard): string { let name = vCard.name; - if(!name) return ''; + if (!name) return ""; const components = [ this.concatWith(name.lastNames), this.concatWith(name.firstNames), this.concatWith(name.middleNames), this.concatWith(name.honorificsPre), - this.concatWith(name.honorificsSuf) + this.concatWith(name.honorificsSuf), ]; - if(components.every(c => c === '')) return ''; - let result = components.reduce((accumulator, current, index) => accumulator + current + (index !== 4 ? ';' : ''), ''); - return 'N:' + result; + if (components.every((c) => c === "")) return ""; + let result = components.reduce( + (accumulator, current, index) => + accumulator + current + (index !== 4 ? ";" : ""), + "" + ); + return "N:" + result; } /** * Adds the NICKNAME componeents entry. This is optional. */ private getNicknames(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.nicknames, 'NICKNAME'); + return this.getSingleValuedProperty(vCard.nicknames, "NICKNAME"); } /** * Adds the PHOTO - photo entry. Creates on for each photo in vCard.photos field. */ private getPhotos(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.photos, 'PHOTO'); + return this.getSingleValuedProperty(vCard.photos, "PHOTO"); } /** @@ -102,13 +128,22 @@ export class Formatter { */ private getAddresses(vCard: IVCard): string[] { let addresses = vCard.addresses; - if(!addresses || !addresses.length) return []; + if (!addresses || !addresses.length) return []; return addresses - .filter(addr => !!addr && !isEmpty(addr)) - .map((addr) => - 'ADR' + this.getFormattedParams(addr.params) + ':;;' + - this.e(addr.street) + ';' + this.e(addr.locality) + ';' + - this.e(addr.region) + ';' + this.e(addr.postCode) + ';' + + .filter((addr) => !!addr && !isEmpty(addr)) + .map( + (addr) => + "ADR" + + this.getFormattedParams(addr.params) + + ":;;" + + this.e(addr.street) + + ";" + + this.e(addr.locality) + + ";" + + this.e(addr.region) + + ";" + + this.e(addr.postCode) + + ";" + this.e(addr.country) ); } @@ -117,28 +152,28 @@ export class Formatter { * Adds the TEL - telephone entry. Creates one for each phone in the vCard.phones field. */ private getPhones(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.phones, 'TEL'); + return this.getSingleValuedProperty(vCard.phones, "TEL"); } /** * Adds the EMAIL - email entry. Creates one for each email in the vCard.emails field. */ private getEmails(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.emails, 'EMAIL'); + return this.getSingleValuedProperty(vCard.emails, "EMAIL"); } /** * Add the TITLE - job title entry. Creates one for each title in the vCard.titles field. */ private getTitles(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.titles, 'TITLE'); + return this.getSingleValuedProperty(vCard.titles, "TITLE"); } /** * Add the ROLE - job role entry. Creates one for each role in the vCard.roles field. */ private getRoles(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.roles, 'ROLE'); + return this.getSingleValuedProperty(vCard.roles, "ROLE"); } /** @@ -146,17 +181,23 @@ export class Formatter { */ private getOrganizations(vCard: IVCard): string[] { const orgs = vCard.organizations; - if(!orgs || !orgs.length) return []; + if (!orgs || !orgs.length) return []; return orgs - .filter(org => !!org && !!org.values && !!org.values.length) - .map((org: any) => 'ORG' + this.getFormattedParams(org.params) + ':' + org.values.map((v: string) => this.e(v)).join(';')); + .filter((org) => !!org && !!org.values && !!org.values.length) + .map( + (org: any) => + "ORG" + + this.getFormattedParams(org.params) + + ":" + + org.values.map((v: string) => this.e(v)).join(";") + ); } /** * Add the NOTE - note entry. Creates one for each note in vCard.notes */ private getNotes(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.notes, 'NOTE'); + return this.getSingleValuedProperty(vCard.notes, "NOTE"); } /** @@ -164,8 +205,10 @@ export class Formatter { */ private getRevision(vCard: IVCard): string { const rev = vCard.revision; - if(!rev || !rev.value) return ''; - return 'REV' + this.getFormattedParams(rev.params) + ':' + this.e(rev.value); + if (!rev || !rev.value) return ""; + return ( + "REV" + this.getFormattedParams(rev.params) + ":" + this.e(rev.value) + ); } /** @@ -173,15 +216,17 @@ export class Formatter { */ private getUID(vCard: IVCard): string { const uid = vCard.uid; - if(!uid || !uid.value) return ''; - return 'UID' + this.getFormattedParams(uid.params) + ':' + this.e(uid.value); + if (!uid || !uid.value) return ""; + return ( + "UID" + this.getFormattedParams(uid.params) + ":" + this.e(uid.value) + ); } /** * Add a URL - uniform resource locator entry. Creates one for each note in vCard.url */ private getUrl(vCard: IVCard): string[] { - return this.getSingleValuedProperty(vCard.url, 'URL'); + return this.getSingleValuedProperty(vCard.url, "URL"); } /** @@ -190,11 +235,11 @@ export class Formatter { * */ private e(s: string | undefined): string { - if(!s) return ''; - const escapedBackslashes = s.split('\\').join('\\\\'); - const escapedCommas = escapedBackslashes.split(',').join('\,'); - const escapedSemicolons = escapedCommas.split(';').join('\;'); - const escapedNewlines = escapedSemicolons.split('\n').join('\\n'); + if (!s) return ""; + const escapedBackslashes = s.split("\\").join("\\\\"); + const escapedCommas = escapedBackslashes.split(",").join(","); + const escapedSemicolons = escapedCommas.split(";").join(";"); + const escapedNewlines = escapedSemicolons.split("\n").join("\\n"); return escapedNewlines; } @@ -205,9 +250,13 @@ export class Formatter { * @param separator - separator to concat with * @return concatenated list */ - private concatWith(list: any[] | undefined, separator = ','): string { - if(!list || !list.length) return ''; - return list.reduce((accumulator, current) => accumulator + (accumulator? separator : '') + this.e(current), ''); + private concatWith(list: any[] | undefined, separator = ","): string { + if (!list || !list.length) return ""; + return list.reduce( + (accumulator, current) => + accumulator + (accumulator ? separator : "") + this.e(current), + "" + ); } /** @@ -218,45 +267,48 @@ export class Formatter { * @return concatenated params */ private getFormattedParams(params: IParams | undefined): string { - if(!params) return ''; - let result = ''; + if (!params) return ""; + let result = ""; - if(params.label) { + if (params.label) { result += `;LABEL=${this.sanitizeParamValue(params.label)}`; } - if(params.language) { + if (params.language) { result += `;LANGUAGE=${this.sanitizeParamValue(params.language)}`; } - if(params.value) { + if (params.value) { result += `;VALUE=${this.sanitizeParamValue(params.value)}`; } - if(params.pref) { + if (params.pref) { result += `;PREF=${this.sanitizeParamValue(params.pref)}`; } - if(params.altId) { + if (params.altId) { result += `;ALTID=${this.sanitizeParamValue(params.altId)}`; } - if(params.pid) { + if (params.pid) { result += `;PID=${this.sanitizeParamValue(params.pid)}`; } - if(params.type) { + if (params.type) { result += `;TYPE=${this.sanitizeParamValue(params.type)}`; } - if(params.mediatype) { + if (params.mediatype) { result += `;MEDIATYPE=${this.sanitizeParamValue(params.mediatype)}`; } - if(params.calscale) { + if (params.calscale) { result += `;CALSCALE=${this.sanitizeParamValue(params.calscale)}`; } - if(params.sortAs) { + if (params.sortAs) { result += `;SORT-AS=${this.sanitizeParamValue(params.sortAs)}`; } - if(params.geo) { + if (params.geo) { result += `;GEO=${this.sanitizeParamValue(params.geo)}`; } - if(params.timezone) { + if (params.timezone) { result += `;TZ=${this.sanitizeParamValue(params.timezone)}`; } + if (params.encoding) { + result += `;ENCODING=${this.sanitizeParamValue(params.encoding)}`; + } return result; } @@ -267,33 +319,46 @@ export class Formatter { * @param value - parameter value to sanitize */ private sanitizeParamValue(value: string) { - if(!value) return ''; + if (!value) return ""; // remove all double quotes - let result = value.split('"').join(''); + let result = value.split('"').join(""); // escape newlines - result = result.split('\n').join('\\n'); + result = result.split("\n").join("\\n"); // if colon, semicolon or comma appear on the string surround with double quotes - if((result.indexOf(':') !== -1) || (result.indexOf(';') !== -1 ) || (result.indexOf(',') !== -1)) - return "\"" + result + "\""; + if ( + result.indexOf(":") !== -1 || + result.indexOf(";") !== -1 || + result.indexOf(",") !== -1 + ) + return '"' + result + '"'; return result; } - private getSingleValuedProperty(entities: ISingleValueProperty[] | undefined, propertyIdentifier: string): string[] { - if(!entities || !entities.length) return []; + private getSingleValuedProperty( + entities: ISingleValueProperty[] | undefined, + propertyIdentifier: string + ): string[] { + if (!entities || !entities.length) return []; return entities - .filter(entity => !!entity && entity.value) - .map((entity) => propertyIdentifier + this.getFormattedParams(entity.params) + ':' + this.e(entity.value)); + .filter((entity) => !!entity && entity.value) + .map( + (entity) => + propertyIdentifier + + this.getFormattedParams(entity.params) + + ":" + + this.e(entity.value) + ); } - private checkIfNameExists(name: IName): boolean{ - return (!name || - (isEmpty(name.fullNames) - && isEmpty(name.firstNames) - && isEmpty(name.middleNames) - && isEmpty(name.lastNames) - && isEmpty(name.honorificsPre) - && isEmpty(name.honorificsSuf) - ) + private checkIfNameExists(name: IName): boolean { + return ( + !name || + (isEmpty(name.fullNames) && + isEmpty(name.firstNames) && + isEmpty(name.middleNames) && + isEmpty(name.lastNames) && + isEmpty(name.honorificsPre) && + isEmpty(name.honorificsSuf)) ); } } diff --git a/src/vcard/vcard.ts b/src/vcard/vcard.ts index a28abab..009b0a0 100644 --- a/src/vcard/vcard.ts +++ b/src/vcard/vcard.ts @@ -1,6 +1,6 @@ -import { Formatter } from '../formatter/formatter'; -import isEmpty = require('lodash.isempty'); -import cloneDeep = require('lodash.clonedeep'); +import { Formatter } from "../formatter/formatter"; +import isEmpty = require("lodash.isempty"); +import cloneDeep = require("lodash.clonedeep"); export interface IParams { label?: string; @@ -15,6 +15,7 @@ export interface IParams { sortAs?: string; geo?: string; timezone?: string; + encoding?: string; } export interface IName { @@ -86,7 +87,7 @@ export class VCard { data = cloneDeep(data); this._name = data.name ?? {}; this._nicknames = data.nicknames ?? []; - this._photos = data.photos ?? [] + this._photos = data.photos ?? []; this._addresses = data.addresses ?? []; this._phones = data.phones ?? []; this._emails = data.emails ?? []; @@ -113,8 +114,8 @@ export class VCard { notes: this._notes, revision: this._revision, uid: this._uid, - url: this._url - }) + url: this._url, + }); } public toVcard(forceV3 = false): string { @@ -168,17 +169,22 @@ export class VCard { return this; } - public addPhoto(uri: string, params?: IParams): VCard { - this._photos.push({ value: uri, params }); + public addPhoto(data: string, params?: IParams): VCard { + this._photos.push({ value: data, params }); return this; } - public addAddress(street: string, locality: string, region: string, - postCode: string, country: string, params?: IParams): VCard { + public addAddress( + street: string, + locality: string, + region: string, + postCode: string, + country: string, + params?: IParams + ): VCard { this._addresses = this._addresses || []; const address = { street, locality, region, postCode, country, params }; - if (!isEmpty(address)) - this._addresses.push(address); + if (!isEmpty(address)) this._addresses.push(address); return this; } @@ -206,12 +212,19 @@ export class VCard { return this; } - public addOrganization(organization: string, organizationUnits: string[], params?: IParams): VCard { - let values = organizationUnits && organizationUnits.length ? organizationUnits.slice() : []; + public addOrganization( + organization: string, + organizationUnits: string[], + params?: IParams + ): VCard { + let values = + organizationUnits && organizationUnits.length + ? organizationUnits.slice() + : []; values.splice(0, 0, organization); this._organizations = this._organizations || []; - this._organizations.push({ values , params }); + this._organizations.push({ values, params }); return this; } From adccb4668f59ad552589cb014d556e108d52f7b3 Mon Sep 17 00:00:00 2001 From: jason Date: Wed, 27 Apr 2022 16:49:16 +0300 Subject: [PATCH 2/2] 1.1.1 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5e8084c..9ebb450 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@covve/easy-vcard", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b6205d7..ba8d09d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@covve/easy-vcard", - "version": "1.1.0", + "version": "1.1.1", "description": "Simple vcard formatter", "main": "dist/index.js", "types": "dist/index.d.ts",