1 import generateUID from '@proton/utils/generateUID';
3 import type { ContactValue } from '../interfaces/contacts';
4 import type { VCardContact, VCardProperty } from '../interfaces/contacts/VCard';
5 import { UID_PREFIX } from './constants';
6 import { isMultiValue } from './vcard';
8 export const FIELDS_WITH_PREF = ['fn', 'email', 'tel', 'adr', 'key', 'photo'];
10 export const getStringContactValue = (value: ContactValue): string => {
11 if (Array.isArray(value)) {
12 return getStringContactValue(value[0]);
14 // Shouldnt really happen but some boolean gets there as boolean instead of strings
19 * Given a vCard field, return true if we take into consideration its PREF parameters
21 export const hasPref = (field: string) => FIELDS_WITH_PREF.includes(field);
24 * For a vCard contact, check if it contains categories
26 export const hasCategories = (vcardContact: VCardProperty[]) => {
27 return vcardContact.some(({ field, value }) => value && field === 'categories');
31 * Extract categories from a vCard contact
33 export const getContactCategories = (contact: VCardContact) => {
34 return (contact.categories || [])
35 .map(({ value, group }) => {
36 if (Array.isArray(value)) {
38 ? value.map((singleValue) => ({ name: getStringContactValue(singleValue), group }))
39 : value.map((singleValue) => ({ name: getStringContactValue(singleValue) }));
41 return group ? { name: value, group } : { name: value };
47 * Generate new group name that doesn't exist
49 export const generateNewGroupName = (existingGroups: string[] = []): string => {
54 if (existingGroups.includes(`item${index}`)) {
61 return `item${index}`;
65 * Extract emails from a vCard contact
67 export const getContactEmails = (contact: VCardContact) => {
68 return (contact.email || []).map(({ value, group }) => {
70 throw new Error('Email properties should have a group');
73 email: getStringContactValue(value),
79 export const createContactPropertyUid = () => generateUID(UID_PREFIX);
81 export const getContactPropertyUid = (uid: string) => Number(uid.replace(`${UID_PREFIX}-`, ''));
83 // TODO: Deprecate this function. See VcardProperty interface
84 export const getVCardProperties = (vCardContact: VCardContact): VCardProperty[] => {
85 return Object.values(vCardContact).flatMap((property) => {
86 if (Array.isArray(property)) {
94 export const fromVCardProperties = (vCardProperties: VCardProperty[]): VCardContact => {
95 const vCardContact = {} as VCardContact;
97 vCardProperties.forEach((property) => {
98 const field = property.field as keyof VCardContact;
100 if (isMultiValue(field)) {
101 if (!vCardContact[field]) {
102 vCardContact[field] = [] as any;
104 (vCardContact[field] as VCardProperty[]).push(property);
106 vCardContact[field] = property as any;
113 export const mergeVCard = (vCardContacts: VCardContact[]): VCardContact => {
114 return fromVCardProperties(vCardContacts.flatMap(getVCardProperties));
117 export const updateVCardContact = (vCardContact: VCardContact, vCardProperty: VCardProperty) => {
118 const properties = getVCardProperties(vCardContact);
119 const newProperties = properties.map((property) => (property.uid === vCardProperty.uid ? vCardProperty : property));
120 return fromVCardProperties(newProperties);
123 export const addVCardProperty = (vCardContact: VCardContact, vCardProperty: VCardProperty) => {
124 const properties = getVCardProperties(vCardContact);
125 const newVCardProperty = { ...vCardProperty, uid: createContactPropertyUid() };
126 properties.push(newVCardProperty);
127 const newVCardContact = fromVCardProperties(properties);
128 return { newVCardProperty, newVCardContact };
131 export const removeVCardProperty = (vCardContact: VCardContact, uid: string) => {
132 let properties = getVCardProperties(vCardContact);
134 const match = properties.find((property) => property.uid === uid);
140 properties = properties.filter((property) => property.uid !== uid);
142 // If we remove an email with groups attached to it, remove all groups properties too
143 if (match.field === 'email' && match.group !== undefined) {
144 properties = properties.filter((property) => property.group !== match.group);
147 // Never remove the last photo property
148 if (match.field === 'photo') {
149 const photoCount = properties.filter((property) => property.field === 'photo').length;
150 if (photoCount === 0) {
151 properties.push({ field: 'photo', value: '', uid: generateUID(UID_PREFIX) });
155 return fromVCardProperties(properties);
158 export const compareVCardPropertyByUid = (a: VCardProperty, b: VCardProperty) => {
159 const aUid = getContactPropertyUid(a.uid);
160 const bUid = getContactPropertyUid(b.uid);
161 return aUid > bUid ? 1 : -1;
164 export const compareVCardPropertyByPref = (a: VCardProperty, b: VCardProperty) => {
165 const aPref = Number(a.params?.pref);
166 const bPref = Number(b.params?.pref);
167 if (!isNaN(aPref) && !isNaN(bPref) && aPref !== bPref) {
168 return aPref > bPref ? 1 : -1;
170 return compareVCardPropertyByUid(a, b);
173 export const getSortedProperties = (vCardContact: VCardContact, field: string) => {
174 return getVCardProperties(vCardContact)
175 .filter((property) => property.field === field)
176 .sort(compareVCardPropertyByPref);