expensify-common 2.0.188 → 2.0.190
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Logger.js +11 -1
- package/dist/esm/API.d.ts +11 -0
- package/dist/esm/API.js +786 -0
- package/dist/esm/APIDeferred.d.ts +7 -0
- package/dist/esm/APIDeferred.js +216 -0
- package/dist/esm/BrowserDetect.d.ts +19 -0
- package/dist/esm/BrowserDetect.js +105 -0
- package/dist/esm/CLI.js +37 -29
- package/dist/esm/CONST.d.ts +1727 -0
- package/dist/esm/CONST.js +1847 -0
- package/dist/esm/Cookie.d.ts +68 -0
- package/dist/esm/Cookie.js +159 -0
- package/dist/esm/CredentialsWrapper.d.ts +32 -0
- package/dist/esm/CredentialsWrapper.js +46 -0
- package/dist/esm/Device.d.ts +8 -0
- package/dist/esm/Device.js +24 -0
- package/dist/esm/ExpenseRule.d.ts +39 -0
- package/dist/esm/ExpenseRule.js +77 -0
- package/dist/esm/ExpensiMark.d.ts +197 -0
- package/dist/esm/ExpensiMark.js +1374 -0
- package/dist/esm/Func.d.ts +40 -0
- package/dist/esm/Func.js +69 -0
- package/dist/esm/Log.d.ts +3 -0
- package/dist/esm/Log.js +35 -0
- package/dist/esm/Logger.d.ts +82 -0
- package/dist/esm/Logger.js +148 -0
- package/dist/esm/Network.d.ts +6 -0
- package/dist/esm/Network.js +168 -0
- package/dist/esm/Num.d.ts +95 -0
- package/dist/esm/Num.js +190 -0
- package/dist/esm/PageEvent.d.ts +25 -0
- package/dist/esm/PageEvent.js +22 -0
- package/dist/esm/PubSub.d.ts +2 -0
- package/dist/esm/PubSub.js +111 -0
- package/dist/esm/ReportHistoryStore.d.ts +8 -0
- package/dist/esm/ReportHistoryStore.js +199 -0
- package/dist/esm/SafeString.js +2 -2
- package/dist/esm/Templates.d.ts +58 -0
- package/dist/esm/Templates.js +196 -0
- package/dist/esm/Url.d.ts +10 -0
- package/dist/esm/Url.js +15 -0
- package/dist/esm/components/CopyText.d.ts +45 -0
- package/dist/esm/components/CopyText.js +56 -0
- package/dist/esm/components/StepProgressBar.d.ts +26 -0
- package/dist/esm/components/StepProgressBar.js +40 -0
- package/dist/esm/components/form/element/combobox.d.ts +231 -0
- package/dist/esm/components/form/element/combobox.js +812 -0
- package/dist/esm/components/form/element/dropdown.d.ts +35 -0
- package/dist/esm/components/form/element/dropdown.js +61 -0
- package/dist/esm/components/form/element/dropdownItem.d.ts +55 -0
- package/dist/esm/components/form/element/dropdownItem.js +113 -0
- package/dist/esm/components/form/element/onOffSwitch.d.ts +94 -0
- package/dist/esm/components/form/element/onOffSwitch.js +167 -0
- package/dist/esm/components/form/element/switch.d.ts +58 -0
- package/dist/esm/components/form/element/switch.js +98 -0
- package/dist/esm/fastMerge.d.ts +9 -0
- package/dist/esm/fastMerge.js +58 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/jquery.expensifyIframify.d.ts +9 -0
- package/dist/esm/jquery.expensifyIframify.js +397 -0
- package/dist/esm/md5.d.ts +2 -0
- package/dist/esm/md5.js +170 -0
- package/dist/esm/mixins/PubSub.d.ts +20 -0
- package/dist/esm/mixins/PubSub.js +47 -0
- package/dist/esm/mixins/extraClasses.d.ts +8 -0
- package/dist/esm/mixins/extraClasses.js +37 -0
- package/dist/esm/mixins/validationClasses.d.ts +12 -0
- package/dist/esm/mixins/validationClasses.js +30 -0
- package/dist/esm/str.d.ts +604 -0
- package/dist/esm/str.js +1036 -0
- package/dist/esm/tlds.d.ts +2 -0
- package/dist/esm/tlds.js +2 -0
- package/dist/esm/utils.d.ts +30 -0
- package/dist/esm/utils.js +65 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -1
- package/package.json +221 -4
package/dist/esm/str.js
ADDED
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
'worklet';
|
|
2
|
+
/* eslint-disable no-control-regex */
|
|
3
|
+
import $ from 'jquery';
|
|
4
|
+
import { parsePhoneNumber } from 'awesome-phonenumber';
|
|
5
|
+
import Punycode from 'punycode/';
|
|
6
|
+
import * as HtmlEntities from 'html-entities';
|
|
7
|
+
import * as Constants from './CONST';
|
|
8
|
+
import * as UrlPatterns from './Url';
|
|
9
|
+
import * as Utils from './utils';
|
|
10
|
+
import Log from './Log';
|
|
11
|
+
const REMOVE_SMS_DOMAIN_PATTERN = /@expensify\.sms/gi;
|
|
12
|
+
function resultFn(parameter, ...args) {
|
|
13
|
+
if (typeof parameter === 'function') {
|
|
14
|
+
return parameter(...args);
|
|
15
|
+
}
|
|
16
|
+
return parameter;
|
|
17
|
+
}
|
|
18
|
+
const Str = {
|
|
19
|
+
/**
|
|
20
|
+
* Return true if the string is ending with the provided suffix
|
|
21
|
+
*
|
|
22
|
+
* @param str String ot search in
|
|
23
|
+
* @param suffix What to look for
|
|
24
|
+
*/
|
|
25
|
+
endsWith(str, suffix) {
|
|
26
|
+
if (!str || !suffix) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return str.substr(-suffix.length) === suffix;
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* Converts a USD string into th number of cents it represents.
|
|
33
|
+
*
|
|
34
|
+
* @param amountStr A string representing a USD value.
|
|
35
|
+
* @param allowFraction Flag indicating if fractions of cents should be
|
|
36
|
+
* allowed in the output.
|
|
37
|
+
*
|
|
38
|
+
* @returns The cent value of the @p amountStr.
|
|
39
|
+
*/
|
|
40
|
+
fromUSDToNumber(amountStr, allowFraction) {
|
|
41
|
+
let amount = String(amountStr).replaceAll(/[^\d.\-()]+/g, '');
|
|
42
|
+
if (amount.match(/\(.*\)/)) {
|
|
43
|
+
const modifiedAmount = amount.replaceAll(/[()]/g, '');
|
|
44
|
+
amount = `-${modifiedAmount}`;
|
|
45
|
+
}
|
|
46
|
+
amount = Number(amount) * 100;
|
|
47
|
+
amount = Math.round(amount * 1e3) / 1e3;
|
|
48
|
+
return allowFraction ? amount : Math.round(amount);
|
|
49
|
+
},
|
|
50
|
+
/**
|
|
51
|
+
* Truncates the middle section of a string based on the max allowed length
|
|
52
|
+
*/
|
|
53
|
+
truncateInMiddle(fullStr, maxLength) {
|
|
54
|
+
if (fullStr.length <= maxLength) {
|
|
55
|
+
return fullStr;
|
|
56
|
+
}
|
|
57
|
+
const separator = '...';
|
|
58
|
+
const halfLengthToShow = (maxLength - separator.length) / 2;
|
|
59
|
+
const beginning = fullStr.substr(0, Math.ceil(halfLengthToShow));
|
|
60
|
+
const end = fullStr.substr(fullStr.length - Math.floor(halfLengthToShow));
|
|
61
|
+
return beginning + separator + end;
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Convert new line to <br />
|
|
65
|
+
*/
|
|
66
|
+
nl2br(str) {
|
|
67
|
+
return str.replaceAll('\n', '<br />');
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Decodes the given HTML encoded string.
|
|
71
|
+
*
|
|
72
|
+
* @param s The string to decode.
|
|
73
|
+
* @returns The decoded string.
|
|
74
|
+
*/
|
|
75
|
+
htmlDecode(s) {
|
|
76
|
+
return HtmlEntities.decode(s);
|
|
77
|
+
},
|
|
78
|
+
/**
|
|
79
|
+
* HTML encodes the given string.
|
|
80
|
+
*
|
|
81
|
+
* @param s The string to encode.
|
|
82
|
+
* @return string @p s HTML encoded.
|
|
83
|
+
*/
|
|
84
|
+
htmlEncode(s) {
|
|
85
|
+
return HtmlEntities.encode(s);
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Decodes the given HTML encoded string.
|
|
89
|
+
*
|
|
90
|
+
* @param s The string to decode.
|
|
91
|
+
* @returns The decoded string.
|
|
92
|
+
*/
|
|
93
|
+
htmlDecodeWithJQueryOrNative(s) {
|
|
94
|
+
// Use jQuery if it exists or else use html-entities
|
|
95
|
+
if (typeof $ !== 'undefined') {
|
|
96
|
+
return $('<textarea/>').html(s).text();
|
|
97
|
+
}
|
|
98
|
+
return HtmlEntities.decode(s);
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* HTML encodes the given string.
|
|
102
|
+
*
|
|
103
|
+
* @param s The string to encode.
|
|
104
|
+
* @return string @p s HTML encoded.
|
|
105
|
+
*/
|
|
106
|
+
htmlEncodeWithJQueryOrNative(s) {
|
|
107
|
+
// Use jQuery if it exists or else use html-entities
|
|
108
|
+
if (typeof $ !== 'undefined') {
|
|
109
|
+
return $('<textarea/>').text(s).html();
|
|
110
|
+
}
|
|
111
|
+
return HtmlEntities.encode(s);
|
|
112
|
+
},
|
|
113
|
+
/**
|
|
114
|
+
* Escape text while preventing any sort of double escape, so 'X & Y' -> 'X & Y' and 'X & Y' -> 'X & Y'
|
|
115
|
+
*
|
|
116
|
+
* @param s The string to escape
|
|
117
|
+
* @returns The escaped string
|
|
118
|
+
*/
|
|
119
|
+
safeEscape(s) {
|
|
120
|
+
return Utils.escapeText(Utils.unescapeText(s));
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* HTML encoding insensitive equals.
|
|
124
|
+
*
|
|
125
|
+
* @param first string to compare
|
|
126
|
+
* @param second string to compare
|
|
127
|
+
* @returns True when first === second, ignoring HTML encoding
|
|
128
|
+
*/
|
|
129
|
+
htmlEncodingInsensitiveEquals(first, second) {
|
|
130
|
+
return first === second || this.htmlDecode(first) === second || this.htmlEncode(first) === second;
|
|
131
|
+
},
|
|
132
|
+
/**
|
|
133
|
+
* Creates an ID that can be used as an HTML attribute from @p str.
|
|
134
|
+
*
|
|
135
|
+
* @param str A string to create an ID from.
|
|
136
|
+
* @returns The ID string made from @p str.
|
|
137
|
+
*/
|
|
138
|
+
makeID(str) {
|
|
139
|
+
const modifiedString = String(str)
|
|
140
|
+
.replaceAll(/[^A-Za-z0-9]/g, '_')
|
|
141
|
+
.toUpperCase();
|
|
142
|
+
return `id_${modifiedString}`;
|
|
143
|
+
},
|
|
144
|
+
/**
|
|
145
|
+
* Extracts an ID made with Str.makeID from a larger string.
|
|
146
|
+
*
|
|
147
|
+
* @param str A string containing an id made with Str.makeID
|
|
148
|
+
* @returns The ID string.
|
|
149
|
+
*/
|
|
150
|
+
extractID(str) {
|
|
151
|
+
const matches = str.match(/id[A-Z0-9_]+/);
|
|
152
|
+
return matches && matches.length > 0 ? matches[0] : null;
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* Modifies the string so the first letter of each word is capitalized and the
|
|
156
|
+
* rest lowercased.
|
|
157
|
+
*
|
|
158
|
+
* @param val The string to modify
|
|
159
|
+
*/
|
|
160
|
+
recapitalize(val) {
|
|
161
|
+
// First replace every letter with its lowercase equivalent
|
|
162
|
+
// Cast to string.
|
|
163
|
+
let str = String(val);
|
|
164
|
+
if (str.length <= 0) {
|
|
165
|
+
return str;
|
|
166
|
+
}
|
|
167
|
+
str = str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase();
|
|
168
|
+
function recap_callback(t, a, b) {
|
|
169
|
+
return a + b.toUpperCase();
|
|
170
|
+
}
|
|
171
|
+
return str.replaceAll(
|
|
172
|
+
// **NOTE: Match to _libfop.php
|
|
173
|
+
/([^A-Za-z'.0-9])([a-z])/g, recap_callback);
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* Replace all the non alphanumerical character by _
|
|
177
|
+
*/
|
|
178
|
+
sanitizeToAlphaNumeric(input) {
|
|
179
|
+
return String(input).replaceAll(/[^\d\w]/g, '_');
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Strip out all the non numerical characters
|
|
183
|
+
*/
|
|
184
|
+
stripNonNumeric(input) {
|
|
185
|
+
return String(input).replaceAll(/[^\d]/g, '');
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* Strips all non ascii characters from a string
|
|
189
|
+
* @returns The ascii version of the string.
|
|
190
|
+
*/
|
|
191
|
+
stripNonASCIICharacters(input) {
|
|
192
|
+
return String(input).replaceAll(/[\u0000-\u0019\u0080-\uffff]/g, '');
|
|
193
|
+
},
|
|
194
|
+
/**
|
|
195
|
+
* Shortens the @p text to @p length and appends an ellipses to it.
|
|
196
|
+
*
|
|
197
|
+
* The ellipses will only be appended if @p text is longer than the @p length
|
|
198
|
+
* given.
|
|
199
|
+
*
|
|
200
|
+
* @param val The string to reduce in size.
|
|
201
|
+
* @param length The maximal length desired.
|
|
202
|
+
* @returns The shortened @p text.
|
|
203
|
+
*/
|
|
204
|
+
shortenText(val, length) {
|
|
205
|
+
// Remove extra spaces because they don't show up in html anyway.
|
|
206
|
+
const text = String(val).replaceAll(/\s+/g, ' ');
|
|
207
|
+
const truncatedText = text.substr(0, length - 3);
|
|
208
|
+
return text.length > length ? `${truncatedText}...` : text;
|
|
209
|
+
},
|
|
210
|
+
/**
|
|
211
|
+
* Returns the byte size of a character
|
|
212
|
+
* @param inputChar You can input more than one character, but it will only return the size of the first
|
|
213
|
+
* one.
|
|
214
|
+
* @returns Byte size of the character
|
|
215
|
+
*/
|
|
216
|
+
getRawByteSize(inputChar) {
|
|
217
|
+
const onlyChar = String(inputChar);
|
|
218
|
+
const c = onlyChar.charCodeAt(0);
|
|
219
|
+
// If we are grabbing the byte size, we need to temporarily diable no-bitwise for linting
|
|
220
|
+
/* eslint-disable no-bitwise */
|
|
221
|
+
if (c < 1 << 7) {
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
224
|
+
if (c < 1 << 11) {
|
|
225
|
+
return 2;
|
|
226
|
+
}
|
|
227
|
+
if (c < 1 << 16) {
|
|
228
|
+
return 3;
|
|
229
|
+
}
|
|
230
|
+
if (c < 1 << 21) {
|
|
231
|
+
return 4;
|
|
232
|
+
}
|
|
233
|
+
if (c < 1 << 26) {
|
|
234
|
+
return 5;
|
|
235
|
+
}
|
|
236
|
+
if (c < 1 << 31) {
|
|
237
|
+
return 6;
|
|
238
|
+
}
|
|
239
|
+
/* eslint-enable no-bitwise */
|
|
240
|
+
return Number.NaN;
|
|
241
|
+
},
|
|
242
|
+
/**
|
|
243
|
+
* Gets the length of a string in bytes, including non-ASCII characters
|
|
244
|
+
* @returns The number of bytes used by string
|
|
245
|
+
*/
|
|
246
|
+
getByteLength(input) {
|
|
247
|
+
// Force string type
|
|
248
|
+
const stringInput = String(input);
|
|
249
|
+
const byteLength = Array.from(stringInput).reduce((acc, char) => acc + this.getRawByteSize(char), 0);
|
|
250
|
+
return byteLength;
|
|
251
|
+
},
|
|
252
|
+
/**
|
|
253
|
+
* Shortens the input by max byte size instead of by character length
|
|
254
|
+
* @param maxSize The max size in bytes, e.g. 256
|
|
255
|
+
* @returns Returns a shorted input if the input size exceeds the max
|
|
256
|
+
*/
|
|
257
|
+
shortenByByte(input, maxSize) {
|
|
258
|
+
const stringInput = String(input);
|
|
259
|
+
let totalByteLength = 0;
|
|
260
|
+
for (let i = 0; i < stringInput.length; i++) {
|
|
261
|
+
const charByteSize = this.getRawByteSize(stringInput[i]);
|
|
262
|
+
if (charByteSize + totalByteLength > maxSize) {
|
|
263
|
+
// If the next character exceeds the limit, stop and return the truncated string.
|
|
264
|
+
return `${stringInput.substr(0, i - 3)}...`;
|
|
265
|
+
}
|
|
266
|
+
totalByteLength += charByteSize;
|
|
267
|
+
}
|
|
268
|
+
return stringInput;
|
|
269
|
+
},
|
|
270
|
+
/**
|
|
271
|
+
* Returns true if the haystack begins with the needle
|
|
272
|
+
*
|
|
273
|
+
* @param haystack The full string to be searched
|
|
274
|
+
* @param needle The case-sensitive string to search for
|
|
275
|
+
* @returns True if the haystack starts with the needle.
|
|
276
|
+
*/
|
|
277
|
+
startsWith(haystack, needle) {
|
|
278
|
+
return this.isString(haystack) && this.isString(needle) && haystack.substring(0, needle.length) === needle;
|
|
279
|
+
},
|
|
280
|
+
/**
|
|
281
|
+
* Gets the textual value of the given string.
|
|
282
|
+
*
|
|
283
|
+
* @param str The string to fetch the text value from.
|
|
284
|
+
* @returns The text from within the HTML string.
|
|
285
|
+
*/
|
|
286
|
+
stripHTML(str) {
|
|
287
|
+
if (!this.isString(str)) {
|
|
288
|
+
return '';
|
|
289
|
+
}
|
|
290
|
+
return str.replaceAll(/<[^>]*>?/gm, '');
|
|
291
|
+
},
|
|
292
|
+
/**
|
|
293
|
+
* Modifies the string so the first letter of the string is capitalized
|
|
294
|
+
*
|
|
295
|
+
* @param str The string to modify.
|
|
296
|
+
* @returns The recapitalized string.
|
|
297
|
+
*/
|
|
298
|
+
UCFirst(str) {
|
|
299
|
+
return str.substr(0, 1).toUpperCase() + str.substr(1);
|
|
300
|
+
},
|
|
301
|
+
/**
|
|
302
|
+
* Returns a string containing all the characters str from the beginning
|
|
303
|
+
* of str to the first occurrence of substr.
|
|
304
|
+
* Example: Str.cutAfter( 'hello$%world', '$%' ) // returns 'hello'
|
|
305
|
+
*
|
|
306
|
+
* @param str The string to modify.
|
|
307
|
+
* @param substr The substring to search for.
|
|
308
|
+
* @returns The cut/trimmed string.
|
|
309
|
+
*/
|
|
310
|
+
cutAfter(str, substr) {
|
|
311
|
+
const index = str.indexOf(substr);
|
|
312
|
+
if (index !== -1) {
|
|
313
|
+
return str.substring(0, index);
|
|
314
|
+
}
|
|
315
|
+
return str;
|
|
316
|
+
},
|
|
317
|
+
/**
|
|
318
|
+
* Returns a string containing all the characters str from after the first
|
|
319
|
+
* occurrence of substr to the end of the string.
|
|
320
|
+
* Example: Str.cutBefore( 'hello$%world', '$%' ) // returns 'world'
|
|
321
|
+
*
|
|
322
|
+
* @param str The string to modify.
|
|
323
|
+
* @param substr The substring to search for.
|
|
324
|
+
* @returns The cut/trimmed string.
|
|
325
|
+
*/
|
|
326
|
+
cutBefore(str, substr) {
|
|
327
|
+
const index = str.indexOf(substr);
|
|
328
|
+
if (index !== -1) {
|
|
329
|
+
return str.substring(index + substr.length);
|
|
330
|
+
}
|
|
331
|
+
return str;
|
|
332
|
+
},
|
|
333
|
+
/**
|
|
334
|
+
* Checks that the string is a domain name (e.g. example.com)
|
|
335
|
+
*
|
|
336
|
+
* @param str The string to check for domainnameness.
|
|
337
|
+
*
|
|
338
|
+
* @returns True if the string is a domain name
|
|
339
|
+
*/
|
|
340
|
+
isValidDomainName(str) {
|
|
341
|
+
return !!String(str).match(Constants.CONST.REG_EXP.DOMAIN);
|
|
342
|
+
},
|
|
343
|
+
/**
|
|
344
|
+
* Checks that the string is a valid url
|
|
345
|
+
*
|
|
346
|
+
* @returns True if the string is a valid hyperlink
|
|
347
|
+
*/
|
|
348
|
+
isValidURL(str) {
|
|
349
|
+
return !!String(str).match(Constants.CONST.REG_EXP.HYPERLINK);
|
|
350
|
+
},
|
|
351
|
+
/**
|
|
352
|
+
* Checks that the string is an email address.
|
|
353
|
+
* NOTE: TLDs are not just 2-4 characters. Keep this in sync with _inputrules.php
|
|
354
|
+
*
|
|
355
|
+
* @param str The string to check for email validity.
|
|
356
|
+
*
|
|
357
|
+
* @returns True if the string is an email
|
|
358
|
+
*/
|
|
359
|
+
isValidEmail(str) {
|
|
360
|
+
if (!str || typeof str !== 'string') {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const unicodeVersion = Punycode.toUnicode(str);
|
|
364
|
+
if (String(unicodeVersion).match(Constants.CONST.REG_EXP.EMOJI_RULE)) {
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
return !!String(str).match(Constants.CONST.REG_EXP.EMAIL);
|
|
368
|
+
},
|
|
369
|
+
/**
|
|
370
|
+
* Checks if the string is an valid email address formed during comment markdown formation.
|
|
371
|
+
*
|
|
372
|
+
* @param str The string to check for email validity.
|
|
373
|
+
*
|
|
374
|
+
* @returns True if the string is an valid email created by comment markdown.
|
|
375
|
+
*/
|
|
376
|
+
isValidEmailMarkdown(str) {
|
|
377
|
+
return !!String(str).match(`^${Constants.CONST.REG_EXP.MARKDOWN_EMAIL}$`);
|
|
378
|
+
},
|
|
379
|
+
/**
|
|
380
|
+
* Remove trailing comma from a string.
|
|
381
|
+
*
|
|
382
|
+
* @param str The string with any trailing comma to be removed.
|
|
383
|
+
*
|
|
384
|
+
* @returns string with the trailing comma removed
|
|
385
|
+
*/
|
|
386
|
+
removeTrailingComma(str) {
|
|
387
|
+
return str.trim().replaceAll(/(,$)/g, '');
|
|
388
|
+
},
|
|
389
|
+
/**
|
|
390
|
+
* Checks that the string is a list of coma separated email addresss.
|
|
391
|
+
*
|
|
392
|
+
* @param str The string to check for emails validity.
|
|
393
|
+
*
|
|
394
|
+
* @returns True if all emails are valid or if input is empty
|
|
395
|
+
*/
|
|
396
|
+
areValidEmails(str) {
|
|
397
|
+
const string = this.removeTrailingComma(str);
|
|
398
|
+
if (string === '') {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
const emails = string.split(',');
|
|
402
|
+
const result = emails.every((email) => this.isValidEmail(email.trim()));
|
|
403
|
+
return result;
|
|
404
|
+
},
|
|
405
|
+
/**
|
|
406
|
+
* Extract the email addresses from a string
|
|
407
|
+
*/
|
|
408
|
+
extractEmail(str) {
|
|
409
|
+
return String(str).match(Constants.CONST.REG_EXP.EMAIL_SEARCH);
|
|
410
|
+
},
|
|
411
|
+
/**
|
|
412
|
+
* Extracts the domain name from the given email address
|
|
413
|
+
* (e.g. "domain.com" for "joe@domain.com").
|
|
414
|
+
*
|
|
415
|
+
* @param email The email address.
|
|
416
|
+
*
|
|
417
|
+
* @returns The domain name in the email address.
|
|
418
|
+
*/
|
|
419
|
+
extractEmailDomain(email) {
|
|
420
|
+
return this.cutBefore(email, '@');
|
|
421
|
+
},
|
|
422
|
+
/**
|
|
423
|
+
* Tries to extract the company name from the given email address
|
|
424
|
+
* (e.g. "yelp" for "joe@yelp.co.uk").
|
|
425
|
+
*
|
|
426
|
+
* @param email The email address.
|
|
427
|
+
*
|
|
428
|
+
* @returns The company name in the email address or null.
|
|
429
|
+
*/
|
|
430
|
+
extractCompanyNameFromEmailDomain(email) {
|
|
431
|
+
const domain = this.extractEmailDomain(email);
|
|
432
|
+
if (!domain) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
const domainParts = domain.split('.');
|
|
436
|
+
if (!domainParts.length) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
return domainParts[0];
|
|
440
|
+
},
|
|
441
|
+
/**
|
|
442
|
+
* Extracts the local part from the given email address
|
|
443
|
+
* (e.g. "joe" for "joe@domain.com").
|
|
444
|
+
*
|
|
445
|
+
* @param email The email address.
|
|
446
|
+
*
|
|
447
|
+
* @returns The local part in the email address.
|
|
448
|
+
*/
|
|
449
|
+
extractEmailLocalPart(email) {
|
|
450
|
+
return this.cutAfter(email, '@');
|
|
451
|
+
},
|
|
452
|
+
/**
|
|
453
|
+
* Sanitize phone number to return only numbers. Return null if non valid phone number.
|
|
454
|
+
*/
|
|
455
|
+
sanitizePhoneNumber(str) {
|
|
456
|
+
const string = str.replaceAll(/(?!^\+)\D/g, '');
|
|
457
|
+
return string.length <= 15 && string.length >= 10 ? string : null;
|
|
458
|
+
},
|
|
459
|
+
/**
|
|
460
|
+
* Sanitize email. Return null if non valid email.
|
|
461
|
+
*/
|
|
462
|
+
sanitizeEmail(str) {
|
|
463
|
+
const string = str.toLowerCase().trim();
|
|
464
|
+
return Constants.CONST.REG_EXP.EMAIL.test(string) ? string : null;
|
|
465
|
+
},
|
|
466
|
+
/**
|
|
467
|
+
* Escapes all special RegExp characters from a string
|
|
468
|
+
*
|
|
469
|
+
* @param str The subject
|
|
470
|
+
*
|
|
471
|
+
* @returns The escaped string
|
|
472
|
+
*/
|
|
473
|
+
escapeForRegExp(str) {
|
|
474
|
+
return str.replaceAll(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
|
|
475
|
+
},
|
|
476
|
+
/**
|
|
477
|
+
* Escapes all special RegExp characters from a string except for the period
|
|
478
|
+
*
|
|
479
|
+
* @param str The subject
|
|
480
|
+
* @returns The escaped string
|
|
481
|
+
*/
|
|
482
|
+
escapeForExpenseRule(str) {
|
|
483
|
+
return str.replaceAll(/[-[\]/{}()*+?\\^$|]/g, '\\$&');
|
|
484
|
+
},
|
|
485
|
+
/**
|
|
486
|
+
* Adds a backslash in front of each of colon
|
|
487
|
+
* if they don't already have a backslash in front of them
|
|
488
|
+
*
|
|
489
|
+
* @param str The subject
|
|
490
|
+
* @returns The escaped string
|
|
491
|
+
*/
|
|
492
|
+
addBackslashBeforeColonsForTagNamesComingFromQBD(str) {
|
|
493
|
+
return str.replaceAll(/([^\\]):/g, '$1\\:');
|
|
494
|
+
},
|
|
495
|
+
/**
|
|
496
|
+
* Removes backslashes from string
|
|
497
|
+
* eg: myString\[\]\* -> myString[]*
|
|
498
|
+
*/
|
|
499
|
+
stripBackslashes(str) {
|
|
500
|
+
return str.replaceAll('\\', '');
|
|
501
|
+
},
|
|
502
|
+
/**
|
|
503
|
+
* Checks if a string's length is in the specified range
|
|
504
|
+
*
|
|
505
|
+
* @returns true if the length is in the range, false otherwise
|
|
506
|
+
*/
|
|
507
|
+
isOfLength(str, minimumLength, maximumLength) {
|
|
508
|
+
if (!this.isString(str)) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
if (str.length < minimumLength) {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
if (!this.isUndefined(maximumLength) && str.length > maximumLength) {
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
return true;
|
|
518
|
+
},
|
|
519
|
+
/**
|
|
520
|
+
* Count the number of occurences of needle in haystack.
|
|
521
|
+
* This is faster than counting the results of haystack.match( /needle/g )
|
|
522
|
+
* via http://stackoverflow.com/questions/4009756/how-to-count-string-occurrence-in-string
|
|
523
|
+
*
|
|
524
|
+
* @param haystack The string to look inside of
|
|
525
|
+
* @param needle What we're looking for
|
|
526
|
+
* @param allowOverlapping Defaults to false
|
|
527
|
+
*
|
|
528
|
+
* @returns The number of times needle is in haystack.
|
|
529
|
+
*/
|
|
530
|
+
occurences(haystack, needle, allowOverlapping) {
|
|
531
|
+
let count = 0;
|
|
532
|
+
let pos = 0;
|
|
533
|
+
// Force strings for input
|
|
534
|
+
const haystackStr = String(haystack);
|
|
535
|
+
const needleStr = String(needle);
|
|
536
|
+
if (needleStr.length <= 0) {
|
|
537
|
+
return haystackStr.length + 1;
|
|
538
|
+
}
|
|
539
|
+
const step = allowOverlapping ? 1 : needleStr.length;
|
|
540
|
+
while (pos >= 0) {
|
|
541
|
+
pos = haystackStr.indexOf(needleStr, pos);
|
|
542
|
+
if (pos >= 0) {
|
|
543
|
+
count += 1;
|
|
544
|
+
pos += step;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
return count;
|
|
548
|
+
},
|
|
549
|
+
/**
|
|
550
|
+
* Uppercases the first letter of each word
|
|
551
|
+
* via https://github.com/kvz/phpjs/blob/master/functions/strings/ucwords.js
|
|
552
|
+
*
|
|
553
|
+
* @param str to uppercase words
|
|
554
|
+
* @returns Uppercase worded string
|
|
555
|
+
*/
|
|
556
|
+
ucwords(str) {
|
|
557
|
+
const capitalize = ($1) => $1.toUpperCase();
|
|
558
|
+
return String(str).replaceAll(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, capitalize);
|
|
559
|
+
},
|
|
560
|
+
/**
|
|
561
|
+
* Returns true if the haystack contains the needle
|
|
562
|
+
*
|
|
563
|
+
* @param haystack The full string to be searched
|
|
564
|
+
* @param needle The case-sensitive string to search for
|
|
565
|
+
*
|
|
566
|
+
* @returns Returns true if the haystack contains the needle
|
|
567
|
+
*/
|
|
568
|
+
contains(haystack, needle) {
|
|
569
|
+
return haystack.indexOf(needle) !== -1;
|
|
570
|
+
},
|
|
571
|
+
/**
|
|
572
|
+
* Returns true if the haystack contains the needle, ignoring case
|
|
573
|
+
*
|
|
574
|
+
* @param haystack The full string to be searched
|
|
575
|
+
* @param needle The case-insensitive string to search for
|
|
576
|
+
*
|
|
577
|
+
* @returns Returns true if the haystack contains the needle, ignoring case
|
|
578
|
+
*/
|
|
579
|
+
caseInsensitiveContains(haystack, needle) {
|
|
580
|
+
return this.contains(haystack.toLowerCase(), needle.toLowerCase());
|
|
581
|
+
},
|
|
582
|
+
/**
|
|
583
|
+
* Case insensitive compare function
|
|
584
|
+
*
|
|
585
|
+
* @param strA string to compare
|
|
586
|
+
* @param strB string to compare
|
|
587
|
+
*
|
|
588
|
+
* @returns -1 if first string < second string
|
|
589
|
+
* 1 if first string > second string
|
|
590
|
+
* 0 if first string = second string
|
|
591
|
+
*/
|
|
592
|
+
caseInsensitiveCompare(strA, strB) {
|
|
593
|
+
const lowerCaseStrA = strA.toLocaleLowerCase();
|
|
594
|
+
const lowerCaseStrB = strB.toLocaleLowerCase();
|
|
595
|
+
return this.compare(lowerCaseStrA, lowerCaseStrB);
|
|
596
|
+
},
|
|
597
|
+
/**
|
|
598
|
+
* Case insensitive equals
|
|
599
|
+
*
|
|
600
|
+
* @param strA string to compare
|
|
601
|
+
* @param strB string to compare
|
|
602
|
+
* @returns true when first == second except for case
|
|
603
|
+
*/
|
|
604
|
+
caseInsensitiveEquals(strA, strB) {
|
|
605
|
+
return this.caseInsensitiveCompare(strA, strB) === 0;
|
|
606
|
+
},
|
|
607
|
+
/**
|
|
608
|
+
* Compare function
|
|
609
|
+
*
|
|
610
|
+
* @param strA string to compare
|
|
611
|
+
* @param strB string to compare
|
|
612
|
+
*
|
|
613
|
+
* @returns -1 if first string < second string
|
|
614
|
+
* 1 if first string > second string
|
|
615
|
+
* 0 if first string = second string
|
|
616
|
+
*/
|
|
617
|
+
compare(strA, strB) {
|
|
618
|
+
if (strA < strB) {
|
|
619
|
+
return -1;
|
|
620
|
+
}
|
|
621
|
+
if (strA > strB) {
|
|
622
|
+
return 1;
|
|
623
|
+
}
|
|
624
|
+
return 0;
|
|
625
|
+
},
|
|
626
|
+
/**
|
|
627
|
+
* Check if a file extension is supported by SmartReports
|
|
628
|
+
*/
|
|
629
|
+
isFileExtensionSmartReportsValid(filename) {
|
|
630
|
+
var _a;
|
|
631
|
+
// Allowed extensions. Make sure to keep them in sync with those defined
|
|
632
|
+
// in SmartReport_Utils::templateFileUploadCheck()
|
|
633
|
+
const allowedExtensions = ['xls', 'xlsx', 'xlsm', 'xltm'];
|
|
634
|
+
const extension = (_a = filename.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
|
|
635
|
+
return !!extension && allowedExtensions.indexOf(extension) > -1;
|
|
636
|
+
},
|
|
637
|
+
/**
|
|
638
|
+
* Mask Permanent Account Number (PAN) the same way Auth does
|
|
639
|
+
* @param num account number
|
|
640
|
+
* @returns masked account number
|
|
641
|
+
*/
|
|
642
|
+
maskPAN(num) {
|
|
643
|
+
// cast to string
|
|
644
|
+
const accountNumber = String(num);
|
|
645
|
+
const len = accountNumber.length;
|
|
646
|
+
// Hide these numbers completely
|
|
647
|
+
// We should not be getting account numbers this small or large
|
|
648
|
+
if (len < 6 || len > 20) {
|
|
649
|
+
return this.maskFirstNCharacters(accountNumber, len, 'X');
|
|
650
|
+
}
|
|
651
|
+
// Can show last 4
|
|
652
|
+
if (len < 14) {
|
|
653
|
+
return this.maskFirstNCharacters(accountNumber, len - 4, 'X');
|
|
654
|
+
}
|
|
655
|
+
// Can show first 6 and last 4
|
|
656
|
+
const first = accountNumber.substr(0, 6);
|
|
657
|
+
const last = accountNumber.substr(7);
|
|
658
|
+
const masked = this.maskFirstNCharacters(last, len - 11, 'X');
|
|
659
|
+
return `${first}${masked}`;
|
|
660
|
+
},
|
|
661
|
+
/**
|
|
662
|
+
* Checks if something is a string
|
|
663
|
+
* Stolen from underscore
|
|
664
|
+
*/
|
|
665
|
+
isString(obj) {
|
|
666
|
+
return this.isTypeOf(obj, 'String');
|
|
667
|
+
},
|
|
668
|
+
/**
|
|
669
|
+
* Checks if something is a number
|
|
670
|
+
* Stolen from underscore
|
|
671
|
+
* @param obj
|
|
672
|
+
*/
|
|
673
|
+
isNumber(obj) {
|
|
674
|
+
return this.isTypeOf(obj, 'Number');
|
|
675
|
+
},
|
|
676
|
+
/**
|
|
677
|
+
* Checks if something is a certain type
|
|
678
|
+
* Stolen from underscore
|
|
679
|
+
*/
|
|
680
|
+
isTypeOf(obj, type) {
|
|
681
|
+
return Object.prototype.toString.call(obj) === `[object ${type}]`;
|
|
682
|
+
},
|
|
683
|
+
/**
|
|
684
|
+
* Checks to see if something is undefined
|
|
685
|
+
* Stolen from underscore
|
|
686
|
+
*/
|
|
687
|
+
isUndefined(obj) {
|
|
688
|
+
// eslint-disable-next-line no-void
|
|
689
|
+
return obj === void 0;
|
|
690
|
+
},
|
|
691
|
+
/**
|
|
692
|
+
* Replace first N characters of the string with maskChar
|
|
693
|
+
* eg: maskFirstNCharacters( '1234567890', 6, 'X' ) yields XXXXXX7890
|
|
694
|
+
* @param str String to mask
|
|
695
|
+
* @param num Number of characters we want to mask from the string
|
|
696
|
+
* @param mask String we want replace the first N chars with
|
|
697
|
+
* @returns Masked string
|
|
698
|
+
*/
|
|
699
|
+
maskFirstNCharacters(str, num, mask) {
|
|
700
|
+
// if str is empty, str or mask aren't strings,
|
|
701
|
+
// or n is not a number, do nothing
|
|
702
|
+
if (!this.isString(str) || !this.isString(mask) || str.length === 0 || !this.isNumber(num)) {
|
|
703
|
+
return str;
|
|
704
|
+
}
|
|
705
|
+
return str.substring(0, num).replaceAll(/./g, mask) + str.substring(num);
|
|
706
|
+
},
|
|
707
|
+
/**
|
|
708
|
+
* Trim a string
|
|
709
|
+
*/
|
|
710
|
+
trim(str) {
|
|
711
|
+
return str.trim();
|
|
712
|
+
},
|
|
713
|
+
/**
|
|
714
|
+
* Convert a percentage string like '25%' to 25/
|
|
715
|
+
* @param percentageString The percentage as a string
|
|
716
|
+
*/
|
|
717
|
+
percentageStringToNumber(percentageString) {
|
|
718
|
+
return Number(this.cutAfter(percentageString, '%'));
|
|
719
|
+
},
|
|
720
|
+
/**
|
|
721
|
+
* Remove all the spaces from a string
|
|
722
|
+
*/
|
|
723
|
+
removeSpaces(input) {
|
|
724
|
+
return String(input).replace(' ', '');
|
|
725
|
+
},
|
|
726
|
+
/**
|
|
727
|
+
* Returns the proper phrase depending on the count that is passed.
|
|
728
|
+
* Example:
|
|
729
|
+
* console.log(Str.pluralize('puppy', 'puppies', 1)) { // puppy
|
|
730
|
+
* console.log(Str.pluralize('puppy', 'puppies', 3)) { // puppies
|
|
731
|
+
*
|
|
732
|
+
* @param singular form of the phrase
|
|
733
|
+
* @param plural form of the phrase
|
|
734
|
+
* @param num the count which determines the plurality
|
|
735
|
+
*/
|
|
736
|
+
pluralize(singular, plural, num) {
|
|
737
|
+
if (!num || num > 1) {
|
|
738
|
+
return plural;
|
|
739
|
+
}
|
|
740
|
+
return singular;
|
|
741
|
+
},
|
|
742
|
+
/**
|
|
743
|
+
* Returns whether or not a string is an encrypted number or not.
|
|
744
|
+
*
|
|
745
|
+
* @param num that we want to see if its encrypted or not
|
|
746
|
+
*
|
|
747
|
+
* @returns Whether or not this string is an encrpypted number
|
|
748
|
+
*/
|
|
749
|
+
isEncryptedCardNumber(num) {
|
|
750
|
+
// Older encrypted versioning.
|
|
751
|
+
if (/^[\da-fA-F]+$/.test(num)) {
|
|
752
|
+
return num.length % 32 === 0;
|
|
753
|
+
}
|
|
754
|
+
// Check with the new versioning.
|
|
755
|
+
if (/^[vV][\d]+:[\da-fA-F]+$/.test(num)) {
|
|
756
|
+
return num.split(':')[1].length % 32 === 0;
|
|
757
|
+
}
|
|
758
|
+
return false;
|
|
759
|
+
},
|
|
760
|
+
/**
|
|
761
|
+
* Converts a value to boolean, case-insensitive.
|
|
762
|
+
*/
|
|
763
|
+
toBool(value) {
|
|
764
|
+
if (this.isString(value)) {
|
|
765
|
+
return value.toLowerCase() === 'true';
|
|
766
|
+
}
|
|
767
|
+
return !!value;
|
|
768
|
+
},
|
|
769
|
+
/**
|
|
770
|
+
* Checks if a string could be the masked version of another one.
|
|
771
|
+
*
|
|
772
|
+
* @param strA String to compare
|
|
773
|
+
* @param strB String to compare
|
|
774
|
+
* @param [mask] Defaults to X
|
|
775
|
+
* @returns True when first could be the masked version of second
|
|
776
|
+
*/
|
|
777
|
+
maskedEquals(strA, strB, mask) {
|
|
778
|
+
var _a, _b;
|
|
779
|
+
const firsts = (_a = strA.match(/.{1,1}/g)) !== null && _a !== void 0 ? _a : [];
|
|
780
|
+
const seconds = (_b = strB.match(/.{1,1}/g)) !== null && _b !== void 0 ? _b : [];
|
|
781
|
+
const defaultMask = mask || 'X';
|
|
782
|
+
if (firsts.length !== seconds.length) {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
for (let i = 0; i < firsts.length; i += 1) {
|
|
786
|
+
if (firsts[i] !== seconds[i] && firsts[i] !== defaultMask && seconds[i] !== defaultMask) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return true;
|
|
791
|
+
},
|
|
792
|
+
/**
|
|
793
|
+
* Bold any word matching the regexp in the text.
|
|
794
|
+
*/
|
|
795
|
+
boldify(text, regexp) {
|
|
796
|
+
return text.replace(regexp, '<strong>$1</strong>');
|
|
797
|
+
},
|
|
798
|
+
/**
|
|
799
|
+
* Check for whether a phone number is valid.
|
|
800
|
+
* @deprecated use isValidE164Phone to validate E.164 phone numbers or isValidPhoneFormat to validate phone numbers in general
|
|
801
|
+
*/
|
|
802
|
+
isValidPhone(phone) {
|
|
803
|
+
return Constants.CONST.SMS.E164_REGEX.test(phone);
|
|
804
|
+
},
|
|
805
|
+
/**
|
|
806
|
+
* Check for whether a phone number is valid.
|
|
807
|
+
*/
|
|
808
|
+
isValidPhoneNumber(phone) {
|
|
809
|
+
return parsePhoneNumber(phone).possible;
|
|
810
|
+
},
|
|
811
|
+
/**
|
|
812
|
+
* Check for whether a phone number is valid according to E.164 standard.
|
|
813
|
+
*/
|
|
814
|
+
isValidE164Phone(phone) {
|
|
815
|
+
return Constants.CONST.SMS.E164_REGEX.test(phone);
|
|
816
|
+
},
|
|
817
|
+
/**
|
|
818
|
+
* Check for whether a phone number is valid in different formats/standards. For example:
|
|
819
|
+
* significant: 4404589784
|
|
820
|
+
* international: +1 440-458-9784
|
|
821
|
+
* e164: +14404589784
|
|
822
|
+
* national: (440) 458-9784
|
|
823
|
+
* 123.456.7890
|
|
824
|
+
*/
|
|
825
|
+
isValidPhoneFormat(phone) {
|
|
826
|
+
return Constants.CONST.REG_EXP.GENERAL_PHONE_PART.test(phone);
|
|
827
|
+
},
|
|
828
|
+
/**
|
|
829
|
+
* We validate mentions by checking if it's first character is an allowed character.
|
|
830
|
+
*/
|
|
831
|
+
isValidMention(mention) {
|
|
832
|
+
// Mentions can start @ proceeded by a space, eg "ping @user@domain.tld"
|
|
833
|
+
if (/[\s@]/g.test(mention.charAt(0))) {
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
// Mentions can also start and end with a *, _, ~, ', or " (with no other preceding characters)
|
|
837
|
+
// eg "ping *@user@domain.tld*"
|
|
838
|
+
const firstChar = mention.charAt(0);
|
|
839
|
+
const lastChar = mention.charAt(mention.length - 1);
|
|
840
|
+
return /[*~_'"]/g.test(firstChar) && /[*~_'"]/g.test(lastChar) && firstChar === lastChar;
|
|
841
|
+
},
|
|
842
|
+
/**
|
|
843
|
+
* Returns text without our SMS domain
|
|
844
|
+
*/
|
|
845
|
+
removeSMSDomain(text) {
|
|
846
|
+
return text.replaceAll(REMOVE_SMS_DOMAIN_PATTERN, '');
|
|
847
|
+
},
|
|
848
|
+
/**
|
|
849
|
+
* Returns true if the text is a valid E.164 phone number with our SMS domain removed
|
|
850
|
+
*/
|
|
851
|
+
isSMSLogin(text) {
|
|
852
|
+
return this.isValidE164Phone(this.removeSMSDomain(text));
|
|
853
|
+
},
|
|
854
|
+
/**
|
|
855
|
+
* This method will return all matches of a single regex like preg_match_all() in PHP. This is not a common part of
|
|
856
|
+
* JS yet, so this is a good way of doing it according to
|
|
857
|
+
* https://github.com/airbnb/javascript/issues/1439#issuecomment-306297399 and doesn't get us in trouble with
|
|
858
|
+
* linting rules.
|
|
859
|
+
*/
|
|
860
|
+
matchAll(str, regex) {
|
|
861
|
+
const matches = [];
|
|
862
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
863
|
+
const collectMatches = (...args) => {
|
|
864
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
865
|
+
const match = Array.prototype.slice.call(args, 0, -2);
|
|
866
|
+
match.input = args[args.length - 1];
|
|
867
|
+
match.index = args[args.length - 2];
|
|
868
|
+
matches.push(match);
|
|
869
|
+
};
|
|
870
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
871
|
+
str.replace(regex, collectMatches);
|
|
872
|
+
return matches;
|
|
873
|
+
},
|
|
874
|
+
/**
|
|
875
|
+
* A simple GUID generator taken from https://stackoverflow.com/a/32760401/9114791
|
|
876
|
+
*
|
|
877
|
+
* @param [prefix] an optional prefix to put in front of the guid
|
|
878
|
+
*/
|
|
879
|
+
guid(prefix = '') {
|
|
880
|
+
function s4() {
|
|
881
|
+
return Math.floor((1 + Math.random()) * 0x10000)
|
|
882
|
+
.toString(16)
|
|
883
|
+
.substring(1);
|
|
884
|
+
}
|
|
885
|
+
return `${prefix}${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
|
|
886
|
+
},
|
|
887
|
+
/**
|
|
888
|
+
* Takes in a URL and returns it with a leading '/'
|
|
889
|
+
*
|
|
890
|
+
* @param url The URL to be formatted
|
|
891
|
+
* @returns The formatted URL
|
|
892
|
+
*/
|
|
893
|
+
normalizeUrl(url) {
|
|
894
|
+
return typeof url === 'string' && url.startsWith('/') ? url : `/${url}`;
|
|
895
|
+
},
|
|
896
|
+
/**
|
|
897
|
+
* Formats a URL by converting the domain name to lowercase and adding the missing 'https://' protocol.
|
|
898
|
+
*
|
|
899
|
+
* @param url The URL to be formatted
|
|
900
|
+
* @param defaultScheme The Scheme to use in the URL
|
|
901
|
+
* @returns The formatted URL
|
|
902
|
+
*/
|
|
903
|
+
sanitizeURL(url, defaultScheme = 'https') {
|
|
904
|
+
const regex = new RegExp(`^${UrlPatterns.URL_REGEX}$`, 'i');
|
|
905
|
+
const match = regex.exec(url);
|
|
906
|
+
if (!match) {
|
|
907
|
+
return url;
|
|
908
|
+
}
|
|
909
|
+
const website = match[3] ? match[2] : `${defaultScheme}://${match[2]}`;
|
|
910
|
+
return website.toLowerCase() + this.cutBefore(match[1], match[2]);
|
|
911
|
+
},
|
|
912
|
+
/**
|
|
913
|
+
* Checks if parameter is a string or function
|
|
914
|
+
* if it is a function then we will call it with
|
|
915
|
+
* any additional arguments.
|
|
916
|
+
*/
|
|
917
|
+
result: resultFn,
|
|
918
|
+
/**
|
|
919
|
+
* Get file extension for a given url with or
|
|
920
|
+
* without query parameters
|
|
921
|
+
*/
|
|
922
|
+
getExtension(url) {
|
|
923
|
+
var _a, _b;
|
|
924
|
+
if (typeof url !== 'string') {
|
|
925
|
+
Log.warn('Str.getExtension: url is not a string', { url });
|
|
926
|
+
return undefined;
|
|
927
|
+
}
|
|
928
|
+
return (_b = (_a = url.split('.').pop()) === null || _a === void 0 ? void 0 : _a.split('?')[0]) === null || _b === void 0 ? void 0 : _b.toLowerCase();
|
|
929
|
+
},
|
|
930
|
+
/**
|
|
931
|
+
* Takes in a URL and checks if the file extension is PDF
|
|
932
|
+
*
|
|
933
|
+
* @param url The URL to be checked
|
|
934
|
+
* @returns Whether file path is PDF or not
|
|
935
|
+
*/
|
|
936
|
+
isPDF(url) {
|
|
937
|
+
return this.getExtension(url) === 'pdf';
|
|
938
|
+
},
|
|
939
|
+
/**
|
|
940
|
+
* Takes in a URL and checks if the file extension is an image
|
|
941
|
+
* that can be rendered by React Native. Do NOT add extensions
|
|
942
|
+
* to this list unless they appear in this list and are
|
|
943
|
+
* supported by all platforms.
|
|
944
|
+
*
|
|
945
|
+
* https://reactnative.dev/docs/image#source
|
|
946
|
+
*/
|
|
947
|
+
isImage(url) {
|
|
948
|
+
const extension = this.getExtension(url);
|
|
949
|
+
if (!extension) {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
return ['jpeg', 'jpg', 'gif', 'png', 'bmp', 'webp'].includes(extension);
|
|
953
|
+
},
|
|
954
|
+
/**
|
|
955
|
+
* Takes in a URL and checks if the file extension is a video
|
|
956
|
+
* that can be rendered by React Native. Do NOT add extensions
|
|
957
|
+
* to this list unless they are supported by all platforms.
|
|
958
|
+
*
|
|
959
|
+
* https://developer.android.com/media/platform/supported-formats#video-formats
|
|
960
|
+
* https://developer.apple.com/documentation/coremedia/1564239-video_codec_constants
|
|
961
|
+
* https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs
|
|
962
|
+
*/
|
|
963
|
+
isVideo(url) {
|
|
964
|
+
const extension = this.getExtension(url);
|
|
965
|
+
if (!extension) {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
return ['mov', 'mp4', 'webm', 'mkv'].includes(extension);
|
|
969
|
+
},
|
|
970
|
+
/**
|
|
971
|
+
* Checks whether the given string is a +@ domain email account, such as
|
|
972
|
+
* +@domain.com
|
|
973
|
+
*
|
|
974
|
+
* @returns True if is a domain account email, otherwise false.
|
|
975
|
+
*/
|
|
976
|
+
isDomainEmail(email) {
|
|
977
|
+
return this.startsWith(email, '+@');
|
|
978
|
+
},
|
|
979
|
+
/**
|
|
980
|
+
* Find the minimum indentation of any line in the string,
|
|
981
|
+
* and remove that number of leading spaces from every line in the string.
|
|
982
|
+
*
|
|
983
|
+
* It also removes at most one leading newline, to reflect a common usage:
|
|
984
|
+
*
|
|
985
|
+
* ```
|
|
986
|
+
* Str.dedent(`
|
|
987
|
+
* const myIndentedStr = 'Hello, world!';
|
|
988
|
+
* console.log(myIndentedStr);
|
|
989
|
+
* `)
|
|
990
|
+
* ```
|
|
991
|
+
*
|
|
992
|
+
* This implementation assumes you'd want that to be:
|
|
993
|
+
*
|
|
994
|
+
* ```
|
|
995
|
+
* const myIndentedStr = 'Hello, world!';
|
|
996
|
+
* console.log(myIndentedStr);
|
|
997
|
+
*
|
|
998
|
+
* ```
|
|
999
|
+
*
|
|
1000
|
+
* Rather than:
|
|
1001
|
+
*
|
|
1002
|
+
* ```
|
|
1003
|
+
*
|
|
1004
|
+
* const myIndentedStr = 'Hello, world!';
|
|
1005
|
+
* console.log(myIndentedStr);
|
|
1006
|
+
*
|
|
1007
|
+
* ```
|
|
1008
|
+
*/
|
|
1009
|
+
dedent(str) {
|
|
1010
|
+
var _a, _b;
|
|
1011
|
+
// Remove at most one leading newline
|
|
1012
|
+
const stringWithoutLeadingNewlines = str.replaceAll(/^\r?\n/g, '');
|
|
1013
|
+
// Split string by remaining newlines
|
|
1014
|
+
const lines = stringWithoutLeadingNewlines.replaceAll('\r\n', '\n').split('\n');
|
|
1015
|
+
// Find the minimum indentation of non-empty lines
|
|
1016
|
+
let minIndent = Number.MAX_SAFE_INTEGER;
|
|
1017
|
+
for (const line of lines) {
|
|
1018
|
+
if (line.trim().length === 0) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
const indentation = (_b = (_a = line.match(/^ */)) === null || _a === void 0 ? void 0 : _a[0].length) !== null && _b !== void 0 ? _b : 0;
|
|
1022
|
+
if (indentation < minIndent) {
|
|
1023
|
+
minIndent = indentation;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
// Remove the common indentation
|
|
1027
|
+
return lines.map((line) => line.slice(minIndent)).join('\n');
|
|
1028
|
+
},
|
|
1029
|
+
/**
|
|
1030
|
+
* Polyfill for String.prototype.replaceAll
|
|
1031
|
+
*/
|
|
1032
|
+
replaceAll(text, searchValue, replaceValue) {
|
|
1033
|
+
return String.prototype.replaceAll.call(text, searchValue, replaceValue);
|
|
1034
|
+
},
|
|
1035
|
+
};
|
|
1036
|
+
export default Str;
|