@libs-ui/utils 0.2.324-0 → 0.2.326-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.
@@ -1,7 +1,6 @@
1
1
  import DeviceDetector from 'device-detector-js';
2
2
  import Quill from 'quill';
3
- import { fromEvent, tap, takeUntil, mergeMap, startWith, finalize, lastValueFrom, timer, Observable, Subject, filter } from 'rxjs';
4
- import CryptoES from 'crypto-es';
3
+ import { Observable, Subject, fromEvent, takeUntil, filter, tap, mergeMap, startWith, finalize, lastValueFrom, timer } from 'rxjs';
5
4
  import { HttpParams } from '@angular/common/http';
6
5
  import { TemplateRef, ElementRef, isSignal, signal, InjectionToken } from '@angular/core';
7
6
  import dayjs from 'dayjs';
@@ -12,6 +11,7 @@ import localeData from 'dayjs/plugin/localeData';
12
11
  import timezone from 'dayjs/plugin/timezone';
13
12
  import updateLocale from 'dayjs/plugin/updateLocale';
14
13
  import utc from 'dayjs/plugin/utc';
14
+ import CryptoES from 'crypto-es';
15
15
 
16
16
  const ERROR_MESSAGE_EMPTY_VALID = 'i18n_valid_empty_message';
17
17
  const ERROR_MESSAGE_PATTEN_VALID = 'i18n_valid_pattern_message';
@@ -233,355 +233,150 @@ const removeEmoji = (text) => {
233
233
  return text.replace(patternEmoji(), '');
234
234
  };
235
235
 
