@libs-ui/utils 0.2.325-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.
- package/dom.d.ts +1 -2
- package/esm2022/dom.mjs +4 -6
- package/esm2022/function-check-embed-frame.mjs +2 -1
- package/esm2022/helpers.mjs +2 -1
- package/esm2022/inject-token.mjs +2 -1
- package/esm2022/key-code.mjs +2 -1
- package/esm2022/xss-filter.mjs +1 -1
- package/fesm2022/libs-ui-utils.mjs +511 -510
- package/fesm2022/libs-ui-utils.mjs.map +1 -1
- package/key-code.d.ts +1 -0
- package/package.json +2 -2
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import DeviceDetector from 'device-detector-js';
|
|
2
2
|
import Quill from 'quill';
|
|
3
|
-
import {
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Kiểm tra xem đối tượng có phải là 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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
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
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
295
|
+
return obj instanceof TemplateRef || obj instanceof ElementRef;
|
|
306
296
|
};
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
|
313
|
+
return obj instanceof Date || isRegExp(obj) || obj instanceof HttpParams || ('hasOwnProperty' in obj && '__ngContext__' in obj);
|
|
320
314
|
};
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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 có phải là Async object không
|
|
323
|
+
*/
|
|
324
|
+
const isAsyncObject = (obj) => {
|
|
325
|
+
if (obj === null || typeof obj !== 'object') {
|
|
326
|
+
return false;
|
|
384
327
|
}
|
|
385
|
-
return
|
|
328
|
+
return obj instanceof Promise || obj instanceof Observable;
|
|
386
329
|
};
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
423
|
+
* @description Thiết lập key mã hóa
|
|
424
|
+
* @param value key mã hóa, độ dài bằng 24 hoặc 32 ký tự.
|
|
650
425
|
*/
|
|
651
|
-
const
|
|
652
|
-
if (
|
|
653
|
-
|
|
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
|
-
|
|
430
|
+
key = value;
|
|
656
431
|
};
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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 {
|
|
@@ -2043,6 +1694,7 @@ const get = (obj, path, defaultValue, keepLastValueIfSignal) => {
|
|
|
2043
1694
|
obj = obj[key];
|
|
2044
1695
|
continue;
|
|
2045
1696
|
}
|
|
1697
|
+
obj = unwrapSignal(obj);
|
|
2046
1698
|
if (isNil(obj) || !Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
2047
1699
|
return out(defaultValue);
|
|
2048
1700
|
}
|
|
@@ -2458,6 +2110,353 @@ const generateInterface = (obj, interfaceName) => {
|
|
|
2458
2110
|
return interfaceStr;
|
|
2459
2111
|
};
|
|
2460
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
|
+
|
|
2461
2460
|
const step = 20;
|
|
2462
2461
|
const percent = 0.05;
|
|
2463
2462
|
const colorStepContrastFromOrigin = (color, stepNumber) => {
|
|
@@ -2711,6 +2710,7 @@ class UtilsKeyCodeConstant {
|
|
|
2711
2710
|
static CLOSE_SQUARE_BRACKET = 221;
|
|
2712
2711
|
static SINGLE_QUOTE = 222;
|
|
2713
2712
|
static MAC_META = 224;
|
|
2713
|
+
static BUFFERED = 229;
|
|
2714
2714
|
}
|
|
2715
2715
|
|
|
2716
2716
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -2919,6 +2919,7 @@ const downloadImageFromELement = (imageElement, typeFileDownload, nameFile) => {
|
|
|
2919
2919
|
link.click();
|
|
2920
2920
|
};
|
|
2921
2921
|
|
|
2922
|
+
//NOTE: Đây là các hàm token inject để các module khác có thể inject vào để sử dụng
|
|
2922
2923
|
const LINK_IMAGE_ERROR_TOKEN_INJECT = new InjectionToken('LINK_IMAGE_ERROR_TOKEN_INJECT');
|
|
2923
2924
|
const PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STANDARD_CONFIG_DEFAULT_TOKEN_INJECT');
|
|
2924
2925
|
const PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT = new InjectionToken('PROCESS_BAR_STEPS_CONFIG_DEFAULT_TOKEN_INJECT');
|