@libs-ui/utils 0.2.355-8 → 0.2.356-0
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/README.md +633 -2
- package/base64.d.ts +25 -0
- package/cache.d.ts +73 -0
- package/collection.d.ts +12 -0
- package/color.d.ts +37 -0
- package/communicate-micro.d.ts +34 -1
- package/crypto-3rd.d.ts +12 -0
- package/crypto.d.ts +17 -0
- package/dangerous-object.d.ts +8 -1
- package/data.d.ts +5 -3
- package/date.d.ts +12 -0
- package/dom.d.ts +56 -0
- package/download.d.ts +21 -0
- package/esm2022/base64.mjs +27 -2
- package/esm2022/cache.mjs +74 -1
- package/esm2022/collection.mjs +13 -1
- package/esm2022/color.mjs +38 -1
- package/esm2022/communicate-micro.mjs +37 -5
- package/esm2022/crypto-3rd.mjs +13 -1
- package/esm2022/crypto.mjs +18 -1
- package/esm2022/dangerous-object.mjs +11 -2
- package/esm2022/data.mjs +6 -4
- package/esm2022/date.mjs +13 -1
- package/esm2022/dom.mjs +57 -1
- package/esm2022/download.mjs +22 -1
- package/esm2022/file.mjs +54 -1
- package/esm2022/format-number.mjs +31 -3
- package/esm2022/format-text.mjs +73 -2
- package/esm2022/function-check-embed-frame.mjs +28 -1
- package/esm2022/pattern.mjs +83 -3
- package/esm2022/trace.mjs +8 -1
- package/esm2022/two-way-signal-object.mjs +14 -1
- package/esm2022/uri.mjs +22 -3
- package/esm2022/url-search-params.mjs +68 -1
- package/esm2022/url.mjs +10 -1
- package/esm2022/uuid.mjs +7 -1
- package/esm2022/xss-filter.mjs +14 -1
- package/fesm2022/libs-ui-utils.mjs +723 -21
- package/fesm2022/libs-ui-utils.mjs.map +1 -1
- package/file.d.ts +53 -0
- package/format-number.d.ts +29 -1
- package/format-text.d.ts +71 -0
- package/function-check-embed-frame.d.ts +22 -0
- package/package.json +2 -2
- package/pattern.d.ts +81 -1
- package/trace.d.ts +7 -0
- package/two-way-signal-object.d.ts +13 -0
- package/uri.d.ts +20 -0
- package/url-search-params.d.ts +67 -0
- package/url.d.ts +9 -0
- package/uuid.d.ts +6 -0
- package/xss-filter.d.ts +13 -0
|
@@ -157,6 +157,26 @@ const isReturnAsIsObject = (obj) => {
|
|
|
157
157
|
const isSafeToProcess = (obj) => {
|
|
158
158
|
return !isDangerousObject(obj);
|
|
159
159
|
};
|
|
160
|
+
/**
|
|
161
|
+
* Kiểm tra xem giá trị có phải là kiểu dữ liệu nguyên thủy (Primitive) không.
|
|
162
|
+
* Bao gồm: string, number, boolean, symbol, bigint, null, undefined.
|
|
163
|
+
* @param value Giá trị cần kiểm tra
|
|
164
|
+
* @returns true nếu là kiểu nguyên thủy
|
|
165
|
+
*/
|
|
166
|
+
const isPrimitiveType = (value) => {
|
|
167
|
+
return value === null || value === undefined || ['string', 'number', 'boolean', 'symbol', 'bigint'].includes(typeof value);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const ERROR_MESSAGE_EMPTY_VALID = 'i18n_valid_empty_message';
|
|
171
|
+
const ERROR_MESSAGE_PATTEN_VALID = 'i18n_valid_pattern_message';
|
|
172
|
+
const ERROR_MESSAGE_MIN_VALID = 'i18n_message_error_input_min_value';
|
|
173
|
+
const ERROR_MESSAGE_MAX_VALID = 'i18n_message_error_input_max_value';
|
|
174
|
+
const ERROR_MESSAGE_MIN_LENGTH = 'i18n_message_error_input_min_length';
|
|
175
|
+
const ERROR_MESSAGE_MAX_LENGTH = 'i18n_message_error_input_max_length';
|
|
176
|
+
const CHARACTER_DATA_EMPTY = '__';
|
|
177
|
+
const DEFAULT_START_PAGE_0 = 'defaultStartPage0';
|
|
178
|
+
const COMMUNICATE_MICRO_PREFIX_TYPE = '3RD_INTEGRATE_MICRO_SITE_';
|
|
179
|
+
const COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE = 'MICRO_SITES_ALL_MESSAGE';
|
|
160
180
|
|
|
161
181
|
let key$1 = '12~@#loqwsxacva(3rdhaq12';
|
|
162
182
|
/**
|
|
@@ -172,6 +192,12 @@ const setKeyCrypto = (value) => {
|
|
|
172
192
|
const keyStore$1 = () => {
|
|
173
193
|
return key$1;
|
|
174
194
|
};
|
|
195
|
+
/**
|
|
196
|
+
* Mã hóa dữ liệu dạng chuỗi bằng thuật toán AES-CBC.
|
|
197
|
+
* Yêu cầu key mã hóa đã được thiết lập qua setKeyCrypto.
|
|
198
|
+
* @param plainData Chuỗi văn bản thuần túy cần mã hóa
|
|
199
|
+
* @returns Chuỗi kết quả đã được mã hóa (Base64 string)
|
|
200
|
+
*/
|
|
175
201
|
const encrypt = (plainData) => {
|
|
176
202
|
if (!keyStore$1()) {
|
|
177
203
|
throw Error('lỗi chưa setup key mã hóa');
|
|
@@ -183,6 +209,12 @@ const encrypt = (plainData) => {
|
|
|
183
209
|
const options = { iv: iv, mode: mode, padding: padding };
|
|
184
210
|
return CryptoES.AES.encrypt(plainData, key, options).toString();
|
|
185
211
|
};
|
|
212
|
+
/**
|
|
213
|
+
* Giải mã dữ liệu đã được mã hóa bằng AES-CBC.
|
|
214
|
+
* Yêu cầu cùng một key mã hóa đã dùng để encrypt.
|
|
215
|
+
* @param encryptedData Chuỗi dữ liệu đã mã hóa
|
|
216
|
+
* @returns Chuỗi văn bản thuần túy sau khi giải mã (UTF-8)
|
|
217
|
+
*/
|
|
186
218
|
const decrypt = (encryptedData) => {
|
|
187
219
|
if (!keyStore$1()) {
|
|
188
220
|
throw Error('lỗi chưa setup key mã hóa');
|
|
@@ -194,6 +226,11 @@ const decrypt = (encryptedData) => {
|
|
|
194
226
|
const options = { iv: iv, mode: mode, padding: padding };
|
|
195
227
|
return CryptoES.AES.decrypt(encryptedData, key, options).toString(CryptoES.enc.Utf8);
|
|
196
228
|
};
|
|
229
|
+
/**
|
|
230
|
+
* Tạo mã hash MD5 từ chuỗi văn bản.
|
|
231
|
+
* @param plainData Chuỗi văn bản cần hash
|
|
232
|
+
* @returns Chuỗi mã hash MD5 (32 ký tự hex)
|
|
233
|
+
*/
|
|
197
234
|
const md5 = (plainData) => {
|
|
198
235
|
return CryptoES.MD5(plainData).toString();
|
|
199
236
|
};
|
|
@@ -212,6 +249,12 @@ const setKeyCrypto3rd = (value) => {
|
|
|
212
249
|
const keyStore = () => {
|
|
213
250
|
return key;
|
|
214
251
|
};
|
|
252
|
+
/**
|
|
253
|
+
* Mã hóa dữ liệu dạng chuỗi bằng thuật toán AES-CBC.
|
|
254
|
+
* Yêu cầu key mã hóa đã được thiết lập qua setKeyCrypto3rd.
|
|
255
|
+
* @param plainData Chuỗi văn bản thuần túy cần mã hóa
|
|
256
|
+
* @returns Chuỗi kết quả đã được mã hóa (Base64 string)
|
|
257
|
+
*/
|
|
215
258
|
const encrypt3rd = (plainData) => {
|
|
216
259
|
if (!keyStore()) {
|
|
217
260
|
throw Error('lỗi chưa setup key mã hóa');
|
|
@@ -223,6 +266,12 @@ const encrypt3rd = (plainData) => {
|
|
|
223
266
|
const options = { iv: iv, mode: mode, padding: padding };
|
|
224
267
|
return CryptoES.AES.encrypt(plainData, key, options).toString();
|
|
225
268
|
};
|
|
269
|
+
/**
|
|
270
|
+
* Giải mã dữ liệu đã được mã hóa bằng AES-CBC.
|
|
271
|
+
* Yêu cầu cùng một key mã hóa đã dùng để encrypt.
|
|
272
|
+
* @param encryptedData Chuỗi dữ liệu đã mã hóa
|
|
273
|
+
* @returns Chuỗi văn bản thuần túy sau khi giải mã (UTF-8)
|
|
274
|
+
*/
|
|
226
275
|
const decrypt3rd = (encryptedData) => {
|
|
227
276
|
if (!keyStore()) {
|
|
228
277
|
throw Error('lỗi chưa setup key mã hóa');
|
|
@@ -236,36 +285,66 @@ const decrypt3rd = (encryptedData) => {
|
|
|
236
285
|
};
|
|
237
286
|
|
|
238
287
|
//NOTE: Do là function truyền từ bên ngoài vào nên sẽ không có unit test
|
|
288
|
+
/**
|
|
289
|
+
* Hàm kiểm tra mặc định — phát hiện ứng dụng đang chạy trong iframe/embed frame
|
|
290
|
+
* bằng cách so sánh `window.parent` với `window.top`.
|
|
291
|
+
* Có thể bị ghi đè bởi `updateFunctionCheckEmbedFrame` cho môi trường tùy chỉnh.
|
|
292
|
+
*/
|
|
239
293
|
let functionCheck = () => {
|
|
240
294
|
return window.parent !== window.top;
|
|
241
295
|
};
|
|
296
|
+
/**
|
|
297
|
+
* Ghi đè hàm kiểm tra embed frame bằng một hàm logic tùy chỉnh.
|
|
298
|
+
* Hữu ích trong môi trường Micro-frontend hoặc khi cần mock cho testing.
|
|
299
|
+
*
|
|
300
|
+
* @param functionCustom Hàm trả về `true` nếu app đang chạy trong iframe/embed frame
|
|
301
|
+
* @example
|
|
302
|
+
* // Micro-frontend: luôn coi là embed
|
|
303
|
+
* updateFunctionCheckEmbedFrame(() => true);
|
|
304
|
+
*
|
|
305
|
+
* // Testing: luôn coi là không embed
|
|
306
|
+
* updateFunctionCheckEmbedFrame(() => false);
|
|
307
|
+
*/
|
|
242
308
|
const updateFunctionCheckEmbedFrame = (functionCustom) => {
|
|
243
309
|
functionCheck = functionCustom;
|
|
244
310
|
};
|
|
311
|
+
/**
|
|
312
|
+
* Kiểm tra ứng dụng hiện tại có đang được nhúng trong iframe/embed frame hay không.
|
|
313
|
+
* Sử dụng hàm logic đang được đăng ký (mặc định hoặc custom).
|
|
314
|
+
*
|
|
315
|
+
* @returns `true` nếu app đang chạy trong iframe/embed, `false` nếu đang chạy standalone
|
|
316
|
+
* @example
|
|
317
|
+
* if (isEmbedFrame()) {
|
|
318
|
+
* // Ẩn header, sidebar vì đã có container bên ngoài quản lý
|
|
319
|
+
* }
|
|
320
|
+
*/
|
|
245
321
|
const isEmbedFrame = () => {
|
|
246
322
|
return functionCheck();
|
|
247
323
|
};
|
|
248
324
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const ERROR_MESSAGE_MAX_VALID = 'i18n_message_error_input_max_value';
|
|
253
|
-
const ERROR_MESSAGE_MIN_LENGTH = 'i18n_message_error_input_min_length';
|
|
254
|
-
const ERROR_MESSAGE_MAX_LENGTH = 'i18n_message_error_input_max_length';
|
|
255
|
-
const CHARACTER_DATA_EMPTY = '__';
|
|
256
|
-
const DEFAULT_START_PAGE_0 = 'defaultStartPage0';
|
|
257
|
-
const COMMUNICATE_MICRO_PREFIX_TYPE = '3RD_INTEGRATE_MICRO_SITE_';
|
|
258
|
-
const COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE = 'MICRO_SITES_ALL_MESSAGE';
|
|
259
|
-
|
|
260
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
325
|
+
/**
|
|
326
|
+
* Cấu hình các key global cho truyền thông micro
|
|
327
|
+
*/
|
|
261
328
|
class UtilsCommunicateMicroKeyGlobal {
|
|
329
|
+
/** Key sự kiện cho modal */
|
|
262
330
|
static KEY_MESSAGE_MODAL = 'LIBS_UI_MODEL_EVENT';
|
|
331
|
+
/** Bỏ qua cập nhật thời gian live event modal */
|
|
263
332
|
static IGNORE_INTERVAL_UPDATE_TIME_LIVE_EVENT_MODAL = false;
|
|
264
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Tiện ích hỗ trợ truyền thông giữa các cửa sổ, iframes (Cross-document communication)
|
|
336
|
+
* Sử dụng Window.postMessage kèm theo mã hóa/giải mã dữ liệu tự động.
|
|
337
|
+
*/
|
|
265
338
|
class UtilsCommunicateMicro {
|
|
266
339
|
static initdEvent;
|
|
267
340
|
static subs = new Map();
|
|
268
341
|
static allMessageSub = new Subject();
|
|
342
|
+
/**
|
|
343
|
+
* Khởi tạo trình lắng nghe sự kiện 'message' trên window hiện tại.
|
|
344
|
+
* Cần được gọi một lần duy nhất tại Root Component (AppComponent).
|
|
345
|
+
* @param currentWindow Đối tượng window hiện tại
|
|
346
|
+
* @param destroyRef Reference để tự động unsubscribe khi component bị hủy
|
|
347
|
+
*/
|
|
269
348
|
static initEvent(currentWindow, destroyRef) {
|
|
270
349
|
if (this.initdEvent) {
|
|
271
350
|
return;
|
|
@@ -311,6 +390,11 @@ class UtilsCommunicateMicro {
|
|
|
311
390
|
UtilsCache.ClearAll();
|
|
312
391
|
});
|
|
313
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* Gửi message tới cửa sổ cha (Parent) hoặc top window.
|
|
395
|
+
* Thường dùng khi app chạy trong Iframe.
|
|
396
|
+
* @param data Dữ liệu cần gửi ({type: string, response: any})
|
|
397
|
+
*/
|
|
314
398
|
static PostMessageToParent(data) {
|
|
315
399
|
data = this.convertData(data);
|
|
316
400
|
try {
|
|
@@ -324,6 +408,10 @@ class UtilsCommunicateMicro {
|
|
|
324
408
|
console.log(error);
|
|
325
409
|
}
|
|
326
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Gửi message tới tất cả các iframe con có trong trang.
|
|
413
|
+
* @param data Dữ liệu cần gửi ({type: string, response: any})
|
|
414
|
+
*/
|
|
327
415
|
static PostMessageToChildren(data) {
|
|
328
416
|
data = this.convertData(data);
|
|
329
417
|
const iframes = document.querySelectorAll('iframe');
|
|
@@ -331,6 +419,10 @@ class UtilsCommunicateMicro {
|
|
|
331
419
|
iframe?.contentWindow?.postMessage(data, '*');
|
|
332
420
|
});
|
|
333
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* Gửi message tới cửa sổ đã mở cửa sổ hiện tại (Window Opener).
|
|
424
|
+
* @param data Dữ liệu cần gửi ({type: string, response: any})
|
|
425
|
+
*/
|
|
334
426
|
static PostMessageToOpener(data) {
|
|
335
427
|
if (!window.opener) {
|
|
336
428
|
return;
|
|
@@ -363,6 +455,11 @@ class UtilsCommunicateMicro {
|
|
|
363
455
|
return data;
|
|
364
456
|
}
|
|
365
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* Đăng ký lắng nghe các message theo type cụ thể. Trả về một RxJS Subject.
|
|
460
|
+
* @param messageType Type của message hoặc danh sách các types cần lắng nghe
|
|
461
|
+
* @returns Subject sẽ phát ra message event khi nhận được dữ liệu hợp lệ
|
|
462
|
+
*/
|
|
366
463
|
static GetMessage(messageType) {
|
|
367
464
|
if (!this.initdEvent) {
|
|
368
465
|
throw Error('chưa khơi tạo hàm lắng nghe sự kiện, gọi UtilsCommunicateMicro.initEvent(window) tại root component');
|
|
@@ -483,6 +580,12 @@ class UtilsLanguageConstants {
|
|
|
483
580
|
}
|
|
484
581
|
}
|
|
485
582
|
|
|
583
|
+
/**
|
|
584
|
+
* Tạo một chuỗi định danh duy nhất (Unique ID) dựa trên thời gian (timestamp) và các ký tự ngẫu nhiên.
|
|
585
|
+
* Chuỗi kết quả được xáo trộn và băm bằng MD5, trả về một chuỗi 32 ký tự alphanumeric.
|
|
586
|
+
*
|
|
587
|
+
* @returns {string} Chuỗi định danh duy nhất (MD5 hash).
|
|
588
|
+
*/
|
|
486
589
|
const uuid = () => {
|
|
487
590
|
const timestamp = performance.now() * 1000;
|
|
488
591
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/~';
|
|
@@ -502,6 +605,10 @@ const uuid = () => {
|
|
|
502
605
|
|
|
503
606
|
/* eslint-disable no-async-promise-executor */
|
|
504
607
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
608
|
+
/**
|
|
609
|
+
* Tiện ích quản lý Cache, hỗ trợ cả LocalStorage (đồng bộ) và IndexedDB (bất đồng bộ)
|
|
610
|
+
* Tự động mã hóa dữ liệu và xử lý thời gian hết hạn
|
|
611
|
+
*/
|
|
505
612
|
class UtilsCache {
|
|
506
613
|
static CACHE_EXPIRE_TIME_DEFAULT = 5 * 60;
|
|
507
614
|
static CACHE_EXPIRE_NONE = -1;
|
|
@@ -515,6 +622,10 @@ class UtilsCache {
|
|
|
515
622
|
static itemIndexByKey = 'key';
|
|
516
623
|
static dbVersion = 1;
|
|
517
624
|
static db = null;
|
|
625
|
+
/**
|
|
626
|
+
* Khởi tạo cấu hình cho Cache
|
|
627
|
+
* @param config Cấu hình khởi tạo (tên DB, key ngôn ngữ, event clear...)
|
|
628
|
+
*/
|
|
518
629
|
static init(config) {
|
|
519
630
|
if (this.initdEvent) {
|
|
520
631
|
return;
|
|
@@ -533,12 +644,20 @@ class UtilsCache {
|
|
|
533
644
|
this.languageKeyCache = config.languageKeyCache;
|
|
534
645
|
}
|
|
535
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Lưu ngôn ngữ hiện tại vào cache
|
|
649
|
+
* @param lang Mã ngôn ngữ (vi, en, ...)
|
|
650
|
+
*/
|
|
536
651
|
static setLang(lang) {
|
|
537
652
|
if (!UtilsLanguageConstants.isSupported(lang)) {
|
|
538
653
|
throw Error(`Language not support ${lang}`);
|
|
539
654
|
}
|
|
540
655
|
this.Set(this.languageKeyCache, lang, this.CACHE_EXPIRE_NONE);
|
|
541
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* Lấy ngôn ngữ từ cache
|
|
659
|
+
* @returns Mã ngôn ngữ
|
|
660
|
+
*/
|
|
542
661
|
static getLang() {
|
|
543
662
|
return this.Get(this.languageKeyCache, UtilsLanguageConstants.defaultLang());
|
|
544
663
|
}
|
|
@@ -636,6 +755,13 @@ class UtilsCache {
|
|
|
636
755
|
},
|
|
637
756
|
};
|
|
638
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Lấy giá trị từ IndexedDB theo key (bất đồng bộ)
|
|
760
|
+
* @param key Khóa cache
|
|
761
|
+
* @param default_value Giá trị mặc định nếu không tìm thấy
|
|
762
|
+
* @param isKeyMD5 Nếu true, key đã là MD5. Nếu false (mặc định), sẽ tự động MD5 key.
|
|
763
|
+
* @returns Promise chứa giá trị đã giải mã
|
|
764
|
+
*/
|
|
639
765
|
static async GetAsync(key, default_value, isKeyMD5 = false) {
|
|
640
766
|
key = isKeyMD5 ? key : md5(key);
|
|
641
767
|
return new Promise(async (resolve) => {
|
|
@@ -664,6 +790,12 @@ class UtilsCache {
|
|
|
664
790
|
};
|
|
665
791
|
});
|
|
666
792
|
}
|
|
793
|
+
/**
|
|
794
|
+
* Lấy giá trị từ LocalStorage theo key (đồng bộ)
|
|
795
|
+
* @param key Khóa cache
|
|
796
|
+
* @param default_value Giá trị mặc định nếu không tìm thấy
|
|
797
|
+
* @returns Giá trị đã giải mã hoặc giá trị mặc định
|
|
798
|
+
*/
|
|
667
799
|
static Get(key, default_value) {
|
|
668
800
|
// support cho những file không thể inject UtilsCache
|
|
669
801
|
if (!key) {
|
|
@@ -692,6 +824,14 @@ class UtilsCache {
|
|
|
692
824
|
static GetDefaultValueBySpecificKey(key, default_value) {
|
|
693
825
|
return default_value;
|
|
694
826
|
}
|
|
827
|
+
/**
|
|
828
|
+
* Lưu giá trị vào IndexedDB (bất đồng bộ)
|
|
829
|
+
* @param key Khóa cache
|
|
830
|
+
* @param value Giá trị cần lưu
|
|
831
|
+
* @param expireTimeBySecond Thời gian hết hạn (giây). Mặc định 5 phút. Dùng CACHE_EXPIRE_NONE để vĩnh viễn.
|
|
832
|
+
* @param isKeyMD5 Nếu true, key đã là MD5.
|
|
833
|
+
* @returns Promise chứa thông tin kết quả
|
|
834
|
+
*/
|
|
695
835
|
static async SetAsync(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT, isKeyMD5 = false) {
|
|
696
836
|
// support inject UtilsCache
|
|
697
837
|
return new Promise(async (resolve) => {
|
|
@@ -723,6 +863,13 @@ class UtilsCache {
|
|
|
723
863
|
}
|
|
724
864
|
});
|
|
725
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Lưu giá trị vào LocalStorage (đồng bộ)
|
|
868
|
+
* @param key Khóa cache
|
|
869
|
+
* @param value Giá trị cần lưu
|
|
870
|
+
* @param expireTimeBySecond Thời gian hết hạn (giây). Mặc định 5 phút.
|
|
871
|
+
* @returns true nếu thành công
|
|
872
|
+
*/
|
|
726
873
|
static Set(key, value, expireTimeBySecond = this.CACHE_EXPIRE_TIME_DEFAULT) {
|
|
727
874
|
// support cho những file không inject UtilsCache
|
|
728
875
|
const currentMillisecond = expireTimeBySecond === this.CACHE_EXPIRE_NONE ? this.CACHE_EXPIRE_NONE : new Date().valueOf() / 1000 + expireTimeBySecond;
|
|
@@ -739,6 +886,11 @@ class UtilsCache {
|
|
|
739
886
|
return false;
|
|
740
887
|
}
|
|
741
888
|
}
|
|
889
|
+
/**
|
|
890
|
+
* Xóa một key trong IndexedDB
|
|
891
|
+
* @param key Khóa cần xóa
|
|
892
|
+
* @param isMD5 Nếu true, key đã được MD5.
|
|
893
|
+
*/
|
|
742
894
|
static async ClearAsync(key, isMD5 = false) {
|
|
743
895
|
return new Promise(async (resolve) => {
|
|
744
896
|
const objectStore = await this.getObjectStore();
|
|
@@ -755,12 +907,19 @@ class UtilsCache {
|
|
|
755
907
|
};
|
|
756
908
|
});
|
|
757
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Xóa một key trong LocalStorage
|
|
912
|
+
* @param key Khóa cần xóa
|
|
913
|
+
*/
|
|
758
914
|
static Clear(key) {
|
|
759
915
|
if (key.includes('kc-callback-')) {
|
|
760
916
|
return;
|
|
761
917
|
}
|
|
762
918
|
this.LocalStorage.removeItem(key);
|
|
763
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Xóa toàn bộ dữ liệu trong IndexedDB
|
|
922
|
+
*/
|
|
764
923
|
static ClearAllAsync() {
|
|
765
924
|
return new Promise(async (resolve) => {
|
|
766
925
|
const objectStore = await this.getObjectStore();
|
|
@@ -778,6 +937,9 @@ class UtilsCache {
|
|
|
778
937
|
};
|
|
779
938
|
});
|
|
780
939
|
}
|
|
940
|
+
/**
|
|
941
|
+
* Xóa toàn bộ dữ liệu trong LocalStorage (kiểm soát các key đặc biệt)
|
|
942
|
+
*/
|
|
781
943
|
static ClearAll() {
|
|
782
944
|
if (isEmbedFrame()) {
|
|
783
945
|
const data = {
|
|
@@ -821,6 +983,11 @@ class UtilsCache {
|
|
|
821
983
|
this.Set(key, value, this.CACHE_EXPIRE_NONE);
|
|
822
984
|
});
|
|
823
985
|
}
|
|
986
|
+
/**
|
|
987
|
+
* Xóa tất cả các khóa bắt đầu bằng tiền tố nhất định trong IndexedDB
|
|
988
|
+
* @param keyCacheStartWith Tiền tố khóa
|
|
989
|
+
* @param isKeyMD5 Nếu true, tiền tố đã được MD5.
|
|
990
|
+
*/
|
|
824
991
|
static DeleteKeyStartWithAsync(keyCacheStartWith, isKeyMD5 = false) {
|
|
825
992
|
return new Promise(async (resolve) => {
|
|
826
993
|
const objectStore = await this.getObjectStore();
|
|
@@ -847,6 +1014,11 @@ class UtilsCache {
|
|
|
847
1014
|
};
|
|
848
1015
|
});
|
|
849
1016
|
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Xóa tất cả các khóa bắt đầu bằng tiền tố nhất định trong LocalStorage
|
|
1019
|
+
* @param keyCache Tiền tố khóa
|
|
1020
|
+
* @param isMD5 Nếu true, tiền tố sẽ được MD5 trước khi so sánh.
|
|
1021
|
+
*/
|
|
850
1022
|
static DeleteKeyStartWith(keyCache, isMD5 = false) {
|
|
851
1023
|
keyCache = isMD5 ? md5(keyCache) : keyCache;
|
|
852
1024
|
const keys = Object.keys(this.LocalStorage);
|
|
@@ -859,6 +1031,10 @@ class UtilsCache {
|
|
|
859
1031
|
}
|
|
860
1032
|
});
|
|
861
1033
|
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Xóa hoàn toàn cơ sở dữ liệu IndexedDB
|
|
1036
|
+
* @param dbName Tên DB, mặc định theo cấu hình.
|
|
1037
|
+
*/
|
|
862
1038
|
static DeleteDatabaseIndexDB(dbName) {
|
|
863
1039
|
return new Promise((resolve) => {
|
|
864
1040
|
dbName = (dbName || this.dbName);
|
|
@@ -887,14 +1063,26 @@ dayjs.extend(timezone);
|
|
|
887
1063
|
dayjs.extend(customParseFormat);
|
|
888
1064
|
let timeZoneSetup = 'Asia/Ho_Chi_Minh';
|
|
889
1065
|
let patternCheckTimeUTC = undefined;
|
|
1066
|
+
/**
|
|
1067
|
+
* Thiết lập múi giờ mặc định cho hệ thống (mặc định là Asia/Ho_Chi_Minh).
|
|
1068
|
+
* @param localeZone Tên múi giờ (ví dụ: 'America/New_York')
|
|
1069
|
+
*/
|
|
890
1070
|
const setDefaultTimeZone = (localeZone = timeZoneSetup) => {
|
|
891
1071
|
timeZoneSetup = localeZone;
|
|
892
1072
|
dayjs.tz.setDefault(localeZone);
|
|
893
1073
|
};
|
|
1074
|
+
/**
|
|
1075
|
+
* Thiết lập pattern Regex để nhận diện và chuẩn hóa các chuỗi thời gian UTC đặc biệt.
|
|
1076
|
+
* @param pattern Regex pattern dùng để kiểm tra và replace chuỗi thời gian
|
|
1077
|
+
*/
|
|
894
1078
|
const setPatternCheckTimeUTC = (pattern) => {
|
|
895
1079
|
patternCheckTimeUTC = pattern;
|
|
896
1080
|
};
|
|
897
1081
|
let functionFormatDate = undefined;
|
|
1082
|
+
/**
|
|
1083
|
+
* Cung cấp một hàm format tùy chỉnh ghi đè lên logic mặc định của formatDate.
|
|
1084
|
+
* @param functionCustom Hàm format tùy chỉnh
|
|
1085
|
+
*/
|
|
898
1086
|
const updateFunctionFormatDate = (functionCustom) => {
|
|
899
1087
|
functionFormatDate = functionCustom;
|
|
900
1088
|
};
|
|
@@ -1212,6 +1400,14 @@ const convertObjectToSignal = (data, cloneDeepIfNotSignal = true, isSignalPrimit
|
|
|
1212
1400
|
}
|
|
1213
1401
|
return data;
|
|
1214
1402
|
};
|
|
1403
|
+
/**
|
|
1404
|
+
* Chuyển đổi một đối tượng signal (có thể lồng nhau) thành đối tượng plain object/array/map/set.
|
|
1405
|
+
* @param data Dữ liệu signal hoặc đối tượng chứa signal cần chuyển đổi.
|
|
1406
|
+
* @param isCloneDeep Có thực hiện sao chép sâu dữ liệu trong quá trình chuyển đổi hay không. Mặc định là true.
|
|
1407
|
+
* @param seen Dùng nội bộ để tránh lặp vô hạn (WeakMap).
|
|
1408
|
+
* @param options Tùy chọn chuyển đổi (ví dụ: convertWhenFirstItemArrayNotIsSignal).
|
|
1409
|
+
* @returns Đối tượng plain object đã được "giải nén" mọi signal bên trong.
|
|
1410
|
+
*/
|
|
1215
1411
|
const convertSignalToObject = (data, isCloneDeep = true, seen = new WeakMap(), options) => {
|
|
1216
1412
|
let ignoreCheckSeenHasDataAfterWhile = false;
|
|
1217
1413
|
while (isSignal(data) && !seen.has(data)) {
|
|
@@ -1276,6 +1472,11 @@ const convertSignalToObject = (data, isCloneDeep = true, seen = new WeakMap(), o
|
|
|
1276
1472
|
}
|
|
1277
1473
|
return result;
|
|
1278
1474
|
};
|
|
1475
|
+
/**
|
|
1476
|
+
* Unwrap một giá trị cho đến khi không còn là signal nữa.
|
|
1477
|
+
* @param data Giá trị (có thể là lồng nhiều cấp signal) cần unwrap.
|
|
1478
|
+
* @returns Giá trị cuối cùng bên trong signal.
|
|
1479
|
+
*/
|
|
1279
1480
|
const unwrapSignal = (data) => {
|
|
1280
1481
|
while (isSignal(data)) {
|
|
1281
1482
|
data = data();
|
|
@@ -1930,13 +2131,24 @@ const generateInterface = (obj, interfaceName) => {
|
|
|
1930
2131
|
|
|
1931
2132
|
let parser = null;
|
|
1932
2133
|
let bowserParser = null;
|
|
2134
|
+
/**
|
|
2135
|
+
* Lấy thông tin thiết bị và trình duyệt (Sử dụng thư viện Bowser).
|
|
2136
|
+
* @returns Đối tượng Parser chứa thông tin OS, Browser, Platform...
|
|
2137
|
+
*/
|
|
1933
2138
|
const getDeviceInfo = () => {
|
|
1934
2139
|
if (!bowserParser) {
|
|
1935
2140
|
bowserParser = Bowser.getParser(window.navigator.userAgent);
|
|
1936
2141
|
}
|
|
1937
2142
|
return bowserParser;
|
|
1938
2143
|
};
|
|
2144
|
+
/**
|
|
2145
|
+
* Kiểm tra xem thiết bị hiện tại có hỗ trợ cảm ứng (Touch) không.
|
|
2146
|
+
* @returns true nếu là thiết bị cảm ứng
|
|
2147
|
+
*/
|
|
1939
2148
|
const isTouchDevice = () => ('onpointerdown' in navigator && navigator.maxTouchPoints > 0) || 'ontouchstart' in window || (window.matchMedia?.('(pointer: coarse)').matches ?? false);
|
|
2149
|
+
/**
|
|
2150
|
+
* Xác định tên sự kiện click phù hợp cho thiết bị (click cho desktop, pointerdown cho touch).
|
|
2151
|
+
*/
|
|
1940
2152
|
const getEventNameHandleClick = (() => {
|
|
1941
2153
|
if (isTouchDevice()) {
|
|
1942
2154
|
return 'pointerdown';
|
|
@@ -1944,12 +2156,22 @@ const getEventNameHandleClick = (() => {
|
|
|
1944
2156
|
const deviceInfo = getDeviceInfo();
|
|
1945
2157
|
return deviceInfo.isPlatform('desktop') ? 'click' : 'touchstart';
|
|
1946
2158
|
})();
|
|
2159
|
+
/**
|
|
2160
|
+
* Chuyển đổi một chuỗi HTML thành đối tượng Document.
|
|
2161
|
+
* @param htmlStr Chuỗi HTML cần parse
|
|
2162
|
+
* @returns Đối tượng Document tương ứng
|
|
2163
|
+
*/
|
|
1947
2164
|
const getDocumentByString = (htmlStr) => {
|
|
1948
2165
|
if (!parser) {
|
|
1949
2166
|
parser = new DOMParser();
|
|
1950
2167
|
}
|
|
1951
2168
|
return parser.parseFromString(htmlStr, 'text/html');
|
|
1952
2169
|
};
|
|
2170
|
+
/**
|
|
2171
|
+
* Sao chép các thuộc tính cơ bản của IBoundingClientRect.
|
|
2172
|
+
* @param rect Đối tượng IBoundingClientRect gốc
|
|
2173
|
+
* @returns Đối tượng IBoundingClientRect đã được copy
|
|
2174
|
+
*/
|
|
1953
2175
|
const cloneIBoundingClientRect = (rect) => {
|
|
1954
2176
|
return {
|
|
1955
2177
|
top: rect['top'],
|
|
@@ -1959,13 +2181,28 @@ const cloneIBoundingClientRect = (rect) => {
|
|
|
1959
2181
|
bottom: rect['bottom'],
|
|
1960
2182
|
};
|
|
1961
2183
|
};
|
|
2184
|
+
/**
|
|
2185
|
+
* Gán nhiều thuộc tính CSS style cho một Element.
|
|
2186
|
+
* @param el Phần tử HTML cần gán style
|
|
2187
|
+
* @param styles Đối tượng chứa các style (ví dụ: { color: 'red', display: 'block' })
|
|
2188
|
+
*/
|
|
1962
2189
|
const setStylesElement = (el, styles) => {
|
|
1963
2190
|
Object.keys(styles).forEach((key) => set(el, `style.${key}`, styles[key]));
|
|
1964
2191
|
};
|
|
2192
|
+
/**
|
|
2193
|
+
* Lấy kích thước Viewport hiển thị của cửa sổ trình duyệt.
|
|
2194
|
+
* @param win Đối tượng Window (mặc định là window)
|
|
2195
|
+
* @returns Đối tượng chứa width và height
|
|
2196
|
+
*/
|
|
1965
2197
|
const getViewport = (win = window) => {
|
|
1966
2198
|
const doc = win.document.documentElement, body = win.document.getElementsByTagName('body')[0], w = win.innerWidth || doc.clientWidth || body.clientWidth, h = win.innerHeight || doc.clientHeight || body.clientHeight;
|
|
1967
2199
|
return { width: w, height: h };
|
|
1968
2200
|
};
|
|
2201
|
+
/**
|
|
2202
|
+
* Thiết lập vị trí con trỏ (caret) cho thẻ Input hoặc Textarea.
|
|
2203
|
+
* @param element Thẻ input hoặc textarea
|
|
2204
|
+
* @param position Vị trí cần đặt con trỏ
|
|
2205
|
+
*/
|
|
1969
2206
|
const setCaretPosition = (element, position) => {
|
|
1970
2207
|
if (!element || !element.setSelectionRange) {
|
|
1971
2208
|
return;
|
|
@@ -1973,6 +2210,14 @@ const setCaretPosition = (element, position) => {
|
|
|
1973
2210
|
element.focus();
|
|
1974
2211
|
element.setSelectionRange(position, position);
|
|
1975
2212
|
};
|
|
2213
|
+
/**
|
|
2214
|
+
* Kiểm tra xem một phần tử có đang hiển thị trong vùng nhìn thấy (viewport) của một container không.
|
|
2215
|
+
* @param container Element cha chứa vùng scroll
|
|
2216
|
+
* @param element Element cần kiểm tra
|
|
2217
|
+
* @param elementScroll (Tùy chọn) Element đại diện cho vùng scroll nếu khác container
|
|
2218
|
+
* @param maxTopLeft (Tùy chọn) Offset bổ sung để kiểm tra
|
|
2219
|
+
* @returns true nếu nằm trong vùng nhìn thấy
|
|
2220
|
+
*/
|
|
1976
2221
|
const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
|
|
1977
2222
|
if (!container || !element) {
|
|
1978
2223
|
return false;
|
|
@@ -2000,6 +2245,13 @@ const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
|
|
|
2000
2245
|
}
|
|
2001
2246
|
return false;
|
|
2002
2247
|
};
|
|
2248
|
+
/**
|
|
2249
|
+
* Kiểm tra xem tọa độ chuột hiện tại có nằm trong vùng của một Element không.
|
|
2250
|
+
* @param mousePosition Đối tượng chứa clientX, clientY
|
|
2251
|
+
* @param element Element cần kiểm tra
|
|
2252
|
+
* @param rect (Tùy chọn) Bounding rect đã có sẵn để tối ưu hiệu năng
|
|
2253
|
+
* @returns true nếu chuột đang đè lên phần tử
|
|
2254
|
+
*/
|
|
2003
2255
|
const checkMouseOverInContainer = (mousePosition, element, rect) => {
|
|
2004
2256
|
if (!element && !rect) {
|
|
2005
2257
|
return false;
|
|
@@ -2014,6 +2266,11 @@ const checkMouseOverInContainer = (mousePosition, element, rect) => {
|
|
|
2014
2266
|
}
|
|
2015
2267
|
return false;
|
|
2016
2268
|
};
|
|
2269
|
+
/**
|
|
2270
|
+
* Tạo sự kiện Kéo-Thả (Drag) nâng cao dựa trên RxJS cho một Element.
|
|
2271
|
+
* @param config Cấu hình sự kiện (element, các hàm callback, destroyRef)
|
|
2272
|
+
* @returns Observable phát ra sự kiện mousemove trong quá trình kéo
|
|
2273
|
+
*/
|
|
2017
2274
|
const getDragEventByElement = (config) => {
|
|
2018
2275
|
let timer;
|
|
2019
2276
|
const addClass = () => {
|
|
@@ -2050,20 +2307,37 @@ const getDragEventByElement = (config) => {
|
|
|
2050
2307
|
return mouseDown.pipe(mergeMap((e) => (config.isStartWithMouseDownEvent ? mousemove.pipe(startWith(e)) : mousemove)), takeUntilDestroyed(config.destroyRef), finalize(removeClass));
|
|
2051
2308
|
};
|
|
2052
2309
|
|
|
2310
|
+
/**
|
|
2311
|
+
* Tiện ích xử lý tham số truy vấn trên URL (URL Search Parameters).
|
|
2312
|
+
* Cung cấp các phương thức để parse, thêm, xóa, lấy giá trị và so sánh các tham số.
|
|
2313
|
+
*/
|
|
2053
2314
|
class UtilsUrlSearchParams {
|
|
2054
2315
|
static instance;
|
|
2055
2316
|
params;
|
|
2317
|
+
/**
|
|
2318
|
+
* Khởi tạo UtilsUrlSearchParams từ một chuỗi query string.
|
|
2319
|
+
* @param paramString Chuỗi tham số (ví dụ: "?id=1&name=test" hoặc đơn giản là "id=1&name=test").
|
|
2320
|
+
*/
|
|
2056
2321
|
constructor(paramString) {
|
|
2057
2322
|
this.params = new Array();
|
|
2058
2323
|
this.params.length = 0;
|
|
2059
2324
|
this.params.push(...this.buildParams(paramString));
|
|
2060
2325
|
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Lấy instance duy nhất (Singleton) của UtilsUrlSearchParams.
|
|
2328
|
+
* @returns {UtilsUrlSearchParams} Instance của lớp.
|
|
2329
|
+
*/
|
|
2061
2330
|
static getInstance() {
|
|
2062
2331
|
if (!this.instance) {
|
|
2063
2332
|
this.instance = new UtilsUrlSearchParams('');
|
|
2064
2333
|
}
|
|
2065
2334
|
return this.instance;
|
|
2066
2335
|
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Chuyển đổi một đối tượng key-value thành chuỗi query string.
|
|
2338
|
+
* @param params Đối tượng chứa các tham số (ví dụ: { id: "1", name: "test" }).
|
|
2339
|
+
* @returns {string} Chuỗi query string đã được sắp xếp theo key (ví dụ: "id=1&name=test").
|
|
2340
|
+
*/
|
|
2067
2341
|
static toStringParamObject(params) {
|
|
2068
2342
|
const paramsArr = Object.keys(params).map((key) => {
|
|
2069
2343
|
return {
|
|
@@ -2073,6 +2347,12 @@ class UtilsUrlSearchParams {
|
|
|
2073
2347
|
});
|
|
2074
2348
|
return UtilsUrlSearchParams.ToString(paramsArr);
|
|
2075
2349
|
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Chuyển đổi mảng các cặp key-value thành chuỗi query string.
|
|
2352
|
+
* Các tham số sẽ được sắp xếp theo thứ tự bảng chữ cái của key.
|
|
2353
|
+
* @param params Mảng các đối tượng { key, value }.
|
|
2354
|
+
* @returns {string} Chuỗi query string.
|
|
2355
|
+
*/
|
|
2076
2356
|
static ToString(params) {
|
|
2077
2357
|
let paramsStr = '';
|
|
2078
2358
|
params
|
|
@@ -2083,6 +2363,12 @@ class UtilsUrlSearchParams {
|
|
|
2083
2363
|
});
|
|
2084
2364
|
return paramsStr;
|
|
2085
2365
|
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Phân tích chuỗi query string thành mảng các cặp key-value.
|
|
2368
|
+
* Tự động decode các giá trị và xử lý các trường hợp URL lỗi (nhiều dấu ?).
|
|
2369
|
+
* @param paramString Chuỗi cần phân tích.
|
|
2370
|
+
* @returns {Array<{ key: string; value: string }>} Mảng kết quả.
|
|
2371
|
+
*/
|
|
2086
2372
|
buildParams(paramString) {
|
|
2087
2373
|
const paramsBuild = new Array();
|
|
2088
2374
|
if (!paramString) {
|
|
@@ -2108,6 +2394,11 @@ class UtilsUrlSearchParams {
|
|
|
2108
2394
|
}
|
|
2109
2395
|
return paramsBuild;
|
|
2110
2396
|
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Thêm hoặc cập nhật giá trị của một tham số.
|
|
2399
|
+
* @param key Tên tham số.
|
|
2400
|
+
* @param value Giá trị tham số.
|
|
2401
|
+
*/
|
|
2111
2402
|
set(key, value) {
|
|
2112
2403
|
const paramByKey = this.params.find((param) => param.key === key);
|
|
2113
2404
|
if (!paramByKey) {
|
|
@@ -2116,25 +2407,51 @@ class UtilsUrlSearchParams {
|
|
|
2116
2407
|
}
|
|
2117
2408
|
paramByKey['value'] = value;
|
|
2118
2409
|
}
|
|
2410
|
+
/**
|
|
2411
|
+
* Lấy giá trị của một tham số theo key.
|
|
2412
|
+
* @param key Tên tham số cần lấy.
|
|
2413
|
+
* @returns {string | undefined} Giá trị của tham số hoặc undefined nếu không tìm thấy.
|
|
2414
|
+
*/
|
|
2119
2415
|
get(key) {
|
|
2120
2416
|
return this.params.find((param) => param.key === key)?.value;
|
|
2121
2417
|
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Kiểm tra xem một tham số có tồn tại hay không.
|
|
2420
|
+
* @param key Tên tham số cần kiểm tra.
|
|
2421
|
+
* @returns {boolean} True nếu tồn tại.
|
|
2422
|
+
*/
|
|
2122
2423
|
has(key) {
|
|
2123
2424
|
return this.params.some((param) => param.key === key);
|
|
2124
2425
|
}
|
|
2426
|
+
/**
|
|
2427
|
+
* Xóa một tham số khỏi danh sách.
|
|
2428
|
+
* @param key Tên tham số cần xóa.
|
|
2429
|
+
*/
|
|
2125
2430
|
delete(key) {
|
|
2126
2431
|
const index = this.params.findIndex((param) => param.key === key);
|
|
2127
2432
|
if (index >= 0) {
|
|
2128
2433
|
this.params.splice(index, 1);
|
|
2129
2434
|
}
|
|
2130
2435
|
}
|
|
2436
|
+
/**
|
|
2437
|
+
* @returns {number} Số lượng tham số hiện có.
|
|
2438
|
+
*/
|
|
2131
2439
|
length() {
|
|
2132
2440
|
return this.params.length;
|
|
2133
2441
|
}
|
|
2442
|
+
/**
|
|
2443
|
+
* Chuyển đổi các tham số hiện tại (hoặc mảng truyền vào) thành chuỗi query string.
|
|
2444
|
+
* @param params (Tùy chọn) Mảng tham số cần convert. Nếu không truyền sẽ dùng params nội bộ.
|
|
2445
|
+
* @returns {string} Chuỗi query string.
|
|
2446
|
+
*/
|
|
2134
2447
|
toString(params) {
|
|
2135
2448
|
params = params || this.params;
|
|
2136
2449
|
return UtilsUrlSearchParams.ToString(params);
|
|
2137
2450
|
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Chuyển đổi danh sách tham số hiện tại thành một đối tượng plain object.
|
|
2453
|
+
* @returns {Record<string, string>} Đối tượng key-value.
|
|
2454
|
+
*/
|
|
2138
2455
|
getParamObject() {
|
|
2139
2456
|
const params = {};
|
|
2140
2457
|
this.params.forEach((item) => {
|
|
@@ -2142,6 +2459,13 @@ class UtilsUrlSearchParams {
|
|
|
2142
2459
|
});
|
|
2143
2460
|
return params;
|
|
2144
2461
|
}
|
|
2462
|
+
/**
|
|
2463
|
+
* So sánh hai chuỗi query string xem chúng có chứa cùng tập tham số và giá trị hay không.
|
|
2464
|
+
* Thứ tự các tham số trong chuỗi ban đầu không quan trọng.
|
|
2465
|
+
* @param param1 Chuỗi thứ nhất.
|
|
2466
|
+
* @param param2 Chuỗi thứ hai.
|
|
2467
|
+
* @returns {boolean} True nếu giống nhau.
|
|
2468
|
+
*/
|
|
2145
2469
|
compareParams(param1, param2) {
|
|
2146
2470
|
const params1 = this.toString(this.buildParams(param1));
|
|
2147
2471
|
const params2 = this.toString(this.buildParams(param2));
|
|
@@ -2151,9 +2475,20 @@ class UtilsUrlSearchParams {
|
|
|
2151
2475
|
|
|
2152
2476
|
const step = 20;
|
|
2153
2477
|
const percent = 0.05;
|
|
2478
|
+
/**
|
|
2479
|
+
* Lấy cụ thể một shade hoặc tint của màu dựa trên bước (step)
|
|
2480
|
+
* @param color Mã màu HEX (3 hoặc 6 ký tự)
|
|
2481
|
+
* @param stepNumber Bước màu cần lấy (5, 10, 15... 100)
|
|
2482
|
+
* @returns Đối tượng chứa thông tin bước màu và mã HEX light/dark
|
|
2483
|
+
*/
|
|
2154
2484
|
const colorStepContrastFromOrigin = (color, stepNumber) => {
|
|
2155
2485
|
return colorContrastFromOrigin(color).find((item) => item.step === stepNumber);
|
|
2156
2486
|
};
|
|
2487
|
+
/**
|
|
2488
|
+
* Tạo ra danh sách các sắc độ (shades & tints) từ một màu gốc
|
|
2489
|
+
* @param color Mã màu HEX
|
|
2490
|
+
* @returns Mảng các đối tượng chứa step, label, light hex và dark hex
|
|
2491
|
+
*/
|
|
2157
2492
|
const colorContrastFromOrigin = (color) => {
|
|
2158
2493
|
const parsedColorsArray = parseColorValues(color);
|
|
2159
2494
|
const colors = [];
|
|
@@ -2206,6 +2541,11 @@ const rgbShade = (rgb, i) => {
|
|
|
2206
2541
|
const rgbTint = (rgb, i) => {
|
|
2207
2542
|
return { red: rgb.red + (255 - rgb.red) * i * percent, green: rgb.green + (255 - rgb.green) * i * percent, blue: rgb.blue + (255 - rgb.blue) * i * percent };
|
|
2208
2543
|
};
|
|
2544
|
+
/**
|
|
2545
|
+
* Chuyển đổi đối tượng RGB sang mã HEX (không có dấu #)
|
|
2546
|
+
* @param rgb Đối tượng chứa red, green, blue
|
|
2547
|
+
* @returns Chuỗi HEX 6 ký tự
|
|
2548
|
+
*/
|
|
2209
2549
|
const rgbToHex = (rgb) => {
|
|
2210
2550
|
return intToHex(rgb.red) + intToHex(rgb.green) + intToHex(rgb.blue);
|
|
2211
2551
|
};
|
|
@@ -2222,6 +2562,9 @@ const pad = (number, length) => {
|
|
|
2222
2562
|
}
|
|
2223
2563
|
return str;
|
|
2224
2564
|
};
|
|
2565
|
+
/**
|
|
2566
|
+
* Danh sách các mã màu định nghĩa sẵn để sử dụng trong getColorById
|
|
2567
|
+
*/
|
|
2225
2568
|
const listColorDefine = [
|
|
2226
2569
|
'#E62222',
|
|
2227
2570
|
'#B81B1B',
|
|
@@ -2269,6 +2612,12 @@ const listColorDefine = [
|
|
|
2269
2612
|
'#1A1A1A',
|
|
2270
2613
|
'#4D4D4D',
|
|
2271
2614
|
];
|
|
2615
|
+
/**
|
|
2616
|
+
* Lấy một màu định nghĩa sẵn dựa trên chuỗi đầu vào (như ID người dùng, tên...)
|
|
2617
|
+
* Màu trả về là duy nhất và cố định cho cùng một chuỗi đầu vào.
|
|
2618
|
+
* @param str Chuỗi để hash lấy màu
|
|
2619
|
+
* @returns Mã màu HEX từ listColorDefine
|
|
2620
|
+
*/
|
|
2272
2621
|
const getColorById = (str) => {
|
|
2273
2622
|
let hashString = 0;
|
|
2274
2623
|
if (!str) {
|
|
@@ -2281,6 +2630,12 @@ const getColorById = (str) => {
|
|
|
2281
2630
|
}
|
|
2282
2631
|
return listColorDefine[Math.abs(hashString) % listColorDefine.length];
|
|
2283
2632
|
};
|
|
2633
|
+
/**
|
|
2634
|
+
* Loại bỏ các thuộc tính màu sắc (color, background...) "gần trắng" khỏi chuỗi style CSS
|
|
2635
|
+
* Giúp tránh việc hiển thị chữ trắng trên nền trắng khi merge style lung tung.
|
|
2636
|
+
* @param style Chuỗi style CSS (ví dụ: "color: #fff; font-size: 14px;")
|
|
2637
|
+
* @returns Chuỗi style đã được làm sạch
|
|
2638
|
+
*/
|
|
2284
2639
|
const detectAndCleanNearWhiteColors = (style) => {
|
|
2285
2640
|
if (!style)
|
|
2286
2641
|
return style;
|
|
@@ -2307,6 +2662,12 @@ const detectAndCleanNearWhiteColors = (style) => {
|
|
|
2307
2662
|
.map(([k, v]) => `${k}: ${v}`)
|
|
2308
2663
|
.join('; ');
|
|
2309
2664
|
};
|
|
2665
|
+
/**
|
|
2666
|
+
* Kiểm tra một màu có phải là "gần trắng" hay không
|
|
2667
|
+
* Dựa trên luminance > 0.95 và saturation < 0.1
|
|
2668
|
+
* @param color Mã màu (HEX, RGB, RGBA hoặc tên màu CSS)
|
|
2669
|
+
* @returns true nếu màu gần trắng
|
|
2670
|
+
*/
|
|
2310
2671
|
const isNearWhite = (color) => {
|
|
2311
2672
|
if (!color)
|
|
2312
2673
|
return false;
|
|
@@ -2505,6 +2866,17 @@ const getKeyCacheByArrayObject = (keyCache, argumentsValue) => {
|
|
|
2505
2866
|
return `${keyCacheMD5}-${md5(`${keyCacheMD5}-${keyCachePlus}`)}`;
|
|
2506
2867
|
};
|
|
2507
2868
|
|
|
2869
|
+
/**
|
|
2870
|
+
* Chuẩn hóa chuỗi số theo locale hiện tại về dạng số thực (parseable).
|
|
2871
|
+
* - Với **EN**: Xóa dấu phẩy (`,`) — dấu phân tách hàng nghìn.
|
|
2872
|
+
* - Với **VI**: Xóa dấu chấm (`.`) và đổi dấu phẩy (`,`) thành dấu chấm (`.`).
|
|
2873
|
+
*
|
|
2874
|
+
* @param value Chuỗi hoặc số cần chuẩn hóa
|
|
2875
|
+
* @returns Chuỗi số đã được chuẩn hóa (không dấu phân cách locale)
|
|
2876
|
+
* @example
|
|
2877
|
+
* // EN: "1,234,567" → "1234567"
|
|
2878
|
+
* // VI: "1.234.567,89" → "1234567.89"
|
|
2879
|
+
*/
|
|
2508
2880
|
const formatNumber = (value) => {
|
|
2509
2881
|
const lang = UtilsCache.getLang();
|
|
2510
2882
|
if (lang === UtilsLanguageConstants.EN) {
|
|
@@ -2512,9 +2884,26 @@ const formatNumber = (value) => {
|
|
|
2512
2884
|
}
|
|
2513
2885
|
return `${value}`.replace(/[.]/g, '').replace(/[,]/g, '.');
|
|
2514
2886
|
};
|
|
2887
|
+
/**
|
|
2888
|
+
* Định dạng và hiển thị số theo ngôn ngữ (VI/EN) với các tùy chọn linh hoạt.
|
|
2889
|
+
* Xử lý số âm, phần thập phân, làm tròn và dấu phân cách hàng nghìn.
|
|
2890
|
+
*
|
|
2891
|
+
* @param value Giá trị số hoặc chuỗi số đầu vào
|
|
2892
|
+
* @param acceptNegativeValue Có chấp nhận số âm không (false → trả về 0 nếu value <= 0)
|
|
2893
|
+
* @param parseFixed Số chữ số thập phân muốn giữ lại (mặc định: 1)
|
|
2894
|
+
* @param ignoreFormatSeparator Bỏ qua bước chuẩn hóa dấu phân cách (khi value đã là số thuần)
|
|
2895
|
+
* @param ignoreParseFloat Bỏ qua bước làm tròn (giữ nguyên phần thập phân)
|
|
2896
|
+
* @param lang Ngôn ngữ hiển thị ('vi' hoặc 'en'), mặc định lấy từ cache
|
|
2897
|
+
* @returns Chuỗi số đã format theo locale (vd: "1.234.567,89" hoặc "1,234,567.89")
|
|
2898
|
+
* @example
|
|
2899
|
+
* // VI, 2 chữ số thập phân
|
|
2900
|
+
* viewDataNumberByLanguage(1234567.891, true, 2) → "1.234.567,89"
|
|
2901
|
+
* // EN, 2 chữ số thập phân
|
|
2902
|
+
* viewDataNumberByLanguage(1234567.891, true, 2, false, false, 'en') → "1,234,567.89"
|
|
2903
|
+
*/
|
|
2515
2904
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2516
|
-
const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ignoreFormatSeparator = false, ignoreParseFloat = false) => {
|
|
2517
|
-
|
|
2905
|
+
const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ignoreFormatSeparator = false, ignoreParseFloat = false, lang) => {
|
|
2906
|
+
lang = lang || UtilsCache.getLang();
|
|
2518
2907
|
if (!`${value}`.trim()) {
|
|
2519
2908
|
return '';
|
|
2520
2909
|
}
|
|
@@ -2569,63 +2958,143 @@ const viewDataNumberByLanguage = (value, acceptNegativeValue, parseFixed = 1, ig
|
|
|
2569
2958
|
};
|
|
2570
2959
|
|
|
2571
2960
|
/* eslint-disable no-useless-escape */
|
|
2961
|
+
/**
|
|
2962
|
+
* Returns regex for email validation
|
|
2963
|
+
* @returns {RegExp}
|
|
2964
|
+
*/
|
|
2572
2965
|
const patternEmail = () => {
|
|
2573
2966
|
return /^[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]@[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]\.[A-z0-9]+[A-z0-9\_\.\+\-]*[A-z0-9]$/;
|
|
2574
2967
|
};
|
|
2575
|
-
|
|
2968
|
+
/**
|
|
2969
|
+
* Returns regex for protocol in URL (http, https, ftp)
|
|
2970
|
+
* @returns {RegExp}
|
|
2971
|
+
*/
|
|
2972
|
+
const patternProtocolUrl = () => {
|
|
2576
2973
|
return /(http|https|ftp):/;
|
|
2577
2974
|
};
|
|
2975
|
+
/**
|
|
2976
|
+
* Returns regex for full URL validation
|
|
2977
|
+
* @returns {RegExp}
|
|
2978
|
+
*/
|
|
2578
2979
|
const patternUrl = () => {
|
|
2579
2980
|
return /^(http|https|ftp):(\/){2}[^\s]+[.]{1}[^\s]+$/;
|
|
2580
2981
|
};
|
|
2982
|
+
/**
|
|
2983
|
+
* Returns regex for host part of URL
|
|
2984
|
+
* @returns {RegExp}
|
|
2985
|
+
*/
|
|
2581
2986
|
const patternHostUrl = () => {
|
|
2582
2987
|
return /^((https|http|ftp):[/]{2}[^/\s]+)/;
|
|
2583
2988
|
};
|
|
2989
|
+
/**
|
|
2990
|
+
* Returns regex for domain validation
|
|
2991
|
+
* @returns {RegExp}
|
|
2992
|
+
*/
|
|
2584
2993
|
const patternDomain = () => {
|
|
2585
|
-
return /^([a-zA-Z0-9])(([a-z0-9-]{1,61})?[a-z0-9]{1})?(\.[a-z0-9](([a-z0-9-]{1,61})?[a-z0-9]{1})?)?(\.[a-zA-Z]{2,
|
|
2994
|
+
return /^([a-zA-Z0-9])(([a-z0-9-]{1,61})?[a-z0-9]{1})?(\.[a-z0-9](([a-z0-9-]{1,61})?[a-z0-9]{1})?)?(\.[a-zA-Z]{2,})+$/;
|
|
2586
2995
|
};
|
|
2996
|
+
/**
|
|
2997
|
+
* Returns regex for Vietnamese mobile phone validation
|
|
2998
|
+
* @returns {RegExp}
|
|
2999
|
+
*/
|
|
2587
3000
|
const patternMobilePhone = () => {
|
|
2588
3001
|
return /^(\+?84|0|84)([0-9]{9})$/;
|
|
2589
3002
|
};
|
|
3003
|
+
/**
|
|
3004
|
+
* Returns regex for general phone number validation
|
|
3005
|
+
* @returns {RegExp}
|
|
3006
|
+
*/
|
|
2590
3007
|
const patternPhone = () => {
|
|
2591
3008
|
return /^(\+?84|[0-9]|84)([0-9]{2,9})$/;
|
|
2592
3009
|
};
|
|
3010
|
+
/**
|
|
3011
|
+
* Returns regex for digits
|
|
3012
|
+
* @returns {RegExp}
|
|
3013
|
+
*/
|
|
2593
3014
|
const patternNumber = () => {
|
|
2594
3015
|
return /\d+/g;
|
|
2595
3016
|
};
|
|
3017
|
+
/**
|
|
3018
|
+
* Returns regex for encoded URI characters
|
|
3019
|
+
* @returns {RegExp}
|
|
3020
|
+
*/
|
|
2596
3021
|
const patternEncodeUri = () => {
|
|
2597
3022
|
return /%([0-9A-F]{2})/g;
|
|
2598
3023
|
};
|
|
3024
|
+
/**
|
|
3025
|
+
* Returns regex for standard name (alphanumeric and spaces)
|
|
3026
|
+
* @returns {RegExp}
|
|
3027
|
+
*/
|
|
2599
3028
|
const patternName = () => {
|
|
2600
3029
|
return /^\w+[A-Za-z\s\d]+$/;
|
|
2601
3030
|
};
|
|
3031
|
+
/**
|
|
3032
|
+
* Returns regex for UTF-8 name (including Vietnamese characters)
|
|
3033
|
+
* @returns {RegExp}
|
|
3034
|
+
*/
|
|
2602
3035
|
const patternNameUtf8 = () => {
|
|
2603
3036
|
return /^[ àáảãạăắằẵặẳâầấậẫẩđèéẻẽẹêềếểễệìíỉĩịòóỏõọôồốổỗộơờớởỡợùúủũụưừứửữựỳýỷỹÀÁẢÃẠĂẮẰẴẶẲÂẦẤẬẪẨĐÈÉẺẼẸÊỀẾỂỄỆÌÍỈĨỊÒÓỎÕỌÔỒỐỔỖỘƠỜỚỞỠỢÙÚỦŨỤƯỪỨỬỮỰỲÝỶỸA-Za-z0-9]+$/;
|
|
2604
3037
|
};
|
|
3038
|
+
/**
|
|
3039
|
+
* Returns regex for special characters in name
|
|
3040
|
+
* @returns {RegExp}
|
|
3041
|
+
*/
|
|
2605
3042
|
const patternNameSpecial = () => {
|
|
2606
3043
|
return /[~!@#$%^&*()-+=<>,?\/\\:;"']/;
|
|
2607
3044
|
};
|
|
3045
|
+
/**
|
|
3046
|
+
* Returns regex for profile name
|
|
3047
|
+
* @returns {RegExp}
|
|
3048
|
+
*/
|
|
2608
3049
|
const patternNameProfile = () => {
|
|
2609
3050
|
return /([\w\W\d\s]+)+/;
|
|
2610
3051
|
};
|
|
3052
|
+
/**
|
|
3053
|
+
* Returns regex for emojis
|
|
3054
|
+
* @returns {RegExp}
|
|
3055
|
+
*/
|
|
2611
3056
|
const patternEmoji = () => {
|
|
2612
3057
|
return /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
|
|
2613
3058
|
};
|
|
3059
|
+
/**
|
|
3060
|
+
* Returns regex for matching field replacement markers (e.g. {{field}})
|
|
3061
|
+
* @returns {RegExp}
|
|
3062
|
+
*/
|
|
2614
3063
|
const patternRuleFieldReplace = () => {
|
|
2615
3064
|
return /[{]{2}[a-zA-Z_-]+[}]{2}/g;
|
|
2616
3065
|
};
|
|
3066
|
+
/**
|
|
3067
|
+
* Returns regex for extracting field name from replacement markers
|
|
3068
|
+
* @returns {RegExp}
|
|
3069
|
+
*/
|
|
2617
3070
|
const patternGetFieldByRuleFieldReplace = () => {
|
|
2618
3071
|
return /[a-zA-Z_-]+/g;
|
|
2619
3072
|
};
|
|
3073
|
+
/**
|
|
3074
|
+
* Returns regex for PEM format validation
|
|
3075
|
+
* @returns {RegExp}
|
|
3076
|
+
*/
|
|
2620
3077
|
const patternPem = () => {
|
|
2621
3078
|
return /^(0|1):([0-9]{1,2}):(\{\{path-api\}\}):([a-zA-Z0-9\/]+)$/g;
|
|
2622
3079
|
};
|
|
3080
|
+
/**
|
|
3081
|
+
* Returns regex for tax ID validation
|
|
3082
|
+
* @returns {RegExp}
|
|
3083
|
+
*/
|
|
2623
3084
|
const patternTax = () => {
|
|
2624
3085
|
return /^([0-9]){10}(-[0-9]{3})?$/;
|
|
2625
3086
|
};
|
|
3087
|
+
/**
|
|
3088
|
+
* Returns regex for key validation (identical to tax regex)
|
|
3089
|
+
* @returns {RegExp}
|
|
3090
|
+
*/
|
|
2626
3091
|
const patternKey = () => {
|
|
2627
3092
|
return /^([0-9]){10}(-[0-9]{3})?$/;
|
|
2628
3093
|
};
|
|
3094
|
+
/**
|
|
3095
|
+
* Returns regex for account validation (must contain @)
|
|
3096
|
+
* @returns {RegExp}
|
|
3097
|
+
*/
|
|
2629
3098
|
const patternAccount = () => {
|
|
2630
3099
|
return /^(?=.*@)[a-z0-9@._-]{2,63}$/;
|
|
2631
3100
|
};
|
|
@@ -2633,14 +3102,33 @@ const patternAccount = () => {
|
|
|
2633
3102
|
let functionXssFilter = async (value) => {
|
|
2634
3103
|
return value;
|
|
2635
3104
|
};
|
|
3105
|
+
/**
|
|
3106
|
+
* Cập nhật hàm lọc XSS.
|
|
3107
|
+
* Cho phép ghi đè hàm xử lý XSS mặc định (trả về nguyên gốc) bằng một implementation bảo mật do project cung cấp (ví dụ: dùng DOMPurify).
|
|
3108
|
+
*
|
|
3109
|
+
* @param functionCustom Hàm xử lý XSS tùy chỉnh cần cập nhật.
|
|
3110
|
+
*/
|
|
2636
3111
|
const updateFunctionXssFilter = (functionCustom) => {
|
|
2637
3112
|
functionXssFilter = functionCustom;
|
|
2638
3113
|
};
|
|
3114
|
+
/**
|
|
3115
|
+
* Lọc dữ liệu thông qua hàm lọc XSS đã được định cấu hình.
|
|
3116
|
+
* Theo mặc định, hàm này sẽ trả lại chuỗi ban đầu. Nếu bạn đã cung cấp một custom filter qua `updateFunctionXssFilter`, hàm đó sẽ được sử dụng.
|
|
3117
|
+
*
|
|
3118
|
+
* @param data Chuỗi dữ liệu đầu vào.
|
|
3119
|
+
* @returns {Promise<string>} Promise chứa kết quả đã được lọc XSS.
|
|
3120
|
+
*/
|
|
2639
3121
|
const xssFilter = async (data) => {
|
|
2640
3122
|
return await functionXssFilter(data);
|
|
2641
3123
|
};
|
|
2642
3124
|
|
|
2643
3125
|
const ENCODE_URI_PATTERN = /%([0-9A-F]{2})/g;
|
|
3126
|
+
/**
|
|
3127
|
+
* Giải mã chuỗi URI một cách an toàn.
|
|
3128
|
+
*
|
|
3129
|
+
* @param value Chuỗi cần giải mã.
|
|
3130
|
+
* @returns Chuỗi đã được giải mã.
|
|
3131
|
+
*/
|
|
2644
3132
|
const decodeURI = (value) => {
|
|
2645
3133
|
return decodeURIComponent(value
|
|
2646
3134
|
.split('')
|
|
@@ -2649,11 +3137,25 @@ const decodeURI = (value) => {
|
|
|
2649
3137
|
})
|
|
2650
3138
|
.join(''));
|
|
2651
3139
|
};
|
|
3140
|
+
/**
|
|
3141
|
+
* Mã hóa chuỗi URI.
|
|
3142
|
+
*
|
|
3143
|
+
* @param value Chuỗi cần mã hóa.
|
|
3144
|
+
* @returns Chuỗi đã được mã hóa.
|
|
3145
|
+
*/
|
|
2652
3146
|
const encodeURI = (value) => {
|
|
2653
3147
|
return encodeURIComponent(value).replace(ENCODE_URI_PATTERN, (match, p1) => {
|
|
2654
3148
|
return String.fromCharCode(parseInt(p1, 16));
|
|
2655
3149
|
});
|
|
2656
3150
|
};
|
|
3151
|
+
/**
|
|
3152
|
+
* Chuyển đổi đối tượng parameters thành chuỗi query string hoặc body string.
|
|
3153
|
+
* Tự động loại bỏ các giá trị rỗng, null, hoặc undefined.
|
|
3154
|
+
*
|
|
3155
|
+
* @param params Đối tượng chứa các tham số.
|
|
3156
|
+
* @param isBody Nếu true, trả về chuỗi không có dấu '?' ở đầu.
|
|
3157
|
+
* @returns Chuỗi tham số đã được mã hóa.
|
|
3158
|
+
*/
|
|
2657
3159
|
const endCodeUrl = (params, isBody) => {
|
|
2658
3160
|
params = omitBy(params, (param) => param === '' || isNil(param));
|
|
2659
3161
|
let res = '';
|
|
@@ -2663,12 +3165,27 @@ const endCodeUrl = (params, isBody) => {
|
|
|
2663
3165
|
return res === '' ? '' : `${isBody ? '' : '?'}${res.substring(1)}`;
|
|
2664
3166
|
};
|
|
2665
3167
|
|
|
3168
|
+
/**
|
|
3169
|
+
* Mã hóa chuỗi thành Base64 (có hỗ trợ ký tự đặc biệt qua encodeURI)
|
|
3170
|
+
* @param value Chuỗi cần mã hóa
|
|
3171
|
+
* @returns Chuỗi đã mã hóa Base64
|
|
3172
|
+
*/
|
|
2666
3173
|
const base64Encode = (value) => {
|
|
2667
3174
|
return btoa(encodeURI(value));
|
|
2668
3175
|
};
|
|
3176
|
+
/**
|
|
3177
|
+
* Giải mã chuỗi Base64 (có hỗ trợ ký tự đặc biệt qua decodeURI)
|
|
3178
|
+
* @param value Chuỗi Base64 cần giải mã
|
|
3179
|
+
* @returns Chuỗi đã giải mã
|
|
3180
|
+
*/
|
|
2669
3181
|
const base64Decode = (value) => {
|
|
2670
3182
|
return decodeURI(atob(value));
|
|
2671
3183
|
};
|
|
3184
|
+
/**
|
|
3185
|
+
* Chuyển đổi dữ liệu Base64 thành Blob
|
|
3186
|
+
* @param dataBase64 Dữ liệu Base64 (có thể bao gồm marker ;base64,)
|
|
3187
|
+
* @returns Blob tương ứng
|
|
3188
|
+
*/
|
|
2672
3189
|
const convertBase64ToBlob = (dataBase64) => {
|
|
2673
3190
|
const BASE64_MARKER = ';base64,';
|
|
2674
3191
|
if (dataBase64.indexOf(BASE64_MARKER) === -1) {
|
|
@@ -2689,12 +3206,22 @@ const convertBase64ToBlob = (dataBase64) => {
|
|
|
2689
3206
|
const blob = new Blob([uInt8Array], { type: contentType });
|
|
2690
3207
|
return blob;
|
|
2691
3208
|
};
|
|
3209
|
+
/**
|
|
3210
|
+
* Chuyển đổi File thành Base64 string hoặc Object URL tùy loại file
|
|
3211
|
+
* @param file Đối tượng File cần chuyển đổi
|
|
3212
|
+
* @returns Nếu là ảnh, trả về Base64 string. Ngược lại trả về Object URL.
|
|
3213
|
+
*/
|
|
2692
3214
|
const convertFileToBase64_ObjectUrl = async (file) => {
|
|
2693
3215
|
if (!file.type.match(/image.*/)) {
|
|
2694
3216
|
return URL.createObjectURL(file);
|
|
2695
3217
|
}
|
|
2696
3218
|
return await convertFileToBase64(file);
|
|
2697
3219
|
};
|
|
3220
|
+
/**
|
|
3221
|
+
* Chuyển đổi File thành Base64 string
|
|
3222
|
+
* @param file Đối tượng File cần chuyển đổi
|
|
3223
|
+
* @returns Promise chứa chuỗi Base64
|
|
3224
|
+
*/
|
|
2698
3225
|
const convertFileToBase64 = (file) => {
|
|
2699
3226
|
return new Promise((resolve) => {
|
|
2700
3227
|
const reader = new FileReader();
|
|
@@ -2705,6 +3232,13 @@ const convertFileToBase64 = (file) => {
|
|
|
2705
3232
|
});
|
|
2706
3233
|
};
|
|
2707
3234
|
|
|
3235
|
+
/**
|
|
3236
|
+
* Tải file từ URL bằng XMLHttpRequest.
|
|
3237
|
+
* Khác với `downloadFileByUrl`, hàm này sử dụng XHR để tải file, hữu ích hơn
|
|
3238
|
+
* trong các trường hợp cần header tùy chỉnh hoặc gọi API nội bộ.
|
|
3239
|
+
* @param fileUrl URL của file cần tải
|
|
3240
|
+
* @param filename Tên file sẽ được lưu về máy
|
|
3241
|
+
*/
|
|
2708
3242
|
const downloadFileByUrlUseXmlRequest = (fileUrl, filename) => {
|
|
2709
3243
|
const xmlRequest = new XMLHttpRequest();
|
|
2710
3244
|
xmlRequest.open('GET', fileUrl, true);
|
|
@@ -2719,6 +3253,13 @@ const downloadFileByUrlUseXmlRequest = (fileUrl, filename) => {
|
|
|
2719
3253
|
};
|
|
2720
3254
|
xmlRequest.send();
|
|
2721
3255
|
};
|
|
3256
|
+
/**
|
|
3257
|
+
* Tải file từ URL bằng cách tạo thẻ `<a>` ảo và kích hoạt sự kiện click.
|
|
3258
|
+
* Hỗ trợ chế độ mở file trên tab mới (onlyOpen) hoặc tải xuống thực sự.
|
|
3259
|
+
* @param fileUrl URL trực tiếp của file hoặc Blob URL
|
|
3260
|
+
* @param filename Tên file sẽ được lưu khi tải về
|
|
3261
|
+
* @param onlyOpen Nếu `true`, chỉ mở file trong tab mới thay vì tải về
|
|
3262
|
+
*/
|
|
2722
3263
|
const downloadFileByUrl = async (fileUrl, filename, onlyOpen) => {
|
|
2723
3264
|
const downloadFileElement = document.createElement('a');
|
|
2724
3265
|
if (!onlyOpen) {
|
|
@@ -2734,6 +3275,13 @@ const downloadFileByUrl = async (fileUrl, filename, onlyOpen) => {
|
|
|
2734
3275
|
downloadFileElement.click();
|
|
2735
3276
|
downloadFileElement.remove();
|
|
2736
3277
|
};
|
|
3278
|
+
/**
|
|
3279
|
+
* Tải ảnh từ một phần tử `<img>` dựa trên thuộc tính `src` (hỗ trợ Base64 Data URL).
|
|
3280
|
+
* Thường dùng để cho phép người dùng lưu ảnh barcode, QR code về máy.
|
|
3281
|
+
* @param imageElement Phần tử `<img>` cần lấy ảnh
|
|
3282
|
+
* @param typeFileDownload Loại MIME của file (mặc định: 'image/png')
|
|
3283
|
+
* @param nameFile Tên file sẽ được lưu (mặc định: 'barcode')
|
|
3284
|
+
*/
|
|
2737
3285
|
const downloadImageFromELement = (imageElement, typeFileDownload, nameFile) => {
|
|
2738
3286
|
const parentElement = imageElement?.src;
|
|
2739
3287
|
const blobData = convertBase64ToBlob(parentElement);
|
|
@@ -2750,11 +3298,17 @@ const LINK_IMAGE_ERROR_TOKEN_INJECT = new InjectionToken('LINK_IMAGE_ERROR_TOKEN
|
|
|
2750
3298
|
const PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT');
|
|
2751
3299
|
const PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT');
|
|
2752
3300
|
|
|
3301
|
+
/** Kiểm tra file có phải là ảnh không (image/*). */
|
|
2753
3302
|
const isTypeImage = (file) => (file.type.match(/image.*/) ? true : false);
|
|
3303
|
+
/** Kiểm tra file có phải là video không (video/*). */
|
|
2754
3304
|
const isTypeVideo = (file) => (file.type.match(/video.*/) ? true : false);
|
|
3305
|
+
/** Kiểm tra file có phải là audio không (audio/*). */
|
|
2755
3306
|
const isTypeAudio = (file) => (file.type.match(/audio.*/) ? true : false);
|
|
3307
|
+
/** Kiểm tra giá trị có phải là đối tượng File không. */
|
|
2756
3308
|
const isTypeFile = (file) => (file instanceof File || Object.prototype.toString.call(file) === '[object File]' ? true : false);
|
|
3309
|
+
/** Danh sách extension/MIME type của file Excel. */
|
|
2757
3310
|
const ExcelExtList = ['xls', 'xlsx', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'];
|
|
3311
|
+
/** Danh sách extension/MIME type của tài liệu (Word, Excel, PDF, JSON...). */
|
|
2758
3312
|
const DocumentExtList = [
|
|
2759
3313
|
'doc',
|
|
2760
3314
|
'docx',
|
|
@@ -2775,13 +3329,40 @@ const DocumentExtList = [
|
|
|
2775
3329
|
'application/json',
|
|
2776
3330
|
'application/xml',
|
|
2777
3331
|
];
|
|
3332
|
+
/**
|
|
3333
|
+
* Kiểm tra extension có thuộc danh sách tài liệu được chấp nhận không.
|
|
3334
|
+
* @param ext Extension hoặc MIME type cần kiểm tra
|
|
3335
|
+
* @param listExt Danh sách tùy chỉnh (mặc định là DocumentExtList)
|
|
3336
|
+
*/
|
|
2778
3337
|
const isIncludeDocumentExtList = (ext, listExt = DocumentExtList) => listExt.includes(ext) || listExt.includes(`application/${ext}`);
|
|
3338
|
+
/** Danh sách extension/MIME type của ảnh. */
|
|
2779
3339
|
const ImageExtList = ['gif', 'jpg', 'jpeg', 'png', 'image/gif', 'image/jpeg', 'image/jpeg', 'image/png', 'webp'];
|
|
3340
|
+
/**
|
|
3341
|
+
* Kiểm tra extension có thuộc danh sách ảnh không.
|
|
3342
|
+
* @param ext Extension hoặc MIME type cần kiểm tra
|
|
3343
|
+
* @param listExt Danh sách tùy chỉnh (mặc định là ImageExtList)
|
|
3344
|
+
*/
|
|
2780
3345
|
const isIncludeImageExtList = (ext, listExt = ImageExtList) => listExt.includes(ext);
|
|
3346
|
+
/** Danh sách extension/MIME type của video. */
|
|
2781
3347
|
const VideoExtList = ['mp4', 'mov', 'mpg', 'avi', 'wmv', 'video/mp4', 'video/quicktime', 'video/mpeg', 'video/x-msvideo', 'video/x-ms-wmv'];
|
|
3348
|
+
/**
|
|
3349
|
+
* Kiểm tra extension có thuộc danh sách video không.
|
|
3350
|
+
* @param ext Extension hoặc MIME type cần kiểm tra
|
|
3351
|
+
*/
|
|
2782
3352
|
const isIncludeVideoExtList = (ext, listExt = VideoExtList) => listExt.includes(ext);
|
|
3353
|
+
/** Danh sách extension/MIME type của audio. */
|
|
2783
3354
|
const AudioExtList = ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'wma', 'audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/aac', 'audio/mp4', 'audio/flac', 'audio/x-ms-wma'];
|
|
3355
|
+
/**
|
|
3356
|
+
* Kiểm tra extension có thuộc danh sách audio không.
|
|
3357
|
+
* @param ext Extension hoặc MIME type cần kiểm tra
|
|
3358
|
+
*/
|
|
2784
3359
|
const isIncludeAudioExtList = (ext, listExt = AudioExtList) => listExt.includes(ext);
|
|
3360
|
+
/**
|
|
3361
|
+
* Lấy phần mở rộng (extension) của một file.
|
|
3362
|
+
* Hỗ trợ cả đối tượng File gốc của Browser và IFile (interface nội bộ).
|
|
3363
|
+
* @param file Đối tượng File hoặc IFile
|
|
3364
|
+
* @returns Extension dạng chữ thường (vd: "jpg", "pdf") hoặc undefined
|
|
3365
|
+
*/
|
|
2785
3366
|
const getFileExtension = (file) => {
|
|
2786
3367
|
if (file instanceof File && file.type) {
|
|
2787
3368
|
return (file.type.split('/').pop() || '').toLowerCase();
|
|
@@ -2805,17 +3386,37 @@ const getFileExtension = (file) => {
|
|
|
2805
3386
|
}
|
|
2806
3387
|
return (dots.pop() || '').toLowerCase();
|
|
2807
3388
|
};
|
|
3389
|
+
/**
|
|
3390
|
+
* Định dạng kích thước file dạng chuỗi dễ đọc (KB hoặc MB).
|
|
3391
|
+
* @param size Kích thước file tính bằng byte
|
|
3392
|
+
* @param toFixed Số chữ số thập phân (mặc định: 2)
|
|
3393
|
+
* @returns Chuỗi định dạng (vd: " 1.50 MB", " 512.00 KB")
|
|
3394
|
+
*/
|
|
2808
3395
|
const getLabelBySizeFile = (size, toFixed = 2) => {
|
|
2809
3396
|
if (size < 1024 * 1024) {
|
|
2810
3397
|
return ` ${(size / 1024).toFixed(toFixed)} KB`;
|
|
2811
3398
|
}
|
|
2812
3399
|
return ` ${(size / (1024 * 1024)).toFixed(toFixed)} MB`;
|
|
2813
3400
|
};
|
|
3401
|
+
/**
|
|
3402
|
+
* Chuyển đổi đối tượng Blob thành File.
|
|
3403
|
+
* Tự động xác định phần mở rộng từ MIME type của Blob.
|
|
3404
|
+
* @param blob Đối tượng Blob cần chuyển
|
|
3405
|
+
* @param fileName Tên file mong muốn (mặc định: UUID ngẫu nhiên)
|
|
3406
|
+
* @returns Đối tượng File tương ứng
|
|
3407
|
+
*/
|
|
2814
3408
|
const convertBlobToFile = (blob, fileName) => {
|
|
2815
3409
|
const type = blob.type.split('/')[1];
|
|
2816
3410
|
const name = fileName ? `${fileName.split('.')[0]}.${type}` : `${uuid()}.${type}`;
|
|
2817
3411
|
return new File([blob], name, { type: blob.type, lastModified: Date.now() });
|
|
2818
3412
|
};
|
|
3413
|
+
/**
|
|
3414
|
+
* Tải file từ URL và chuyển về đối tượng File.
|
|
3415
|
+
* Sử dụng XHR + FileReader để đọc nội dung, sau đó chuyển Base64 → Blob → File.
|
|
3416
|
+
* @param url URL của file cần tải
|
|
3417
|
+
* @param fileName Tên file mong muốn (mặc định: timestamp)
|
|
3418
|
+
* @returns Promise trả về File hoặc undefined nếu thất bại
|
|
3419
|
+
*/
|
|
2819
3420
|
const convertUrlToFile = (url, fileName) => {
|
|
2820
3421
|
return new Promise((resolve, reject) => {
|
|
2821
3422
|
const xhr = new XMLHttpRequest();
|
|
@@ -2837,6 +3438,16 @@ const convertUrlToFile = (url, fileName) => {
|
|
|
2837
3438
|
});
|
|
2838
3439
|
};
|
|
2839
3440
|
|
|
3441
|
+
/**
|
|
3442
|
+
* Highlight một từ khóa tìm kiếm bên trong chuỗi văn bản bằng cách bọc vào thẻ `<span>`.
|
|
3443
|
+
* Hỗ trợ tìm kiếm không phân biệt dấu (Unicode-insensitive) và không phân biệt hoa/thường.
|
|
3444
|
+
*
|
|
3445
|
+
* @param value Chuỗi văn bản gốc cần highlight
|
|
3446
|
+
* @param search Từ khóa cần tìm và highlight
|
|
3447
|
+
* @param ignoreHighlight Nếu `true`, bỏ qua highlight và trả về chuỗi gốc
|
|
3448
|
+
* @param classHightLight Class CSS áp dụng cho span highlight (mặc định: 'bg-[#19344a] text-white')
|
|
3449
|
+
* @returns Chuỗi HTML đã được inject thẻ `<span>` quanh từ khóa
|
|
3450
|
+
*/
|
|
2840
3451
|
const highlightByKeyword = (value, search, ignoreHighlight, classHightLight) => {
|
|
2841
3452
|
if (!value) {
|
|
2842
3453
|
return CHARACTER_DATA_EMPTY;
|
|
@@ -2877,6 +3488,14 @@ const getValueReplace = (search, value) => {
|
|
|
2877
3488
|
}
|
|
2878
3489
|
return Array.from(keys);
|
|
2879
3490
|
};
|
|
3491
|
+
/**
|
|
3492
|
+
* Chuẩn hóa chuỗi văn bản theo chuẩn Unicode NFC và áp dụng các tùy chọn format.
|
|
3493
|
+
* Thường dùng để so sánh chuỗi nhất quán, không phụ thuộc vào cách gõ có dấu.
|
|
3494
|
+
*
|
|
3495
|
+
* @param text Chuỗi cần chuẩn hóa
|
|
3496
|
+
* @param options Các tùy chọn transform (trim, removeEmoji, removeUnicode...)
|
|
3497
|
+
* @returns Chuỗi đã chuẩn hóa
|
|
3498
|
+
*/
|
|
2880
3499
|
const formatTextCompare = (text, options) => {
|
|
2881
3500
|
if (!text) {
|
|
2882
3501
|
return text;
|
|
@@ -2884,12 +3503,25 @@ const formatTextCompare = (text, options) => {
|
|
|
2884
3503
|
text = text.normalize('NFC');
|
|
2885
3504
|
return formatByOptions(text, options);
|
|
2886
3505
|
};
|
|
3506
|
+
/**
|
|
3507
|
+
* Định dạng họ tên đầy đủ: viết hoa chữ cái đầu mỗi từ, xóa khoảng trắng thừa và emoji.
|
|
3508
|
+
* @param value Chuỗi họ tên cần format
|
|
3509
|
+
* @returns Chuỗi đã được chuẩn hóa
|
|
3510
|
+
* @example fullNameFormat(' nguyễn văn a ') → 'Nguyễn Văn A'
|
|
3511
|
+
*/
|
|
2887
3512
|
const fullNameFormat = (value) => {
|
|
2888
3513
|
if (!value) {
|
|
2889
3514
|
return value;
|
|
2890
3515
|
}
|
|
2891
3516
|
return capitalize(value, { lowercaseOtherCharacter: true, trim: true, removeMultipleSpace: true, removeEmoji: true });
|
|
2892
3517
|
};
|
|
3518
|
+
/**
|
|
3519
|
+
* Viết hoa chữ cái đầu tiên của mỗi từ trong chuỗi.
|
|
3520
|
+
* @param text Chuỗi cần capitalize
|
|
3521
|
+
* @param options Tùy chọn xử lý trước (lowercase, trim, removeEmoji...)
|
|
3522
|
+
* @returns Chuỗi đã được capitalize theo từng từ
|
|
3523
|
+
* @example capitalize('hello world') → 'Hello World'
|
|
3524
|
+
*/
|
|
2893
3525
|
const capitalize = (text, options) => {
|
|
2894
3526
|
if (!text) {
|
|
2895
3527
|
return text;
|
|
@@ -2900,12 +3532,26 @@ const capitalize = (text, options) => {
|
|
|
2900
3532
|
.map((word) => firstLetterToUpperCase(word))
|
|
2901
3533
|
.join(' ');
|
|
2902
3534
|
};
|
|
3535
|
+
/**
|
|
3536
|
+
* Viết hoa chữ cái đầu tiên của chuỗi.
|
|
3537
|
+
* @param text Chuỗi cần xử lý
|
|
3538
|
+
* @param options Tùy chọn format bổ sung
|
|
3539
|
+
* @returns Chuỗi với chữ cái đầu tiên được viết hoa
|
|
3540
|
+
* @example firstLetterToUpperCase('hello') → 'Hello'
|
|
3541
|
+
*/
|
|
2903
3542
|
const firstLetterToUpperCase = (text, options) => {
|
|
2904
3543
|
if (!text) {
|
|
2905
3544
|
return text;
|
|
2906
3545
|
}
|
|
2907
3546
|
return uppercaseByPosition(text, 0, options);
|
|
2908
3547
|
};
|
|
3548
|
+
/**
|
|
3549
|
+
* Viết hoa một ký tự tại vị trí bất kỳ trong chuỗi.
|
|
3550
|
+
* @param text Chuỗi cần xử lý
|
|
3551
|
+
* @param position Vị trí ký tự cần viết hoa (0-indexed)
|
|
3552
|
+
* @param options Tùy chọn format bổ sung
|
|
3553
|
+
* @returns Chuỗi với ký tự tại vị trí được viết hoa
|
|
3554
|
+
*/
|
|
2909
3555
|
const uppercaseByPosition = (text, position, options) => {
|
|
2910
3556
|
if (!text) {
|
|
2911
3557
|
return text;
|
|
@@ -2938,12 +3584,25 @@ const formatByOptions = (text, options) => {
|
|
|
2938
3584
|
}
|
|
2939
3585
|
return text;
|
|
2940
3586
|
};
|
|
3587
|
+
/**
|
|
3588
|
+
* Escape các ký tự đặc biệt HTML thành HTML entities để tránh XSS.
|
|
3589
|
+
* @param str Chuỗi cần escape
|
|
3590
|
+
* @returns Chuỗi an toàn (& → &, < → <, > → >, " → ", ' → ')
|
|
3591
|
+
* @example escapeHtml('<script>') → '<script>'
|
|
3592
|
+
*/
|
|
2941
3593
|
const escapeHtml = (str) => {
|
|
2942
3594
|
if (!str || typeof str !== 'string') {
|
|
2943
3595
|
return str;
|
|
2944
3596
|
}
|
|
2945
3597
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
2946
3598
|
};
|
|
3599
|
+
/**
|
|
3600
|
+
* Giải mã chuỗi HTML entities về dạng ký tự gốc.
|
|
3601
|
+
* Sử dụng thẻ textarea để giải mã an toàn, hỗ trợ multi-level encoding.
|
|
3602
|
+
* @param str Chuỗi HTML encoded cần giải mã
|
|
3603
|
+
* @returns Chuỗi gốc sau khi decode
|
|
3604
|
+
* @example decodeEscapeHtml('<b>') → '<b>'
|
|
3605
|
+
*/
|
|
2947
3606
|
const decodeEscapeHtml = (str) => {
|
|
2948
3607
|
const htmlTag = document.createElement('textarea');
|
|
2949
3608
|
htmlTag.innerHTML = str;
|
|
@@ -2958,9 +3617,15 @@ const decodeEscapeHtml = (str) => {
|
|
|
2958
3617
|
}
|
|
2959
3618
|
return str;
|
|
2960
3619
|
};
|
|
3620
|
+
/**
|
|
3621
|
+
* Xóa dấu tiếng Việt khỏi chuỗi, chuyển tất cả ký tự có dấu về dạng Latin cơ bản.
|
|
3622
|
+
* Hỗ trợ cả chữ thường lẫn hoa.
|
|
3623
|
+
* @param str Chuỗi tiếng Việt cần xóa dấu
|
|
3624
|
+
* @returns Chuỗi đã xóa dấu (vd: 'Nguyễn Văn An' → 'Nguyen Van An')
|
|
3625
|
+
*/
|
|
2961
3626
|
const deleteUnicode = (str) => {
|
|
2962
3627
|
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
|
|
2963
|
-
str = str.replace(
|
|
3628
|
+
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ|ễ/g, 'e');
|
|
2964
3629
|
str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
|
|
2965
3630
|
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
|
|
2966
3631
|
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
|
|
@@ -2977,6 +3642,13 @@ const deleteUnicode = (str) => {
|
|
|
2977
3642
|
str = str.replace(/\u02C6|\u0306|\u031B/g, ''); // ˆ ̆ ̛ Â, Ê, Ă, Ơ, Ư
|
|
2978
3643
|
return str.normalize('NFC');
|
|
2979
3644
|
};
|
|
3645
|
+
/**
|
|
3646
|
+
* Xóa tất cả emoji khỏi chuỗi văn bản.
|
|
3647
|
+
* Sử dụng pattern regex từ `patternEmoji()` để nhận diện toàn bộ emoji Unicode.
|
|
3648
|
+
* @param text Chuỗi cần xóa emoji
|
|
3649
|
+
* @returns Chuỗi đã được xóa emoji
|
|
3650
|
+
* @example removeEmoji('Hello 😀 World 🎉') → 'Hello World '
|
|
3651
|
+
*/
|
|
2980
3652
|
const removeEmoji = (text) => {
|
|
2981
3653
|
if (!text || !text.trim()) {
|
|
2982
3654
|
return text;
|
|
@@ -3199,12 +3871,24 @@ const createUniqueRandomIntGenerator = (min, max) => {
|
|
|
3199
3871
|
};
|
|
3200
3872
|
};
|
|
3201
3873
|
|
|
3874
|
+
/**
|
|
3875
|
+
* Thêm tất cả phần tử trong mảng vào một Set
|
|
3876
|
+
* @param setStore Đối tượng Set mục tiêu
|
|
3877
|
+
* @param data Mảng các phần tử cần thêm
|
|
3878
|
+
* @returns Set sau khi đã thêm dữ liệu
|
|
3879
|
+
*/
|
|
3202
3880
|
const addArrayToSet = (setStore, data) => {
|
|
3203
3881
|
for (const value of data) {
|
|
3204
3882
|
setStore.add(value);
|
|
3205
3883
|
}
|
|
3206
3884
|
return setStore;
|
|
3207
3885
|
};
|
|
3886
|
+
/**
|
|
3887
|
+
* Chuyển đổi Set sang Array, có hỗ trợ mapping function
|
|
3888
|
+
* @param data Đối tượng Set cần chuyển đổi
|
|
3889
|
+
* @param functionMap Hàm mapping tùy chọn để biến đổi từng phần tử
|
|
3890
|
+
* @returns Mảng kết quả
|
|
3891
|
+
*/
|
|
3208
3892
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3209
3893
|
const convertSetToArray = (data, functionMap) => {
|
|
3210
3894
|
if (functionMap) {
|
|
@@ -3214,9 +3898,11 @@ const convertSetToArray = (data, functionMap) => {
|
|
|
3214
3898
|
};
|
|
3215
3899
|
|
|
3216
3900
|
/**
|
|
3217
|
-
* Tính dung lượng (ước tính) của
|
|
3218
|
-
*
|
|
3219
|
-
*
|
|
3901
|
+
* Tính toán dung lượng (ước tính) của một đối tượng trong bộ nhớ.
|
|
3902
|
+
* Hàm sử dụng JSON.stringify và Blob để tính toán kích thước thực tế theo byte.
|
|
3903
|
+
*
|
|
3904
|
+
* @param obj Đối tượng cần tính dung lượng (Object, Array, Primitive...)
|
|
3905
|
+
* @returns Chuỗi mô tả dung lượng đã được format (ví dụ: "100 bytes", "2.50 KB", "1.20 MB"). Trả về "N/A" nếu không thể serialize.
|
|
3220
3906
|
*/
|
|
3221
3907
|
const getObjectSize = (obj) => {
|
|
3222
3908
|
try {
|
|
@@ -3236,6 +3922,15 @@ const getObjectSize = (obj) => {
|
|
|
3236
3922
|
}
|
|
3237
3923
|
};
|
|
3238
3924
|
|
|
3925
|
+
/**
|
|
3926
|
+
* Chuẩn hóa một URL bằng cách loại bỏ các dấu gạch chéo kép (//) trong pathname.
|
|
3927
|
+
*
|
|
3928
|
+
* @param rawUrl - URL cần chuẩn hóa.
|
|
3929
|
+
* @returns URL đã được chuẩn hóa dưới dạng chuỗi.
|
|
3930
|
+
*
|
|
3931
|
+
* @example
|
|
3932
|
+
* normalizeUrl('https://example.com//path//to//file') // => 'https://example.com/path/to/file'
|
|
3933
|
+
*/
|
|
3239
3934
|
const normalizeUrl = (rawUrl) => {
|
|
3240
3935
|
const url = new URL(rawUrl);
|
|
3241
3936
|
url.pathname = url.pathname.replace(/\/{2,}/g, '/');
|
|
@@ -3265,6 +3960,13 @@ const ignorePatterns = [
|
|
|
3265
3960
|
/vite/,
|
|
3266
3961
|
/node_modules/,
|
|
3267
3962
|
];
|
|
3963
|
+
/**
|
|
3964
|
+
* Trả về danh sách tên các hàm trong stack trace hiện tại.
|
|
3965
|
+
* Đã lọc bỏ các hàm nội bộ của Angular, RxJS, Zone.js và các thư viện hệ thống.
|
|
3966
|
+
* Hữu ích cho việc logging hoặc debugging để biết source gọi đến function hiện tại.
|
|
3967
|
+
*
|
|
3968
|
+
* @returns {string[]} Mảng chứa tên các hàm theo thứ tự từ gốc đến ngọn (ngược callback stack).
|
|
3969
|
+
*/
|
|
3268
3970
|
const traceStack = () => {
|
|
3269
3971
|
const stack = new Error().stack;
|
|
3270
3972
|
if (!stack)
|
|
@@ -3296,5 +3998,5 @@ const traceStack = () => {
|
|
|
3296
3998
|
* Generated bundle index. Do not edit.
|
|
3297
3999
|
*/
|
|
3298
4000
|
|
|
3299
|
-
export { AudioExtList, CHARACTER_DATA_EMPTY, COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE, COMMUNICATE_MICRO_PREFIX_TYPE, DEFAULT_START_PAGE_0, DocumentExtList, ENCODE_URI_PATTERN, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MAX_LENGTH, ERROR_MESSAGE_MAX_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MIN_VALID, ERROR_MESSAGE_PATTEN_VALID, ExcelExtList, ImageExtList, LINK_IMAGE_ERROR_TOKEN_INJECT, PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT, PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT, UtilsCache, UtilsCommunicateMicro, UtilsCommunicateMicroKeyGlobal, UtilsHttpParamsRequest, UtilsHttpParamsRequestInstance, UtilsKeyCodeConstant, UtilsLanguageConstants, UtilsUrlSearchParams, VideoExtList, addArrayToSet, base64Decode, base64Encode, capitalize, checkMouseOverInContainer, checkViewInScreen, cloneDeep, cloneIBoundingClientRect, colorContrastFromOrigin, colorStepContrastFromOrigin, convertBase64ToBlob, convertBlobToFile, convertFileToBase64, convertFileToBase64_ObjectUrl, convertObjectToSignal, convertSetToArray, convertSignalToObject, convertUrlToFile, createUniqueRandomIntGenerator, decodeEscapeHtml, decodeURI, decrypt, decrypt3rd, deleteUnicode, detectAndCleanNearWhiteColors, downloadFileByUrl, downloadFileByUrlUseXmlRequest, downloadImageFromELement, encodeURI, encrypt, encrypt3rd, endCodeUrl, escapeHtml, firstLetterToUpperCase, formatDate, formatNumber, formatTextCompare, fullNameFormat, generateInterface, get, getColorById, getDayjs, getDeviceInfo, getDocumentByString, getDragEventByElement, getEventNameHandleClick, getFileExtension, getKeyCacheByArrayObject, getLabelBySizeFile, getObjectSize, getSmartAxisScale, getViewport, groupBy, hasDangerousConstructorName, highlightByKeyword, isArray, isAsyncObject, isBrowserAPIObject, isBrowserGlobalObject, isBuiltInObject, isDOMObject, isDangerousObject, isDayjsObject, isDifferenceDay, isDifferenceMonth, isDifferenceYear, isEmbedFrame, isEmpty, isEqual, isFalsy, isFile, isFrameworkObject, isIncludeAudioExtList, isIncludeDocumentExtList, isIncludeImageExtList, isIncludeVideoExtList, isMap, isNearWhite, isNil, isRegExp, isReturnAsIsObject, isSafeToProcess, isSet, isSkippableObject, isSpecialObject, isTouchDevice, isTruthy, isTypeAudio, isTypeFile, isTypeImage, isTypeVideo, keyBy, listColorDefine, md5, normalizeUrl, omitBy,
|
|
4001
|
+
export { AudioExtList, CHARACTER_DATA_EMPTY, COMMUNICATE_MICRO_KEY_GET_ALL_MESSAGE, COMMUNICATE_MICRO_PREFIX_TYPE, DEFAULT_START_PAGE_0, DocumentExtList, ENCODE_URI_PATTERN, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MAX_LENGTH, ERROR_MESSAGE_MAX_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MIN_VALID, ERROR_MESSAGE_PATTEN_VALID, ExcelExtList, ImageExtList, LINK_IMAGE_ERROR_TOKEN_INJECT, PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT, PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT, UtilsCache, UtilsCommunicateMicro, UtilsCommunicateMicroKeyGlobal, UtilsHttpParamsRequest, UtilsHttpParamsRequestInstance, UtilsKeyCodeConstant, UtilsLanguageConstants, UtilsUrlSearchParams, VideoExtList, addArrayToSet, base64Decode, base64Encode, capitalize, checkMouseOverInContainer, checkViewInScreen, cloneDeep, cloneIBoundingClientRect, colorContrastFromOrigin, colorStepContrastFromOrigin, convertBase64ToBlob, convertBlobToFile, convertFileToBase64, convertFileToBase64_ObjectUrl, convertObjectToSignal, convertSetToArray, convertSignalToObject, convertUrlToFile, createUniqueRandomIntGenerator, decodeEscapeHtml, decodeURI, decrypt, decrypt3rd, deleteUnicode, detectAndCleanNearWhiteColors, downloadFileByUrl, downloadFileByUrlUseXmlRequest, downloadImageFromELement, encodeURI, encrypt, encrypt3rd, endCodeUrl, escapeHtml, firstLetterToUpperCase, formatDate, formatNumber, formatTextCompare, fullNameFormat, generateInterface, get, getColorById, getDayjs, getDeviceInfo, getDocumentByString, getDragEventByElement, getEventNameHandleClick, getFileExtension, getKeyCacheByArrayObject, getLabelBySizeFile, getObjectSize, getSmartAxisScale, getViewport, groupBy, hasDangerousConstructorName, highlightByKeyword, isArray, isAsyncObject, isBrowserAPIObject, isBrowserGlobalObject, isBuiltInObject, isDOMObject, isDangerousObject, isDayjsObject, isDifferenceDay, isDifferenceMonth, isDifferenceYear, isEmbedFrame, isEmpty, isEqual, isFalsy, isFile, isFrameworkObject, isIncludeAudioExtList, isIncludeDocumentExtList, isIncludeImageExtList, isIncludeVideoExtList, isMap, isNearWhite, isNil, isPrimitiveType, isRegExp, isReturnAsIsObject, isSafeToProcess, isSet, isSkippableObject, isSpecialObject, isTouchDevice, isTruthy, isTypeAudio, isTypeFile, isTypeImage, isTypeVideo, keyBy, listColorDefine, md5, normalizeUrl, omitBy, patternAccount, patternDomain, patternEmail, patternEmoji, patternEncodeUri, patternGetFieldByRuleFieldReplace, patternHostUrl, patternKey, patternMobilePhone, patternName, patternNameProfile, patternNameSpecial, patternNameUtf8, patternNumber, patternPem, patternPhone, patternProtocolUrl, patternRuleFieldReplace, patternTax, patternUrl, protectString, range, removeEmoji, revealString, rgbToHex, set, setCaretPosition, setDefaultTimeZone, setKeyCrypto, setKeyCrypto3rd, setPatternCheckTimeUTC, setStylesElement, traceStack, uniqBy, unwrapSignal, updateFunctionCheckEmbedFrame, updateFunctionFormatDate, updateFunctionXssFilter, uppercaseByPosition, uuid, viewDataNumberByLanguage, xssFilter };
|
|
3300
4002
|
//# sourceMappingURL=libs-ui-utils.mjs.map
|