@libs-ui/components-inputs-quill 0.2.348-7 → 0.2.350-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.
@@ -14,13 +14,15 @@ import { LibsUiComponentsSkeletonComponent } from '@libs-ui/components-skeleton'
14
14
  import { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';
15
15
  import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
16
16
  import { LibsUiNotificationService } from '@libs-ui/services-notification';
17
- import { patternUrl, convertFileToBase64_ObjectUrl, get, set, isNil, xssFilter, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MAX_LENGTH, getLabelBySizeFile, setStylesElement, UtilsKeyCodeConstant, isNearWhite, quill2xGetHtmlByDelta, patterProtocolUrl } from '@libs-ui/utils';
17
+ import { patternUrl, convertFileToBase64_ObjectUrl, decodeEscapeHtml, get, set, isNil, xssFilter, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MAX_LENGTH, getLabelBySizeFile, setStylesElement, UtilsKeyCodeConstant, isNearWhite, patterProtocolUrl } from '@libs-ui/utils';
18
18
  import * as i1 from '@ngx-translate/core';
19
19
  import { TranslateService, TranslateModule } from '@ngx-translate/core';
20
20
  import Quill from 'quill';
21
- import { Subject, lastValueFrom, timer, fromEvent, takeUntil, merge } from 'rxjs';
21
+ import { Subject, lastValueFrom as lastValueFrom$1, timer as timer$1, fromEvent, takeUntil, merge } from 'rxjs';
22
22
  import { LibsUiComponentsModalComponent } from '@libs-ui/components-modal';
23
23
  import { returnListObject } from '@libs-ui/services-http-request';
24
+ import { lastValueFrom } from 'rxjs/internal/lastValueFrom';
25
+ import { timer } from 'rxjs/internal/observable/timer';
24
26
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
25
27
  import Quill2x, { Delta } from 'quill2x';
26
28
  import ResizeModule from '@ssumo/quill-resize-module';
@@ -136,6 +138,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
136
138
  args: [{ selector: 'libs_ui-components-inputs-quill-upload_image', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [LibsUiComponentsModalComponent], template: "<libs_ui-components-modal\n [title]=\"'i18n_upload_image'\"\n [mode]=\"'center'\"\n [width]=\"'598px'\"\n [headerConfig]=\"{ ignoreHeaderTheme: true }\"\n [maxHeight]=\"'calc(100% - 100px)'\"\n [height]=\"'auto'\"\n [zIndex]=\"uploadImageConfig().zIndex || 1203\"\n [disable]=\"disable()\"\n [ignoreCommunicateMicroEvent]=\"ignoreCommunicateMicroEvent()\"\n [buttonsFooter]=\"buttonFooter()\"\n (outEvent)=\"handlerEventModal($event)\">\n <div class=\"libs-ui-modal-body-custom\">\n <!-- <libs-uicomponents-media-upload [zIndex]=\"zIndex+1\"\n [labelConfig]=\"labelConfig\"\n [multiple]=\"false\"\n [fileType]=\"'image'\"\n [doNotDelete]=\"true\"\n [maxImageSize]=\"maxImageSize || 1048576\"\n [limitFile]=\"10\"\n [validRequired]=\"{isRequired: true}\"\n (moChangeFile)=\"handlerChangeFile($event)\"\n (outFunctionsControl)=\"handlerFunctionsControl($event)\">\n </libs-uicomponents-media-upload> -->\n </div>\n</libs_ui-components-modal>\n" }]
137
139
  }] });
138
140
 