236
- const deviceDetector = new DeviceDetector();
237
- const parser = new DOMParser();
238
- const quill = new Quill(document.createElement('div'));
239
- const getDeviceInfo = () => {
240
- return deviceDetector.parse(window.navigator.userAgent);
241
- };
242
- const getPlatFromBrowser = () => {
243
- const info = getDeviceInfo();
244
- return `${info.client?.name} ${info.client?.version}`;
245
- };
246
- const getEventNameHandleClick = (() => {
247
- const deviceInfo = getDeviceInfo();
248
- let nameEventHandleClick = deviceInfo.device?.type === 'desktop' ? 'click' : 'touchstart';
249
- if ('onpointerdown' in window) {
250
- nameEventHandleClick = 'pointerdown';
236
+ /* eslint-disable @typescript-eslint/no-explicit-any */
237
+ /**
238
+ * Danh sách các constructor name nguy hiểm
239
+ */
240
+ const DANGEROUS_CONSTRUCTOR_NAMES = ['Window', 'Document', 'HTMLDocument', 'HTMLElement', 'Element', 'Node', 'Location', 'Navigator', 'Screen', 'History', 'Storage', 'Console', 'Performance'];
241
+ /**
242
+ * Kiểm tra xem đối tượng có phải là browser global object không
243
+ */
244
+ const isBrowserGlobalObject = (obj) => {
245
+ if (obj === null || typeof obj !== 'object') {
246
+ return false;
251
247
  }
252
- return nameEventHandleClick;
253
- })();
254
- const getDocumentByString = (htmlStr) => {
255
- return parser.parseFromString(htmlStr, 'text/html');
256
- };
257
- const cloneIBoundingClientRect = (rect) => {
258
- return {
259
- top: rect['top'],
260
- left: rect['left'],
261
- width: rect['width'],
262
- height: rect['height'],
263
- bottom: rect['bottom'],
264
- };
265
- };
266
- const setStylesElement = (el, styles, render2) => {
267
- Object.keys(styles).forEach((key) => render2.setStyle(el, key, styles[key]));
248
+ return obj instanceof Window || obj instanceof Document || obj === window || obj === document || obj === globalThis;
268
249
  };
269
- const getViewport = (win = window) => {
270
- 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;
271
- return { width: w, height: h };
250
+ /**
251
+ * Kiểm tra xem đối tượng phải DOM object không
252
+ */
253
+ const isDOMObject = (obj) => {
254
+ if (obj === null || typeof obj !== 'object') {
255
+ return false;
256
+ }
257
+ return obj instanceof HTMLElement || obj instanceof Node || obj instanceof Element;
272
258
  };
273
- const setCaretPosition = (element, position) => {
274
- if (!element || !element.setSelectionRange) {
275
- return;
259
+ /**
260
+ * Kiểm tra xem đối tượng có phải là browser API object không
261
+ */
262
+ const isBrowserAPIObject = (obj) => {
263
+ if (obj === null || typeof obj !== 'object' || typeof window === 'undefined') {
264
+ return false;
276
265
  }
277
- element.focus();
278
- element.setSelectionRange(position, position);
266
+ return obj === window.location || obj === window.navigator || obj === window.screen || obj === window.history || obj === window.localStorage || obj === window.sessionStorage || obj === window.console || obj === window.performance;
279
267
  };
280
- const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
281
- if (!container || !element) {
268
+ /**
269
+ * Kiểm tra constructor name có nằm trong danh sách nguy hiểm không
270
+ */
271
+ const hasDangerousConstructorName = (obj) => {
272
+ if (obj === null || typeof obj !== 'object') {
282
273
  return false;
283
274
  }
284
- const rectContainer = cloneIBoundingClientRect(container.getBoundingClientRect());
285
- if (elementScroll) {
286
- const rectElementScroll = elementScroll.getBoundingClientRect();
287
- rectContainer['left'] = rectElementScroll.left;
288
- rectContainer['top'] = rectElementScroll.top;
275
+ const constructorName = obj.constructor?.name;
276
+ return constructorName && DANGEROUS_CONSTRUCTOR_NAMES.includes(constructorName);
277
+ };
278
+ /**
279
+ * Kiểm tra xem đối tượng có phải là DOM object hoặc browser object nguy hiểm không
280
+ * Những đối tượng này có thể gây ra circular reference và maximum call stack
281
+ */
282
+ const isDangerousObject = (obj) => {
283
+ if (obj === null || typeof obj !== 'object') {
284
+ return false;
289
285
  }
290
- const rectElement = element.getBoundingClientRect();
291
- const { left, top } = rectElement;
292
- const topContainer = rectContainer['top'];
293
- const leftContainer = rectContainer['left'];
294
- const widthContainer = rectContainer['width'];
295
- const heightContainer = rectContainer['height'];
296
- const scrollTopContainer = container.scrollTop;
297
- const scrollLeftContainer = container.scrollLeft;
298
- const limitTop = topContainer + scrollTopContainer + heightContainer;
299
- const limitLeft = leftContainer + scrollLeftContainer + widthContainer;
300
- const maxTopItem = top + (maxTopLeft?.top || 0);
301
- const maxLeftItem = left + (maxTopLeft?.left || 0);
302
- if (topContainer <= maxTopItem && maxTopItem <= limitTop && leftContainer <= maxLeftItem && maxLeftItem <= limitLeft) {
303
- return true;
286
+ return isBrowserGlobalObject(obj) || isDOMObject(obj) || isBrowserAPIObject(obj) || hasDangerousConstructorName(obj);
287
+ };
288
+ /**
289
+ * Kiểm tra đối tượng có phải là Angular/Framework object không
290
+ */
291
+ const isFrameworkObject = (obj) => {
292
+ if (obj === null || typeof obj !== 'object') {
293
+ return false;
304
294
  }
305
- return false;
295
+ return obj instanceof TemplateRef || obj instanceof ElementRef;
306
296
  };
307
- const checkMouseOverInContainer = (mousePosition, element, rect) => {
308
- if (!element && !rect) {
297
+ /**
298
+ * Kiểm tra đối tượng có phải là File/Blob object không
299
+ */
300
+ const isFile = (obj) => {
301
+ if (obj === null || typeof obj !== 'object') {
309
302
  return false;
310
303
  }
311
- const rectElement = rect || element?.getBoundingClientRect() || {};
312
- const { left, top, width, height } = rectElement;
313
- const limitLeft = left + width;
314
- const limitTop = top + height;
315
- const { clientX, clientY } = mousePosition;
316
- if (left <= clientX && clientX <= limitLeft && top <= clientY && clientY <= limitTop) {
317
- return true;
304
+ return obj instanceof File || obj instanceof Blob || Object.prototype.toString.call(obj) === '[object File]';
305
+ };
306
+ /**
307
+ * Kiểm tra đối tượng có phải là Built-in object không
308
+ */
309
+ const isBuiltInObject = (obj) => {
310
+ if (obj === null || typeof obj !== 'object') {
311
+ return false;
318
312
  }
319
- return false;
313
+ return obj instanceof Date || isRegExp(obj) || obj instanceof HttpParams || ('hasOwnProperty' in obj && '__ngContext__' in obj);
320
314
  };
321
- const getDragEventByElement = (config) => {
322
- let timer;
323
- const addClass = () => {
324
- timer = setTimeout(() => {
325
- document.body.classList.add('!select-none');
326
- document.body.classList.add('!cursor-grabbing');
327
- }, 250);
328
- };
329
- const removeClass = () => {
330
- document.body.classList.remove('!select-none');
331
- document.body.classList.remove('!cursor-grabbing');
332
- clearTimeout(timer);
333
- };
334
- fromEvent(config.elementMouseDown, 'mouseleave')
335
- .pipe(tap(() => document.body.classList.remove('cursor-pointer')), takeUntil(config.onDestroy))
336
- .subscribe();
337
- fromEvent(config.elementMouseDown, 'mouseenter')
338
- .pipe(tap(() => document.body.classList.add('cursor-pointer')), takeUntil(config.onDestroy))
339
- .subscribe();
340
- const mouseDown = fromEvent(config.elementMouseDown, 'mousedown').pipe(tap((e) => {
341
- e.stopPropagation();
342
- addClass();
343
- config.functionMouseDown?.(e);
344
- }));
345
- const mouseup = fromEvent(config.elementMouseUp || document, 'mouseup').pipe(tap((e) => {
346
- e.stopPropagation();
347
- removeClass();
348
- config.functionMouseUp?.(e);
349
- }));
350
- const mousemove = fromEvent(config.elementMouseMove || document, 'mousemove').pipe(tap((e) => {
351
- e.stopPropagation();
352
- config.functionMouseMove?.(e);
353
- }), takeUntil(mouseup));
354
- return mouseDown.pipe(mergeMap((e) => (config.isStartWithMouseDownEvent ? mousemove.pipe(startWith(e)) : mousemove)), takeUntil(config.onDestroy), finalize(removeClass));
315
+ /**
316
+ * Kiểm tra đối tượng có phải là RegExp object không
317
+ */
318
+ const isRegExp = (obj) => {
319
+ return obj instanceof RegExp;
355
320
  };
356
- const getHTMLFromQuill = async (data, options) => {
357
- const { replaceNewLineTo = '<br>', replaceTagBRTo, replaceTags, replaceBrToDiv } = options || {};
358
- const delta = typeof data === 'string' ? await getDeltaFromHTML(data) : data;
359
- if (options?.functionReplaceDelta) {
360
- options.functionReplaceDelta(delta);
361
- }
362
- delta.ops.forEach((op) => {
363
- if (op.insert) {
364
- if (typeof op.insert === 'string') {
365
- if (replaceNewLineTo) {
366
- op.insert = op.insert.replace(/\n/g, replaceNewLineTo);
367
- }
368
- if (replaceTagBRTo) {
369
- op.insert = op.insert.replace(/<br>/g, replaceTagBRTo);
370
- }
371
- if (replaceTags?.length) {
372
- for (const tag of replaceTags) {
373
- op.insert = op.insert.replace(new RegExp(`<${tag.tag}>`, 'g'), `<${tag.replaceTo}>`);
374
- op.insert = op.insert.replace(new RegExp(`</${tag.tag}>`, 'g'), `</${tag.replaceTo}>`);
375
- }
376
- }
377
- }
378
- }
379
- });
380
- quill.setContents(delta);
381
- let htmlText = options?.getRootHtml ? quill.root.innerHTML : quill.root.firstElementChild?.innerHTML;
382
- if (replaceBrToDiv) {
383
- htmlText = convertHtmlToDivBlocks(htmlText || '');
321
+ /**
322
+ * Kiểm tra đối tượng phải Async object không
323
+ */
324
+ const isAsyncObject = (obj) => {
325
+ if (obj === null || typeof obj !== 'object') {
326
+ return false;
384
327
  }
385
- return decodeEscapeHtml(htmlText || '');
328
+ return obj instanceof Promise || obj instanceof Observable;
386
329
  };
387
- const convertHtmlToDivBlocks = (html) => {
388
- const BREAK_TOKEN = '<<<BREAK>>>';
389
- // Bước 1: thay <br> thành token tạm
390
- const normalizedHtml = html.replace(/<br\s*\/?>/gi, BREAK_TOKEN);
391
- // Bước 2: tách theo token
392
- const parts = normalizedHtml.split(BREAK_TOKEN);
393
- const parser = new DOMParser();
394
- const divs = [];
395
- for (const raw of parts) {
396
- const trimmed = raw.trim();
397
- if (!trimmed)
398
- continue;
399
- // parse mỗi phần nhỏ như một document riêng
400
- const doc = parser.parseFromString(trimmed, 'text/html');
401
- const body = doc.body;
402
- // Lấy lại nội dung bên trong body
403
- divs.push(`<div>${body.innerHTML}</div>`);
404
- }
405
- return divs.join('');
330
+ /**
331
+ * Kiểm tra đối tượng có phải là Special object cần bỏ qua không
332
+ * Bao gồm: Framework objects, File objects, Built-in objects, Async objects
333
+ */
334
+ const isSpecialObject = (obj) => {
335
+ return isFrameworkObject(obj) || isFile(obj) || isBuiltInObject(obj) || isAsyncObject(obj);
406
336
  };
407
- const getDeltaFromHTML = async (html) => {
408
- quill.root.innerHTML = html;
409
- setTimeout(() => {
410
- console.log(quill.getContents());
411
- }, 1000);
412
- await lastValueFrom(timer(1000));
413
- return quill.getContents();
337
+ /**
338
+ * Kiểm tra đối tượng có phải là dayjs object không
339
+ */
340
+ const isDayjsObject = (obj) => {
341
+ return dayjs.isDayjs(obj);
414
342
  };
415
- const processPasteData = async (e, config) => {
416
- const element = config.element;
417
- const files = e.clipboardData?.files;
418
- if (files?.length) {
419
- e.preventDefault();
420
- config.handlerPasteFile?.(files);
421
- config.callBack?.('file');
422
- return;
423
- }
424
- // Lưu selection TRƯỚC khi prevent default
425
- const selection = window.getSelection();
426
- let savedRange = null;
427
- if (selection && selection.rangeCount > 0) {
428
- const range = selection.getRangeAt(0);
429
- // Chỉ lưu nếu range nằm trong contentText element
430
- const container = range.commonAncestorContainer;
431
- const isInContentElement = element.contains(container.nodeType === Node.TEXT_NODE ? container.parentNode : container);
432
- if (isInContentElement) {
433
- savedRange = range.cloneRange();
434
- }
435
- }
436
- // Prevent default để tự xử lý paste
437
- e.preventDefault();
438
- // Sử dụng Quill để clean HTML content
439
- const htmlContent = e.clipboardData?.getData('text/html') || '';
440
- const plainText = e.clipboardData?.getData('text/plain') || '';
441
- let contentToInsert = (plainText || '').replace(/\n/g, '<br>');
442
- if (htmlContent) {
443
- contentToInsert = await getHTMLFromQuill(htmlContent);
444
- }
445
- if (!contentToInsert) {
446
- config.callBack?.('no-content');
447
- return;
448
- }
449
- if (savedRange) {
450
- insertContentWithRange(contentToInsert, savedRange, element);
451
- config.callBack?.('range');
452
- return;
453
- }
454
- element.innerHTML += contentToInsert;
455
- config.callBack?.('content');
343
+ /**
344
+ * Kiểm tra đối tượng có phải là object cần bỏ qua trong quá trình convert không
345
+ */
346
+ const isSkippableObject = (obj) => {
347
+ return isDangerousObject(obj) || isSpecialObject(obj);
456
348
  };
457
- const insertContentWithRange = (content, savedRange, element) => {
458
- const selection = window.getSelection();
459
- if (!selection) {
460
- // Fallback: append vào cuối
461
- element.innerHTML += content;
462
- return;
463
- }
464
- // Restore selection
465
- selection.removeAllRanges();
466
- selection.addRange(savedRange);
467
- // Xóa nội dung đã select (nếu có)
468
- savedRange.deleteContents();
469
- // Tạo document fragment từ HTML content
470
- const tempDiv = document.createElement('div');
471
- tempDiv.innerHTML = content;
472
- const fragment = document.createDocumentFragment();
473
- while (tempDiv.firstChild) {
474
- fragment.appendChild(tempDiv.firstChild);
475
- }
476
- // Insert fragment tại vị trí range
477
- savedRange.insertNode(fragment);
478
- // Di chuyển cursor đến cuối nội dung vừa insert
479
- if (fragment.lastChild) {
480
- savedRange.setStartAfter(fragment.lastChild);
481
- }
482
- savedRange.collapse(true);
483
- selection.removeAllRanges();
484
- selection.addRange(savedRange);
349
+ /**
350
+ * Kiểm tra đối tượng có phải là Map object không
351
+ */
352
+ const isMap = (obj) => {
353
+ return obj instanceof Map;
354
+ };
355
+ /**
356
+ * Kiểm tra đối tượng có phải là Set object không
357
+ */
358
+ const isSet = (obj) => {
359
+ return obj instanceof Set;
360
+ };
361
+ /**
362
+ * Kiểm tra đối tượng có phải là Array object không
363
+ */
364
+ const isArray = (obj) => {
365
+ return Array.isArray(obj);
366
+ };
367
+ /**
368
+ * Kiểm tra đối tượng có phải là object cần trả về nguyên trạng không
369
+ * Bao gồm: dangerous objects, special objects, dayjs objects
370
+ */
371
+ const isReturnAsIsObject = (obj) => {
372
+ return isSkippableObject(obj) || isDayjsObject(obj);
373
+ };
374
+ /**
375
+ * Kiểm tra xem đối tượng có an toàn để clone/convert không
376
+ */
377
+ const isSafeToProcess = (obj) => {
378
+ return !isDangerousObject(obj);
485
379
  };
486
-
487
- class UtilsUrlSearchParams {
488
- static instance;
489
- params;
490
- constructor(paramString) {
491
- this.params = new Array();
492
- this.params.length = 0;
493
- this.params.push(...this.buildParams(paramString));
494
- }
495
- static getInstance() {
496
- if (!this.instance) {
497
- this.instance = new UtilsUrlSearchParams('');
498
- }
499
- return this.instance;
500
- }
501
- static toStringParamObject(params) {
502
- const paramsArr = Object.keys(params).map((key) => {
503
- return {
504
- key,
505
- value: params[key],
506
- };
507
- });
508
- return UtilsUrlSearchParams.ToString(paramsArr);
509
- }
510
- static ToString(params) {
511
- let paramsStr = '';
512
- params
513
- .sort((item1, item2) => item1.key.localeCompare(item2.key))
514
- .forEach((item) => {
515
- const { key, value } = item;
516
- paramsStr = `${paramsStr}${paramsStr ? '&' : ''}${key}=${value}`;
517
- });
518
- return paramsStr;
519
- }
520
- buildParams(paramString) {
521
- const paramsBuild = new Array();
522
- if (!paramString) {
523
- return paramsBuild;
524
- }
525
- const paramsError = paramString.split('?');
526
- if (paramsError.length > 2) {
527
- paramString = `${paramsError[0]}?${paramsError[1]}&${paramsError.slice(2).join('&')}`;
528
- }
529
- const params = paramString.replace(/\S+[?]/g, '').split('&');
530
- if (params) {
531
- params.forEach((param) => {
532
- if (param.indexOf('=') < 0) {
533
- return;
534
- }
535
- let [key, value] = param.split('=');
536
- key = key.replace(/[?&]+/g, '');
537
- value = decodeURIComponent(value.replace(/\+/g, ' '));
538
- if (!paramsBuild.some((param) => param.key === key)) {
539
- paramsBuild.push({ key, value });
540
- }
541
- });
542
- }
543
- return paramsBuild;
544
- }
545
- set(key, value) {
546
- const paramByKey = this.params.find((param) => param.key === key);
547
- if (!paramByKey) {
548
- this.params.push({ key, value: decodeURIComponent(value.replace(/\+/g, ' ')) });
549
- return;
550
- }
551
- paramByKey['value'] = value;
552
- }
553
- get(key) {
554
- return this.params.find((param) => param.key === key)?.value;
555
- }
556
- has(key) {
557
- return this.params.some((param) => param.key === key);
558
- }
559
- delete(key) {
560
- const index = this.params.findIndex((param) => param.key === key);
561
- if (index >= 0) {
562
- this.params.splice(index, 1);
563
- }
564
- }
565
- length() {
566
- return this.params.length;
567
- }
568
- toString(params) {
569
- params = params || this.params;
570
- return UtilsUrlSearchParams.ToString(params);
571
- }
572
- getParamObject() {
573
- const params = {};
574
- this.params.forEach((item) => {
575
- params[item.key] = item.value;
576
- });
577
- return params;
578
- }
579
- compareParams(param1, param2) {
580
- const params1 = this.toString(this.buildParams(param1));
581
- const params2 = this.toString(this.buildParams(param2));
582
- return params1 === params2;
583
- }
584
- }
585
380
 
586
381
  let key$1 = '12~@#loqwsxacva(3rdhaq12';
587
382
  /**
@@ -623,192 +418,30 @@ const md5 = (plainData) => {
623
418
  return CryptoES.MD5(plainData).toString();
624
419
  };
625
420
 
626
- const uuid = () => {
627
- const timestamp = performance.now() * 1000;
628
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/~';
629
- let randomString = '';
630
- for (let i = 0; i < 5; i++) {
631
- randomString += chars.charAt(Math.floor(Math.random() * chars.length));
632
- }
633
- const baseString = `${Math.floor(timestamp)}-${randomString}`;
634
- const charArray = baseString.split('');
635
- for (let i = charArray.length - 1; i > 0; i--) {
636
- const j = Math.floor(Math.random() * (i + 1));
637
- [charArray[i], charArray[j]] = [charArray[j], charArray[i]];
638
- }
639
- const shuffledString = charArray.join('');
640
- return md5(shuffledString);
641
- };
642
-
643
- /* eslint-disable @typescript-eslint/no-explicit-any */
644
- /**
645
- * Danh sách các constructor name nguy hiểm
646
- */
647
- const DANGEROUS_CONSTRUCTOR_NAMES = ['Window', 'Document', 'HTMLDocument', 'HTMLElement', 'Element', 'Node', 'Location', 'Navigator', 'Screen', 'History', 'Storage', 'Console', 'Performance'];
421
+ let key = '12~@#loqwsxacva(3rdhaq12';
648
422
  /**
649
- * Kiểm tra xem đối tượng có phải là browser global object không
423
+ * @description Thiết lập key hóa
424
+ * @param value key mã hóa, độ dài bằng 24 hoặc 32 ký tự.
650
425
  */
651
- const isBrowserGlobalObject = (obj) => {
652
- if (obj === null || typeof obj !== 'object') {
653
- return false;
426
+ const setKeyCrypto3rd = (value) => {
427
+ if (value.length !== 24 && value.length !== 32) {
428
+ throw Error(`key.length = ${value.length}; Key phải là 1 chuỗi dài 24 hoặc 32 ký tự`);
654
429
  }
655
- return obj instanceof Window || obj instanceof Document || obj === window || obj === document || obj === globalThis;
430
+ key = value;
656
431
  };
657
- /**
658
- * Kiểm tra xem đối tượng có phải là DOM object không
659
- */
660
- const isDOMObject = (obj) => {
661
- if (obj === null || typeof obj !== 'object') {
662
- return false;
663
- }
664
- return obj instanceof HTMLElement || obj instanceof Node || obj instanceof Element;
432
+ const keyStore = () => {
433
+ return key;
665
434
  };
666
- /**
667
- * Kiểm tra xem đối tượng có phải là browser API object không
668
- */
669
- const isBrowserAPIObject = (obj) => {
670
- if (obj === null || typeof obj !== 'object' || typeof window === 'undefined') {
671
- return false;
435
+ const encrypt3rd = (plainData) => {
436
+ if (!keyStore()) {
437
+ throw Error('lỗi chưa setup key mã hóa');
672
438
  }
673
- return obj === window.location || obj === window.navigator || obj === window.screen || obj === window.history || obj === window.localStorage || obj === window.sessionStorage || obj === window.console || obj === window.performance;
674
- };
675
- /**
676
- * Kiểm tra constructor name có nằm trong danh sách nguy hiểm không
677
- */
678
- const hasDangerousConstructorName = (obj) => {
679
- if (obj === null || typeof obj !== 'object') {
680
- return false;
681
- }
682
- const constructorName = obj.constructor?.name;
683
- return constructorName && DANGEROUS_CONSTRUCTOR_NAMES.includes(constructorName);
684
- };
685
- /**
686
- * Kiểm tra xem đối tượng có phải là DOM object hoặc browser object nguy hiểm không
687
- * Những đối tượng này có thể gây ra circular reference và maximum call stack
688
- */
689
- const isDangerousObject = (obj) => {
690
- if (obj === null || typeof obj !== 'object') {
691
- return false;
692
- }
693
- return isBrowserGlobalObject(obj) || isDOMObject(obj) || isBrowserAPIObject(obj) || hasDangerousConstructorName(obj);
694
- };
695
- /**
696
- * Kiểm tra đối tượng có phải là Angular/Framework object không
697
- */
698
- const isFrameworkObject = (obj) => {
699
- if (obj === null || typeof obj !== 'object') {
700
- return false;
701
- }
702
- return obj instanceof TemplateRef || obj instanceof ElementRef;
703
- };
704
- /**
705
- * Kiểm tra đối tượng có phải là File/Blob object không
706
- */
707
- const isFile = (obj) => {
708
- if (obj === null || typeof obj !== 'object') {
709
- return false;
710
- }
711
- return obj instanceof File || obj instanceof Blob || Object.prototype.toString.call(obj) === '[object File]';
712
- };
713
- /**
714
- * Kiểm tra đối tượng có phải là Built-in object không
715
- */
716
- const isBuiltInObject = (obj) => {
717
- if (obj === null || typeof obj !== 'object') {
718
- return false;
719
- }
720
- return obj instanceof Date || isRegExp(obj) || obj instanceof HttpParams || ('hasOwnProperty' in obj && '__ngContext__' in obj);
721
- };
722
- /**
723
- * Kiểm tra đối tượng có phải là RegExp object không
724
- */
725
- const isRegExp = (obj) => {
726
- return obj instanceof RegExp;
727
- };
728
- /**
729
- * Kiểm tra đối tượng có phải là Async object không
730
- */
731
- const isAsyncObject = (obj) => {
732
- if (obj === null || typeof obj !== 'object') {
733
- return false;
734
- }
735
- return obj instanceof Promise || obj instanceof Observable;
736
- };
737
- /**
738
- * Kiểm tra đối tượng có phải là Special object cần bỏ qua không
739
- * Bao gồm: Framework objects, File objects, Built-in objects, Async objects
740
- */
741
- const isSpecialObject = (obj) => {
742
- return isFrameworkObject(obj) || isFile(obj) || isBuiltInObject(obj) || isAsyncObject(obj);
743
- };
744
- /**
745
- * Kiểm tra đối tượng có phải là dayjs object không
746
- */
747
- const isDayjsObject = (obj) => {
748
- return dayjs.isDayjs(obj);
749
- };
750
- /**
751
- * Kiểm tra đối tượng có phải là object cần bỏ qua trong quá trình convert không
752
- */
753
- const isSkippableObject = (obj) => {
754
- return isDangerousObject(obj) || isSpecialObject(obj);
755
- };
756
- /**
757
- * Kiểm tra đối tượng có phải là Map object không
758
- */
759
- const isMap = (obj) => {
760
- return obj instanceof Map;
761
- };
762
- /**
763
- * Kiểm tra đối tượng có phải là Set object không
764
- */
765
- const isSet = (obj) => {
766
- return obj instanceof Set;
767
- };
768
- /**
769
- * Kiểm tra đối tượng có phải là Array object không
770
- */
771
- const isArray = (obj) => {
772
- return Array.isArray(obj);
773
- };
774
- /**
775
- * Kiểm tra đối tượng có phải là object cần trả về nguyên trạng không
776
- * Bao gồm: dangerous objects, special objects, dayjs objects
777
- */
778
- const isReturnAsIsObject = (obj) => {
779
- return isSkippableObject(obj) || isDayjsObject(obj);
780
- };
781
- /**
782
- * Kiểm tra xem đối tượng có an toàn để clone/convert không
783
- */
784
- const isSafeToProcess = (obj) => {
785
- return !isDangerousObject(obj);
786
- };
787
-
788
- let key = '12~@#loqwsxacva(3rdhaq12';
789
- /**
790
- * @description Thiết lập key mã hóa
791
- * @param value key mã hóa, độ dài bằng 24 hoặc 32 ký tự.
792
- */
793
- const setKeyCrypto3rd = (value) => {
794
- if (value.length !== 24 && value.length !== 32) {
795
- throw Error(`key.length = ${value.length}; Key phải là 1 chuỗi dài 24 hoặc 32 ký tự`);
796
- }
797
- key = value;
798
- };
799
- const keyStore = () => {
800
- return key;
801
- };
802
- const encrypt3rd = (plainData) => {
803
- if (!keyStore()) {
804
- throw Error('lỗi chưa setup key mã hóa');
805
- }
806
- const key = CryptoES.enc.Hex.parse(keyStore());
807
- const iv = CryptoES.enc.Hex.parse(keyStore());
808
- const mode = CryptoES.mode.CBC;
809
- const padding = CryptoES.pad.Pkcs7;
810
- const options = { iv: iv, mode: mode, padding: padding };
811
- return CryptoES.AES.encrypt(plainData, key, options).toString();
439
+ const key = CryptoES.enc.Hex.parse(keyStore());
440
+ const iv = CryptoES.enc.Hex.parse(keyStore());
441
+ const mode = CryptoES.mode.CBC;
442
+ const padding = CryptoES.pad.Pkcs7;
443
+ const options = { iv: iv, mode: mode, padding: padding };
444
+ return CryptoES.AES.encrypt(plainData, key, options).toString();
812
445
  };
813
446
  const decrypt3rd = (encryptedData) => {
814
447
  if (!keyStore()) {
@@ -822,6 +455,7 @@ const decrypt3rd = (encryptedData) => {
822
455
  return CryptoES.AES.decrypt(encryptedData, key, options).toString(CryptoES.enc.Utf8);
823
456
  };
824
457
 
458
+ //NOTE: Do là function truyền từ bên ngoài vào nên sẽ không có unit test
825
459
  let functionCheck = () => {
826
460
  return window.parent !== window.top;
827
461
  };
@@ -1045,6 +679,23 @@ class UtilsLanguageConstants {
1045
679
  }
1046
680
  }
1047
681
 
682
+ const uuid = () => {
683
+ const timestamp = performance.now() * 1000;
684
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?/~';
685
+ let randomString = '';
686
+ for (let i = 0; i < 5; i++) {
687
+ randomString += chars.charAt(Math.floor(Math.random() * chars.length));
688
+ }
689
+ const baseString = `${Math.floor(timestamp)}-${randomString}`;
690
+ const charArray = baseString.split('');
691
+ for (let i = charArray.length - 1; i > 0; i--) {
692
+ const j = Math.floor(Math.random() * (i + 1));
693
+ [charArray[i], charArray[j]] = [charArray[j], charArray[i]];
694
+ }
695
+ const shuffledString = charArray.join('');
696
+ return md5(shuffledString);
697
+ };
698
+
1048
699
  /* eslint-disable no-async-promise-executor */
1049
700
  /* eslint-disable @typescript-eslint/no-explicit-any */
1050
701
  class UtilsCache {
@@ -1849,10 +1500,13 @@ const isEmpty = (value, options) => {
1849
1500
  if (!options?.ignoreUnWrapSignal) {
1850
1501
  value = unwrapSignal(value);
1851
1502
  }
1503
+ if (options?.ignoreCheckString && value === '') {
1504
+ return false;
1505
+ }
1852
1506
  if (options?.ignoreCheckTypePrimitive) {
1853
- return isEmptyTypeObject(value);
1507
+ return typeof value === 'object' && isEmptyTypeObject(value);
1854
1508
  }
1855
- return isEmptyTypePrimitive(value, { ignoreCheckString: options?.ignoreCheckString }) || isEmptyTypeObject(value);
1509
+ return isEmptyTypeObject(value);
1856
1510
  };
1857
1511
  /**
1858
1512
  * Kiểm tra xem một giá trị có phải là object rỗng hay không
@@ -1863,13 +1517,22 @@ const isEmpty = (value, options) => {
1863
1517
  * isEmptyTypeObject({ a: 1 }); // false
1864
1518
  */
1865
1519
  const isEmptyTypeObject = (value) => {
1866
- return typeof value === 'object' && (JSON.stringify(value) === '{}' || JSON.stringify(value) === '[]');
1867
- };
1868
- const isEmptyTypePrimitive = (value, options) => {
1869
- if (options?.ignoreCheckString && value === '') {
1520
+ try {
1521
+ if (isNil(value, { ignoreUnWrapSignal: true }) || value === '') {
1522
+ return true;
1523
+ }
1524
+ if (typeof value !== 'object') {
1525
+ return false;
1526
+ }
1527
+ if (Array.isArray(value)) {
1528
+ return value.length === 0;
1529
+ }
1530
+ return Object.keys(value).length === 0;
1531
+ }
1532
+ catch (error) {
1533
+ console.error(error);
1870
1534
  return false;
1871
1535
  }
1872
- return value === '' || isNil(value, { ignoreUnWrapSignal: true });
1873
1536
  };
1874
1537
  /**
1875
1538
  * Kiểm tra xem một giá trị có phải là null, rỗng, undefined hoặc 0 hay không
@@ -2031,6 +1694,7 @@ const get = (obj, path, defaultValue, keepLastValueIfSignal) => {
2031
1694
  obj = obj[key];
2032
1695
  continue;
2033
1696
  }
1697
+ obj = unwrapSignal(obj);
2034
1698
  if (isNil(obj) || !Object.prototype.hasOwnProperty.call(obj, key)) {
2035
1699
  return out(defaultValue);
2036
1700
  }
@@ -2446,6 +2110,353 @@ const generateInterface = (obj, interfaceName) => {
2446
2110
  return interfaceStr;
2447
2111
  };
2448
2112
 
2113
+ const deviceDetector = new DeviceDetector();
2114
+ const parser = new DOMParser();
2115
+ const quill = new Quill(document.createElement('div'));
2116
+ const getDeviceInfo = () => {
2117
+ return deviceDetector.parse(window.navigator.userAgent);
2118
+ };
2119
+ const getPlatFromBrowser = () => {
2120
+ const info = getDeviceInfo();
2121
+ return `${info.client?.name} ${info.client?.version}`;
2122
+ };
2123
+ const getEventNameHandleClick = (() => {
2124
+ const deviceInfo = getDeviceInfo();
2125
+ let nameEventHandleClick = deviceInfo.device?.type === 'desktop' ? 'click' : 'touchstart';
2126
+ if ('onpointerdown' in window) {
2127
+ nameEventHandleClick = 'pointerdown';
2128
+ }
2129
+ return nameEventHandleClick;
2130
+ })();
2131
+ const getDocumentByString = (htmlStr) => {
2132
+ return parser.parseFromString(htmlStr, 'text/html');
2133
+ };
2134
+ const cloneIBoundingClientRect = (rect) => {
2135
+ return {
2136
+ top: rect['top'],
2137
+ left: rect['left'],
2138
+ width: rect['width'],
2139
+ height: rect['height'],
2140
+ bottom: rect['bottom'],
2141
+ };
2142
+ };
2143
+ const setStylesElement = (el, styles) => {
2144
+ Object.keys(styles).forEach((key) => set(el, `style.${key}`, styles[key]));
2145
+ };
2146
+ const getViewport = (win = window) => {
2147
+ 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;
2148
+ return { width: w, height: h };
2149
+ };
2150
+ const setCaretPosition = (element, position) => {
2151
+ if (!element || !element.setSelectionRange) {
2152
+ return;
2153
+ }
2154
+ element.focus();
2155
+ element.setSelectionRange(position, position);
2156
+ };
2157
+ const checkViewInScreen = (container, element, elementScroll, maxTopLeft) => {
2158
+ if (!container || !element) {
2159
+ return false;
2160
+ }
2161
+ const rectContainer = cloneIBoundingClientRect(container.getBoundingClientRect());
2162
+ if (elementScroll) {
2163
+ const rectElementScroll = elementScroll.getBoundingClientRect();
2164
+ rectContainer['left'] = rectElementScroll.left;
2165
+ rectContainer['top'] = rectElementScroll.top;
2166
+ }
2167
+ const rectElement = element.getBoundingClientRect();
2168
+ const { left, top } = rectElement;
2169
+ const topContainer = rectContainer['top'];
2170
+ const leftContainer = rectContainer['left'];
2171
+ const widthContainer = rectContainer['width'];
2172
+ const heightContainer = rectContainer['height'];
2173
+ const scrollTopContainer = container.scrollTop;
2174
+ const scrollLeftContainer = container.scrollLeft;
2175
+ const limitTop = topContainer + scrollTopContainer + heightContainer;
2176
+ const limitLeft = leftContainer + scrollLeftContainer + widthContainer;
2177
+ const maxTopItem = top + (maxTopLeft?.top || 0);
2178
+ const maxLeftItem = left + (maxTopLeft?.left || 0);
2179
+ if (topContainer <= maxTopItem && maxTopItem <= limitTop && leftContainer <= maxLeftItem && maxLeftItem <= limitLeft) {
2180
+ return true;
2181
+ }
2182
+ return false;
2183
+ };
2184
+ const checkMouseOverInContainer = (mousePosition, element, rect) => {
2185
+ if (!element && !rect) {
2186
+ return false;
2187
+ }
2188
+ const rectElement = rect || element?.getBoundingClientRect() || {};
2189
+ const { left, top, width, height } = rectElement;
2190
+ const limitLeft = left + width;
2191
+ const limitTop = top + height;
2192
+ const { clientX, clientY } = mousePosition;
2193
+ if (left <= clientX && clientX <= limitLeft && top <= clientY && clientY <= limitTop) {
2194
+ return true;
2195
+ }
2196
+ return false;
2197
+ };
2198
+ const getDragEventByElement = (config) => {
2199
+ let timer;
2200
+ const addClass = () => {
2201
+ timer = setTimeout(() => {
2202
+ document.body.classList.add('!select-none');
2203
+ document.body.classList.add('!cursor-grabbing');
2204
+ }, 250);
2205
+ };
2206
+ const removeClass = () => {
2207
+ document.body.classList.remove('!select-none');
2208
+ document.body.classList.remove('!cursor-grabbing');
2209
+ clearTimeout(timer);
2210
+ };
2211
+ fromEvent(config.elementMouseDown, 'mouseleave')
2212
+ .pipe(tap(() => document.body.classList.remove('cursor-pointer')), takeUntil(config.onDestroy))
2213
+ .subscribe();
2214
+ fromEvent(config.elementMouseDown, 'mouseenter')
2215
+ .pipe(tap(() => document.body.classList.add('cursor-pointer')), takeUntil(config.onDestroy))
2216
+ .subscribe();
2217
+ const mouseDown = fromEvent(config.elementMouseDown, 'mousedown').pipe(tap((e) => {
2218
+ e.stopPropagation();
2219
+ addClass();
2220
+ config.functionMouseDown?.(e);
2221
+ }));
2222
+ const mouseup = fromEvent(config.elementMouseUp || document, 'mouseup').pipe(tap((e) => {
2223
+ e.stopPropagation();
2224
+ removeClass();
2225
+ config.functionMouseUp?.(e);
2226
+ }));
2227
+ const mousemove = fromEvent(config.elementMouseMove || document, 'mousemove').pipe(tap((e) => {
2228
+ e.stopPropagation();
2229
+ config.functionMouseMove?.(e);
2230
+ }), takeUntil(mouseup));
2231
+ return mouseDown.pipe(mergeMap((e) => (config.isStartWithMouseDownEvent ? mousemove.pipe(startWith(e)) : mousemove)), takeUntil(config.onDestroy), finalize(removeClass));
2232
+ };
2233
+ const getHTMLFromQuill = async (data, options) => {
2234
+ const { replaceNewLineTo = '<br>', replaceTagBRTo, replaceTags, replaceBrToDiv } = options || {};
2235
+ const delta = typeof data === 'string' ? await getDeltaFromHTML(data) : data;
2236
+ if (options?.functionReplaceDelta) {
2237
+ options.functionReplaceDelta(delta);
2238
+ }
2239
+ delta.ops.forEach((op) => {
2240
+ if (op.insert) {
2241
+ if (typeof op.insert === 'string') {
2242
+ if (replaceNewLineTo) {
2243
+ op.insert = op.insert.replace(/\n/g, replaceNewLineTo);
2244
+ }
2245
+ if (replaceTagBRTo) {
2246
+ op.insert = op.insert.replace(/<br>/g, replaceTagBRTo);
2247
+ }
2248
+ if (replaceTags?.length) {
2249
+ for (const tag of replaceTags) {
2250
+ op.insert = op.insert.replace(new RegExp(`<${tag.tag}>`, 'g'), `<${tag.replaceTo}>`);
2251
+ op.insert = op.insert.replace(new RegExp(`</${tag.tag}>`, 'g'), `</${tag.replaceTo}>`);
2252
+ }
2253
+ }
2254
+ }
2255
+ }
2256
+ });
2257
+ quill.setContents(delta);
2258
+ let htmlText = options?.getRootHtml ? quill.root.innerHTML : quill.root.firstElementChild?.innerHTML;
2259
+ if (replaceBrToDiv) {
2260
+ htmlText = convertHtmlToDivBlocks(htmlText || '');
2261
+ }
2262
+ return decodeEscapeHtml(htmlText || '');
2263
+ };
2264
+ const convertHtmlToDivBlocks = (html) => {
2265
+ const BREAK_TOKEN = '<<<BREAK>>>';
2266
+ // Bước 1: thay <br> thành token tạm
2267
+ const normalizedHtml = html.replace(/<br\s*\/?>/gi, BREAK_TOKEN);
2268
+ // Bước 2: tách theo token
2269
+ const parts = normalizedHtml.split(BREAK_TOKEN);
2270
+ const parser = new DOMParser();
2271
+ const divs = [];
2272
+ for (const raw of parts) {
2273
+ const trimmed = raw.trim();
2274
+ if (!trimmed)
2275
+ continue;
2276
+ // parse mỗi phần nhỏ như một document riêng
2277
+ const doc = parser.parseFromString(trimmed, 'text/html');
2278
+ const body = doc.body;
2279
+ // Lấy lại nội dung bên trong body
2280
+ divs.push(`<div>${body.innerHTML}</div>`);
2281
+ }
2282
+ return divs.join('');
2283
+ };
2284
+ const getDeltaFromHTML = async (html) => {
2285
+ quill.root.innerHTML = html;
2286
+ await lastValueFrom(timer(1000));
2287
+ return quill.getContents();
2288
+ };
2289
+ const processPasteData = async (e, config) => {
2290
+ const element = config.element;
2291
+ const files = e.clipboardData?.files;
2292
+ if (files?.length) {
2293
+ e.preventDefault();
2294
+ config.handlerPasteFile?.(files);
2295
+ config.callBack?.('file');
2296
+ return;
2297
+ }
2298
+ // Lưu selection TRƯỚC khi prevent default
2299
+ const selection = window.getSelection();
2300
+ let savedRange = null;
2301
+ if (selection && selection.rangeCount > 0) {
2302
+ const range = selection.getRangeAt(0);
2303
+ // Chỉ lưu nếu range nằm trong contentText element
2304
+ const container = range.commonAncestorContainer;
2305
+ const isInContentElement = element.contains(container.nodeType === Node.TEXT_NODE ? container.parentNode : container);
2306
+ if (isInContentElement) {
2307
+ savedRange = range.cloneRange();
2308
+ }
2309
+ }
2310
+ // Prevent default để tự xử lý paste
2311
+ e.preventDefault();
2312
+ // Sử dụng Quill để clean HTML content
2313
+ const htmlContent = e.clipboardData?.getData('text/html') || '';
2314
+ const plainText = e.clipboardData?.getData('text/plain') || '';
2315
+ let contentToInsert = (plainText || '').replace(/\n/g, '<br>');
2316
+ if (htmlContent) {
2317
+ contentToInsert = await getHTMLFromQuill(htmlContent);
2318
+ }
2319
+ if (!contentToInsert) {
2320
+ config.callBack?.('no-content');
2321
+ return;
2322
+ }
2323
+ if (savedRange) {
2324
+ insertContentWithRange(contentToInsert, savedRange, element);
2325
+ config.callBack?.('range');
2326
+ return;
2327
+ }
2328
+ element.innerHTML += contentToInsert;
2329
+ config.callBack?.('content');
2330
+ };
2331
+ const insertContentWithRange = (content, savedRange, element) => {
2332
+ const selection = window.getSelection();
2333
+ if (!selection) {
2334
+ // Fallback: append vào cuối
2335
+ element.innerHTML += content;
2336
+ return;
2337
+ }
2338
+ // Restore selection
2339
+ selection.removeAllRanges();
2340
+ selection.addRange(savedRange);
2341
+ // Xóa nội dung đã select (nếu có)
2342
+ savedRange.deleteContents();
2343
+ // Tạo document fragment từ HTML content
2344
+ const tempDiv = document.createElement('div');
2345
+ tempDiv.innerHTML = content;
2346
+ const fragment = document.createDocumentFragment();
2347
+ while (tempDiv.firstChild) {
2348
+ fragment.appendChild(tempDiv.firstChild);
2349
+ }
2350
+ // Insert fragment tại vị trí range
2351
+ savedRange.insertNode(fragment);
2352
+ // Di chuyển cursor đến cuối nội dung vừa insert
2353
+ if (fragment.lastChild) {
2354
+ savedRange.setStartAfter(fragment.lastChild);
2355
+ }
2356
+ savedRange.collapse(true);
2357
+ selection.removeAllRanges();
2358
+ selection.addRange(savedRange);
2359
+ };
2360
+
2361
+ class UtilsUrlSearchParams {
2362
+ static instance;
2363
+ params;
2364
+ constructor(paramString) {
2365
+ this.params = new Array();
2366
+ this.params.length = 0;
2367
+ this.params.push(...this.buildParams(paramString));
2368
+ }
2369
+ static getInstance() {
2370
+ if (!this.instance) {
2371
+ this.instance = new UtilsUrlSearchParams('');
2372
+ }
2373
+ return this.instance;
2374
+ }
2375
+ static toStringParamObject(params) {
2376
+ const paramsArr = Object.keys(params).map((key) => {
2377
+ return {
2378
+ key,
2379
+ value: params[key],
2380
+ };
2381
+ });
2382
+ return UtilsUrlSearchParams.ToString(paramsArr);
2383
+ }
2384
+ static ToString(params) {
2385
+ let paramsStr = '';
2386
+ params
2387
+ .sort((item1, item2) => item1.key.localeCompare(item2.key))
2388
+ .forEach((item) => {
2389
+ const { key, value } = item;
2390
+ paramsStr = `${paramsStr}${paramsStr ? '&' : ''}${key}=${value}`;
2391
+ });
2392
+ return paramsStr;
2393
+ }
2394
+ buildParams(paramString) {
2395
+ const paramsBuild = new Array();
2396
+ if (!paramString) {
2397
+ return paramsBuild;
2398
+ }
2399
+ const paramsError = paramString.split('?');
2400
+ if (paramsError.length > 2) {
2401
+ paramString = `${paramsError[0]}?${paramsError[1]}&${paramsError.slice(2).join('&')}`;
2402
+ }
2403
+ const params = paramString.replace(/\S+[?]/g, '').split('&');
2404
+ if (params) {
2405
+ params.forEach((param) => {
2406
+ if (param.indexOf('=') < 0) {
2407
+ return;
2408
+ }
2409
+ let [key, value] = param.split('=');
2410
+ key = key.replace(/[?&]+/g, '');
2411
+ value = decodeURIComponent(value.replace(/\+/g, ' '));
2412
+ if (!paramsBuild.some((param) => param.key === key)) {
2413
+ paramsBuild.push({ key, value });
2414
+ }
2415
+ });
2416
+ }
2417
+ return paramsBuild;
2418
+ }
2419
+ set(key, value) {
2420
+ const paramByKey = this.params.find((param) => param.key === key);
2421
+ if (!paramByKey) {
2422
+ this.params.push({ key, value: decodeURIComponent(value.replace(/\+/g, ' ')) });
2423
+ return;
2424
+ }
2425
+ paramByKey['value'] = value;
2426
+ }
2427
+ get(key) {
2428
+ return this.params.find((param) => param.key === key)?.value;
2429
+ }
2430
+ has(key) {
2431
+ return this.params.some((param) => param.key === key);
2432
+ }
2433
+ delete(key) {
2434
+ const index = this.params.findIndex((param) => param.key === key);
2435
+ if (index >= 0) {
2436
+ this.params.splice(index, 1);
2437
+ }
2438
+ }
2439
+ length() {
2440
+ return this.params.length;
2441
+ }
2442
+ toString(params) {
2443
+ params = params || this.params;
2444
+ return UtilsUrlSearchParams.ToString(params);
2445
+ }
2446
+ getParamObject() {
2447
+ const params = {};
2448
+ this.params.forEach((item) => {
2449
+ params[item.key] = item.value;
2450
+ });
2451
+ return params;
2452
+ }
2453
+ compareParams(param1, param2) {
2454
+ const params1 = this.toString(this.buildParams(param1));
2455
+ const params2 = this.toString(this.buildParams(param2));
2456
+ return params1 === params2;
2457
+ }
2458
+ }
2459
+
2449
2460
  const step = 20;
2450
2461
  const percent = 0.05;
2451
2462
  const colorStepContrastFromOrigin = (color, stepNumber) => {
@@ -2699,6 +2710,7 @@ class UtilsKeyCodeConstant {
2699
2710
  static CLOSE_SQUARE_BRACKET = 221;
2700
2711
  static SINGLE_QUOTE = 222;
2701
2712
  static MAC_META = 224;
2713
+ static BUFFERED = 229;
2702
2714
  }
2703
2715
 
2704
2716
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -2907,6 +2919,7 @@ const downloadImageFromELement = (imageElement, typeFileDownload, nameFile) => {
2907
2919
  link.click();
2908
2920
  };
2909
2921
 
2922
+ //NOTE: Đây là các hàm token inject để các module khác có thể inject vào để sử dụng
2910
2923
  const LINK_IMAGE_ERROR_TOKEN_INJECT = new InjectionToken('LINK_IMAGE_ERROR_TOKEN_INJECT');
2911
2924
  const PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT');
2912
2925
  const PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT');