Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / lib / mail / recipient.ts
blobb54233e88552801d40b8f8a6782125e12acfff8b
1 import isTruthy from '@proton/utils/isTruthy';
3 import { validateEmailAddress } from '../helpers/email';
4 import type { Recipient } from '../interfaces';
5 import type { ContactEmail } from '../interfaces/contacts';
6 import { unescapeFromString } from '../sanitize/escape';
8 export const REGEX_RECIPIENT = /(.*?)\s*<([^>]*)>/;
10 const SEPARATOR_REGEX = /[,;]/;
12 /**
13  * Trim and remove surrounding chevrons
14  */
15 export const clearValue = (value: string) => {
16     const trimmed = value.trim();
17     if (trimmed.startsWith('<') && trimmed.endsWith('>')) {
18         return trimmed.slice(1, -1);
19     }
20     return trimmed;
23 /**
24  * Split input content by comma or semicolon
25  */
26 export const splitBySeparator = (input: string) => {
27     return input
28         .split(SEPARATOR_REGEX)
29         .map((value) => clearValue(value))
30         .filter(isTruthy);
32 /**
33  * Find in a string potential recipients separated by a space
34  */
35 export const findRecipientsWithSpaceSeparator = (input: string) => {
36     // Do nothing if the input does not contain a space
37     // Otherwise, executing the logic might break the behaviour we have
38     // For example it will detect as valid input "address@pm.m", but the user might want to type "address@pm.me"
39     if (!input.includes(' ')) {
40         return [];
41     }
43     const emails = input.split(' ');
45     const clearedEmails = emails.map((email) => email.trim()).filter((email) => email.length > 0);
47     const isValid = clearedEmails.every((email) => validateEmailAddress(email));
49     return isValid ? clearedEmails : [];
52 export const inputToRecipient = (input: string) => {
53     // Remove potential unwanted HTML entities such as '&shy;' from the string
54     const cleanInput = unescapeFromString(input);
55     const trimmedInput = cleanInput.trim();
56     const match = REGEX_RECIPIENT.exec(trimmedInput);
58     if (match !== null && (match[1] || match[2])) {
59         const trimmedMatches = match.map((match) => match.trim());
60         return {
61             Name: trimmedMatches[1],
62             Address: trimmedMatches[2] || trimmedMatches[1],
63         };
64     }
65     return {
66         Name: trimmedInput,
67         Address: trimmedInput,
68     };
71 /**
72  *  Used in AddressesAutocomplete and AddressesAutocomplete v2 to detect recipients strings inside the input
73  */
74 export const handleRecipientInputChange = (
75     newValue: string,
76     hasEmailPasting: boolean,
77     onAddRecipients: (recipients: Recipient[]) => void,
78     setInput: (val: string) => void
79 ) => {
80     if (newValue === ';' || newValue === ',') {
81         return;
82     }
84     if (!hasEmailPasting) {
85         setInput(newValue);
86         return;
87     }
89     const values = newValue.split(SEPARATOR_REGEX).map((value) => clearValue(value));
90     if (values.length > 1) {
91         onAddRecipients(values.map(inputToRecipient));
92         setInput('');
93         return;
94     }
96     // Try to find recipients with space separator e.g. "address1@pm.me address2@pm.me"
97     const valuesWithSpaceBar = newValue.split(' ').map((val) => val.trim());
98     if (valuesWithSpaceBar.length > 0) {
99         const recipientsWithSpaceSeparator = findRecipientsWithSpaceSeparator(newValue);
101         if (recipientsWithSpaceSeparator.length > 0) {
102             onAddRecipients(recipientsWithSpaceSeparator.map(inputToRecipient));
103             setInput('');
104             return;
105         }
106     }
108     setInput(newValue);
111 export const contactToRecipient = (contact: ContactEmail, groupPath?: string) => ({
112     Name: contact.Name,
113     Address: contact.Email,
114     ContactID: contact.ContactID,
115     Group: groupPath,
118 export const majorToRecipient = (email: string) => ({
119     Name: email,
120     Address: email,
123 export const recipientToInput = (recipient: Recipient): string => {
124     if (recipient.Address && recipient.Name && recipient.Address !== recipient.Name) {
125         return `${recipient.Name} <${recipient.Address}>`;
126     }
128     if (recipient.Address === recipient.Name) {
129         return recipient.Address || '';
130     }
132     return `${recipient.Name} ${recipient.Address}`;
135 export const contactToInput = (contact: ContactEmail): string => recipientToInput(contactToRecipient(contact));