141
+ let quill = null;
142
+ let timeout$1 = undefined;
143
+ let timeout2 = undefined;
139
144
  const listDataAlign = () => {
140
145
  return [
141
146
  { key: '', icon: 'libs-ui-icon-align-left text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
@@ -608,6 +613,147 @@ const fontSizeWhiteList = () => [
608
613
  '71px',
609
614
  '72px',
610
615
  ];
616
+ const getHTMLFromDeltaOfQuill = (delta, options) => {
617
+ if (!quill) {
618
+ quill = new Quill(document.createElement('div'));
619
+ }
620
+ const { replaceNewLineTo = '<br>', replaceTagBRTo, replaceTags, replaceBrToDiv } = options || {};
621
+ if (options?.functionReplaceDelta) {
622
+ options.functionReplaceDelta(delta);
623
+ }
624
+ delta.ops.forEach((op) => {
625
+ if (op.insert) {
626
+ if (typeof op.insert === 'string') {
627
+ if (replaceNewLineTo) {
628
+ op.insert = op.insert.replace(/\n/g, replaceNewLineTo);
629
+ }
630
+ if (replaceTagBRTo) {
631
+ op.insert = op.insert.replace(/<br>/g, replaceTagBRTo);
632
+ }
633
+ if (replaceTags?.length) {
634
+ for (const tag of replaceTags) {
635
+ op.insert = op.insert.replace(new RegExp(`<${tag.tag}>`, 'g'), `<${tag.replaceTo}>`);
636
+ op.insert = op.insert.replace(new RegExp(`</${tag.tag}>`, 'g'), `</${tag.replaceTo}>`);
637
+ }
638
+ }
639
+ }
640
+ }
641
+ });
642
+ quill.setContents(delta);
643
+ let htmlText = options?.getRootHtml ? quill.root.innerHTML : quill.root.firstElementChild?.innerHTML;
644
+ if (replaceBrToDiv) {
645
+ htmlText = convertHtmlToDivBlocks(htmlText || '');
646
+ }
647
+ clearTimeout(timeout$1);
648
+ timeout$1 = setTimeout(() => {
649
+ quill = null;
650
+ }, 10000);
651
+ return decodeEscapeHtml(htmlText || '');
652
+ };
653
+ const convertHtmlToDivBlocks = (html) => {
654
+ const BREAK_TOKEN = '<<<BREAK>>>';
655
+ // Bước 1: thay <br> thành token tạm
656
+ const normalizedHtml = html.replace(/<br\s*\/?>/gi, BREAK_TOKEN);
657
+ // Bước 2: tách theo token
658
+ const parts = normalizedHtml.split(BREAK_TOKEN);
659
+ const parser = new DOMParser();
660
+ const divs = [];
661
+ for (const raw of parts) {
662
+ const trimmed = raw.trim();
663
+ if (!trimmed)
664
+ continue;
665
+ // parse mỗi phần nhỏ như một document riêng
666
+ const doc = parser.parseFromString(trimmed, 'text/html');
667
+ const body = doc.body;
668
+ // Lấy lại nội dung bên trong body
669
+ divs.push(`<div>${body.innerHTML}</div>`);
670
+ }
671
+ return divs.join('');
672
+ };
673
+ const getDeltaOfQuillFromHTML = async (html) => {
674
+ if (!quill) {
675
+ quill = new Quill(document.createElement('div'));
676
+ }
677
+ quill.root.innerHTML = html;
678
+ await lastValueFrom(timer(1000));
679
+ clearTimeout(timeout2);
680
+ timeout2 = setTimeout(() => {
681
+ quill = null;
682
+ }, 10000);
683
+ return quill.getContents();
684
+ };
685
+ const processPasteData = async (e, config) => {
686
+ const element = config.element;
687
+ const files = e.clipboardData?.files;
688
+ if (files?.length) {
689
+ e.preventDefault();
690
+ config.handlerPasteFile?.(files);
691
+ config.callBack?.('file');
692
+ return;
693
+ }
694
+ // Lưu selection TRƯỚC khi prevent default
695
+ const selection = window.getSelection();
696
+ let savedRange = null;
697
+ if (selection && selection.rangeCount > 0) {
698
+ const range = selection.getRangeAt(0);
699
+ // Chỉ lưu nếu range nằm trong contentText element
700
+ const container = range.commonAncestorContainer;
701
+ const isInContentElement = element.contains(container.nodeType === Node.TEXT_NODE ? container.parentNode : container);
702
+ if (isInContentElement) {
703
+ savedRange = range.cloneRange();
704
+ }
705
+ }
706
+ // Prevent default để tự xử lý paste
707
+ e.preventDefault();
708
+ // Sử dụng Quill để clean HTML content
709
+ const htmlContent = e.clipboardData?.getData('text/html') || '';
710
+ const plainText = e.clipboardData?.getData('text/plain') || '';
711
+ let contentToInsert = (plainText || '').replace(/\n/g, '<br>');
712
+ if (htmlContent) {
713
+ const delta = await getDeltaOfQuillFromHTML(htmlContent);
714
+ contentToInsert = getHTMLFromDeltaOfQuill(delta);
715
+ }
716
+ if (!contentToInsert) {
717
+ config.callBack?.('no-content');
718
+ return;
719
+ }
720
+ if (savedRange) {
721
+ insertContentWithRange(contentToInsert, savedRange, element);
722
+ config.callBack?.('range');
723
+ return;
724
+ }
725
+ element.innerHTML += contentToInsert;
726
+ config.callBack?.('content');
727
+ };
728
+ const insertContentWithRange = (content, savedRange, element) => {
729
+ const selection = window.getSelection();
730
+ if (!selection) {
731
+ // Fallback: append vào cuối
732
+ element.innerHTML += content;
733
+ return;
734
+ }
735
+ // Restore selection
736
+ selection.removeAllRanges();
737
+ selection.addRange(savedRange);
738
+ // Xóa nội dung đã select (nếu có)
739
+ savedRange.deleteContents();
740
+ // Tạo document fragment từ HTML content
741
+ const tempDiv = document.createElement('div');
742
+ tempDiv.innerHTML = content;
743
+ const fragment = document.createDocumentFragment();
744
+ while (tempDiv.firstChild) {
745
+ fragment.appendChild(tempDiv.firstChild);
746
+ }
747
+ // Insert fragment tại vị trí range
748
+ savedRange.insertNode(fragment);
749
+ // Di chuyển cursor đến cuối nội dung vừa insert
750
+ if (fragment.lastChild) {
751
+ savedRange.setStartAfter(fragment.lastChild);
752
+ }
753
+ savedRange.collapse(true);
754
+ selection.removeAllRanges();
755
+ selection.addRange(savedRange);
756
+ };
611
757
 
612
758
  const Block = Quill.import('blots/block');
613
759
  class QuillDivBlot extends Block {
@@ -811,7 +957,7 @@ class LibsUiComponentsInputsQuillComponent {
811
957
  }
812
958
  async ngAfterViewInit() {
813
959
  this.setHandlerFunction();
814
- await lastValueFrom(timer(this.timeDelayInit()));
960
+ await lastValueFrom$1(timer$1(this.timeDelayInit()));
815
961
  this.initQuill();
816
962
  this.setContent(get(this.item(), this.fieldNameBind(), ''));
817
963
  this.handlerTextChange();
@@ -826,7 +972,7 @@ class LibsUiComponentsInputsQuillComponent {
826
972
  fromEvent(window, 'resize')
827
973
  .pipe(takeUntil(this.onDestroy))
828
974
  .subscribe(() => this.calculatorToolbar(true));
829
- await lastValueFrom(timer(400));
975
+ await lastValueFrom$1(timer$1(400));
830
976
  this.display.set(true);
831
977
  const cursorSpan = this.quill()?.root.querySelector('.ql-cursor');
832
978
  if (cursorSpan) {
@@ -1181,12 +1327,12 @@ class LibsUiComponentsInputsQuillComponent {
1181
1327
  }
1182
1328
  async setContent(content) {
1183
1329
  const contentXssFilter = await xssFilter(this.removeUnwantedPart(content || ''));
1184
- await lastValueFrom(timer(1));
1330
+ await lastValueFrom$1(timer$1(1));
1185
1331
  (this.quill()?.root).innerHTML = contentXssFilter;
1186
1332
  if (!this.autoFocus()) {
1187
1333
  return;
1188
1334
  }
1189
- await lastValueFrom(timer(100));
1335
+ await lastValueFrom$1(timer$1(100));
1190
1336
  const target = this.quillEditorEl()?.nativeElement.querySelector('[contenteditable="true"]');
1191
1337
  const contentScroll = this.heightAuto() ? this.wrapperEditorEl()?.nativeElement : target;
1192
1338
  contentScroll.scrollTop = contentScroll.scrollHeight;
@@ -1264,7 +1410,7 @@ class LibsUiComponentsInputsQuillComponent {
1264
1410
  if (!isNil(index)) {
1265
1411
  this.quill()?.insertText(index, content, Quill.sources.USER);
1266
1412
  if (focusLast) {
1267
- await lastValueFrom(timer(0));
1413
+ await lastValueFrom$1(timer$1(0));
1268
1414
  this.quill()?.setSelection(index + content.length, 0, Quill.sources.USER);
1269
1415
  }
1270
1416
  }
@@ -1522,6 +1668,9 @@ class CustomImage2xBlot extends ImageFormat {
1522
1668
 
1523
1669
  /* eslint-disable @typescript-eslint/no-explicit-any */
1524
1670
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
1671
+ let quill2x = null;
1672
+ let timeout = undefined;
1673
+ let timeout1 = undefined;
1525
1674
  let startCell = null;
1526
1675
  let endCell = null;
1527
1676
  let isSelecting = false;
@@ -1950,13 +2099,65 @@ const registerQuill2x = () => {
1950
2099
  Quill.register('modules/resize', ResizeModule);
1951
2100
  [...iconList()].forEach((element) => set(icons, element.key, `<span class="${element.icon} ${element.key === 'unLink' ? 'hover:text-[#ee2d41] ' : 'hover:text-[var(--libs-ui-color-light-1)]'} text-[16px] text-[#6a7383]"></span>`));
1952
2101
  };
1953
- const isEmptyQuill = (quill) => {
2102
+ const isEmptyQuill2x = (quill) => {
1954
2103
  const rootElement = quill?.root;
1955
2104
  if (!rootElement)
1956
2105
  return true;
1957
2106
  const html = rootElement.innerHTML.trim();
1958
2107
  return html === '<p><br></p>' || html === '<p></p>' || html === '' || html === '<p><span class="ql-cursor"></span><br></p>' || rootElement.classList.contains('ql-blank');
1959
2108
  };
2109
+ const getHTMLFromDeltaOfQuill2x = (delta, options) => {
2110
+ if (!delta || !delta.ops || !delta.ops.length) {
2111
+ return '';
2112
+ }
2113
+ if (!quill2x) {
2114
+ quill2x = new Quill2x(document.createElement('div'));
2115
+ }
2116
+ const { replaceNewLineTo = '<br>', replaceTagBRTo, replaceTags, replaceBrToDiv } = options || {};
2117
+ if (options?.functionReplaceDelta) {
2118
+ options.functionReplaceDelta(delta);
2119
+ }
2120
+ delta.ops.forEach((op) => {
2121
+ if (op.insert) {
2122
+ if (typeof op.insert === 'string') {
2123
+ if (replaceNewLineTo) {
2124
+ op.insert = op.insert.replace(/\n/g, replaceNewLineTo);
2125
+ }
2126
+ if (replaceTagBRTo) {
2127
+ op.insert = op.insert.replace(/<br>/g, replaceTagBRTo);
2128
+ }
2129
+ if (replaceTags?.length) {
2130
+ for (const tag of replaceTags) {
2131
+ op.insert = op.insert.replace(new RegExp(`<${tag.tag}>`, 'g'), `<${tag.replaceTo}>`);
2132
+ op.insert = op.insert.replace(new RegExp(`</${tag.tag}>`, 'g'), `</${tag.replaceTo}>`);
2133
+ }
2134
+ }
2135
+ }
2136
+ }
2137
+ });
2138
+ quill2x.setContents(delta);
2139
+ let htmlText = options?.getRootHtml ? quill2x.root.innerHTML : quill2x.root.firstElementChild?.innerHTML;
2140
+ if (replaceBrToDiv) {
2141
+ htmlText = convertHtmlToDivBlocks(htmlText || '');
2142
+ }
2143
+ clearTimeout(timeout);
2144
+ timeout = setTimeout(() => {
2145
+ quill2x = null;
2146
+ }, 10000);
2147
+ return decodeEscapeHtml(htmlText || '');
2148
+ };
2149
+ const getDeltaOfQuill2xFromHTML = async (html) => {
2150
+ if (!quill2x) {
2151
+ quill2x = new Quill2x(document.createElement('div'));
2152
+ }
2153
+ quill2x.root.innerHTML = html;
2154
+ await lastValueFrom$1(timer$1(1000));
2155
+ clearTimeout(timeout1);
2156
+ timeout1 = setTimeout(() => {
2157
+ quill2x = null;
2158
+ }, 10000);
2159
+ return quill2x.getContents();
2160
+ };
1960
2161
 
1961
2162
  /* eslint-disable @typescript-eslint/no-explicit-any */
1962
2163
  class LibsUiComponentsInputsQuill2xComponent {
@@ -2200,12 +2401,12 @@ class LibsUiComponentsInputsQuill2xComponent {
2200
2401
  .subscribe(() => this.calculatorToolbar(true));
2201
2402
  this.outFunctionsControl.emit(this.FunctionsControl);
2202
2403
  await this.setContent(this.item()[this.fieldBind()]);
2203
- timer(this.autoFocus() ? 0 : 250)
2404
+ timer$1(this.autoFocus() ? 0 : 250)
2204
2405
  .pipe(takeUntilDestroyed(this.destroyRef))
2205
2406
  .subscribe(() => this.displayEditor.set(true));
2206
2407
  this.setStyleForContent();
2207
2408
  if (this.autoFocus()) {
2208
- timer(this.focusTimerOnInit() + 250)
2409
+ timer$1(this.focusTimerOnInit() + 250)
2209
2410
  .pipe(takeUntilDestroyed(this.destroyRef))
2210
2411
  .subscribe(() => this.focus());
2211
2412
  }
@@ -2223,7 +2424,7 @@ class LibsUiComponentsInputsQuill2xComponent {
2223
2424
  rootElement.classList.remove('ql-blank');
2224
2425
  return;
2225
2426
  }
2226
- if (isEmptyQuill(this.quill)) {
2427
+ if (isEmptyQuill2x(this.quill)) {
2227
2428
  rootElement.classList.add('ql-blank');
2228
2429
  return;
2229
2430
  }
@@ -2351,7 +2552,7 @@ class LibsUiComponentsInputsQuill2xComponent {
2351
2552
  if (!isNil(index)) {
2352
2553
  this.quill?.insertText(index, content, Quill2x.sources.USER);
2353
2554
  if (focusLastValueInsert) {
2354
- await lastValueFrom(timer(0));
2555
+ await lastValueFrom$1(timer$1(0));
2355
2556
  this.quill?.setSelection(index + content.length, 0, Quill2x.sources.USER);
2356
2557
  }
2357
2558
  }
@@ -2427,7 +2628,7 @@ class LibsUiComponentsInputsQuill2xComponent {
2427
2628
  return;
2428
2629
  e.preventDefault();
2429
2630
  const delta = this.quill.getContents(sel.index, sel.length);
2430
- const html2x = quill2xGetHtmlByDelta(delta);
2631
+ const html2x = getHTMLFromDeltaOfQuill2x(delta);
2431
2632
  if (e.clipboardData) {
2432
2633
  e.clipboardData.setData('text/html', html2x);
2433
2634
  }
@@ -2493,7 +2694,7 @@ class LibsUiComponentsInputsQuill2xComponent {
2493
2694
  const item = this.item();
2494
2695
  const fieldNameBind = this.fieldBind();
2495
2696
  const value = get(item, fieldNameBind);
2496
- if (validRequired && validRequired.isRequired && !this.readonly() && isEmptyQuill(this.quill)) {
2697
+ if (validRequired && validRequired.isRequired && !this.readonly() && isEmptyQuill2x(this.quill)) {
2497
2698
  this.messageError.set(validRequired.message || ERROR_MESSAGE_EMPTY_VALID);
2498
2699
  this.outMessageError.emit(this.messageError());
2499
2700
  return false;
@@ -2858,5 +3059,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2858
3059
  * Generated bundle index. Do not edit.
2859
3060
  */
2860
3061
 
2861
- export { LibsUiComponentsInputsQuill2xComponent, LibsUiComponentsInputsQuillComponent };
3062
+ export { LibsUiComponentsInputsQuill2xComponent, LibsUiComponentsInputsQuillComponent, convertHtmlToDivBlocks, getDeltaOfQuill2xFromHTML, getDeltaOfQuillFromHTML, getHTMLFromDeltaOfQuill, getHTMLFromDeltaOfQuill2x, insertContentWithRange, isEmptyQuill2x, processPasteData };
2862
3063
  //# sourceMappingURL=libs-ui-components-inputs-quill.mjs.map