@libs-ui/components-inputs-quill2x 0.2.351-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.
@@ -0,0 +1,2141 @@
1
+ import { NgStyle, NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { signal, input, output, ChangeDetectionStrategy, Component, computed, model, viewChild, inject, DestroyRef, ChangeDetectorRef, effect, untracked } from '@angular/core';
4
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
5
+ import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
6
+ import { LibsUiComponentsButtonsSelectColorComponent } from '@libs-ui/components-buttons-select-color';
7
+ import { LibsUiComponentsDropdownComponent } from '@libs-ui/components-dropdown';
8
+ import { LibsUiComponentsEmojiComponent } from '@libs-ui/components-inputs-emoji';
9
+ import { LibsUiComponentsInputsMentionDirective } from '@libs-ui/components-inputs-mention';
10
+ import { LibsUiComponentsInputsValidComponent } from '@libs-ui/components-inputs-valid';
11
+ import { LibsUiComponentsLabelComponent } from '@libs-ui/components-label';
12
+ import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
13
+ import { LibsUiComponentsScrollOverlayDirective } from '@libs-ui/components-scroll-overlay';
14
+ import { LibsUiComponentsSkeletonComponent } from '@libs-ui/components-skeleton';
15
+ import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
16
+ import { LibsUiNotificationService } from '@libs-ui/services-notification';
17
+ import { patternUrl, convertFileToBase64_ObjectUrl, setStylesElement, set, decodeEscapeHtml, get, UtilsKeyCodeConstant, isNearWhite, isNil, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MAX_LENGTH, xssFilter, getLabelBySizeFile, patterProtocolUrl } from '@libs-ui/utils';
18
+ import * as i1 from '@ngx-translate/core';
19
+ import { TranslateService, TranslateModule } from '@ngx-translate/core';
20
+ import Quill2x, { Delta } from 'quill2x';
21
+ import { fromEvent, lastValueFrom, timer, merge } from 'rxjs';
22
+ import { LibsUiComponentsModalComponent } from '@libs-ui/components-modal';
23
+ import { returnListObject } from '@libs-ui/services-http-request';
24
+ import ResizeModule from '@ssumo/quill-resize-module';
25
+
26
+ class LibsUiComponentsInputsQuillLinkComponent {
27
+ dataLink = signal({ title: '', link: '' });
28
+ patternLink = signal(patternUrl());
29
+ inputValidFunctionControl = signal([]);
30
+ zIndex = input(1200);
31
+ title = input();
32
+ link = input();
33
+ ignoreCommunicateMicroEvent = input();
34
+ outClose = output();
35
+ outSaveLink = output();
36
+ ngOnInit() {
37
+ const title = this.title();
38
+ const link = this.link();
39
+ if (title || link) {
40
+ this.dataLink.update((item) => ({ ...item, title: title || '', link: link || '' }));
41
+ }
42
+ }
43
+ async handlerFunctionsControl(event) {
44
+ this.inputValidFunctionControl.update((items) => [...items, event]);
45
+ }
46
+ async validate() {
47
+ let valid = true;
48
+ for (const control of this.inputValidFunctionControl()) {
49
+ if (!(await control.checkIsValid())) {
50
+ valid = false;
51
+ }
52
+ }
53
+ return valid;
54
+ }
55
+ async handlerEvent(event) {
56
+ if (event !== 'agree') {
57
+ this.outClose.emit();
58
+ return;
59
+ }
60
+ if (await this.validate()) {
61
+ this.outSaveLink.emit(this.dataLink());
62
+ this.outClose.emit();
63
+ }
64
+ }
65
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
66
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: LibsUiComponentsInputsQuillLinkComponent, isStandalone: true, selector: "libs_ui-components-inputs-quill-link", inputs: { zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, link: { classPropertyName: "link", publicName: "link", isSignal: true, isRequired: false, transformFunction: null }, ignoreCommunicateMicroEvent: { classPropertyName: "ignoreCommunicateMicroEvent", publicName: "ignoreCommunicateMicroEvent", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { outClose: "outClose", outSaveLink: "outSaveLink" }, ngImport: i0, template: "<libs_ui-components-modal\n [title]=\"'i18n_insert_link'\"\n [mode]=\"'center'\"\n [height]=\"'auto'\"\n [width]=\"'598px'\"\n [ignoreCommunicateMicroEvent]=\"ignoreCommunicateMicroEvent()\"\n [headerConfig]=\"{ ignoreHeaderTheme: true }\"\n [zIndex]=\"zIndex()\"\n (outEvent)=\"handlerEvent($event)\">\n <div class=\"libs-ui-modal-body-custom\">\n <libs_ui-components-inputs-valid\n [labelConfig]=\"{ labelLeft: 'i18n_content', required: true }\"\n [(item)]=\"dataLink\"\n [fieldNameBind]=\"'title'\"\n [placeholder]=\"'i18n_import_content'\"\n [validRequired]=\"{ isRequired: true }\"\n (outFunctionsControl)=\"handlerFunctionsControl($event)\" />\n <div class=\"mt-[16px]\">\n <libs_ui-components-inputs-valid\n [labelConfig]=\"{ labelLeft: 'Link', required: true }\"\n [(item)]=\"dataLink\"\n [fieldNameBind]=\"'link'\"\n [placeholder]=\"'https://example.com'\"\n [validRequired]=\"{ isRequired: true }\"\n [validPattern]=\"[{ pattern: patternLink(), valuePatternShowError: false }]\"\n [keySelectedUnitLeft]=\"'1'\"\n [unitsLeft]=\"[{ id: '1', label: 'URL' }]\"\n (outFunctionsControl)=\"handlerFunctionsControl($event)\" />\n </div>\n </div>\n</libs_ui-components-modal>\n", dependencies: [{ kind: "component", type: LibsUiComponentsModalComponent, selector: "libs_ui-components-modal", inputs: ["show", "mode", "isBackdropTransparent", "isBackgroundTransparentModal", "isSizeBackdropByWidthHeightInput", "hasShadowBoxWhenHiddenBackDropTransparent", "classIncludeModalWrapper", "zIndex", "width", "height", "maxWidth", "maxHeight", "minWidth", "isFullScreen", "disable", "ignoreCommunicateMicroEvent", "escapeKeyboardCloseModal", "headerConfig", "bodyConfig", "footerConfig", "buttonsFooter", "title", "titleUseXssFilter", "titleUseTooltip", "titleUseInnerText"], outputs: ["showChange", "widthChange", "heightChange", "maxWidthChange", "maxHeightChange", "minWidthChange", "disableChange", "buttonsFooterChange", "outScrollContent", "outEvent", "outFunctionControl"] }, { kind: "component", type: LibsUiComponentsInputsValidComponent, selector: "libs_ui-components-inputs-valid", inputs: ["item", "labelConfig", "emitEmptyInDataTypeNumber", "ignoreBlockInputMaxValue", "fieldNameBind", "showCount", "typeComponentSelectItem", "valueComponentSelectItem", "disableComponentSelectItem", "tagInput", "dataType", "typeInput", "modeInput", "resetAutoCompletePassword", "textAreaEnterNotNewLine", "fixedFloat", "acceptNegativeValue", "valueUpDownNumber", "ignoreWidthInput100", "classIncludeInput", "classContainerInput", "readonly", "disable", "noBorder", "backgroundNone", "useColorModeExist", "placeholder", "keepPlaceholderOnly", "classContainerBottomInput", "autoRemoveEmoji", "defaultHeight", "maxHeightTextArea", "minHeightTextArea", "ignoreShowError", "borderError", "iconLeftClass", "popoverContentIconLeft", "iconRightClass", "popoverContentIconRight", "zIndexPopoverContent", "unitsLeft", "configUnitLeft", "keySelectedUnitLeft", "unitsRight", "configUnitRight", "keySelectedUnitRight", "maxValueNumber", "minValueNumber", "ignoreContentLeft", "ignoreContentRight", "isBaselineStyle", "valuePatternShowError", "validPattern", "validRequired", "validMinLength", "validMinValue", "validMaxValue", "validMaxLength", "functionValid", "maxLength", "positionMessageErrorStartInput", "classInclude", "resize", "templateLeftBottomInput", "templateRightBottomInput", "onlyAcceptNegativeValue", "autoAddZeroLessThan10InTypeInt", "maxLengthNumberCount", "classMessageErrorInclude", "ignoreStopPropagationEvent", "ignoreUnitRightClassReadOnly", "paddingRightCustomSpecific", "focusTimeOut", "debounceTimeValidate"], outputs: ["itemChange", "outValueChange", "outSelect", "outIconLeft", "outIconRight", "outClickButtonLabel", "outSwitchEventLabel", "outLabelRightClick", "outEnterInputEvent", "outHeightAreaChange", "outFunctionsControl", "outFocusAndBlur", "outChangeValueByButtonUpDown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
67
+ }
68
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillLinkComponent, decorators: [{
69
+ type: Component,
70
+ args: [{ selector: 'libs_ui-components-inputs-quill-link', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [LibsUiComponentsModalComponent, LibsUiComponentsInputsValidComponent], template: "<libs_ui-components-modal\n [title]=\"'i18n_insert_link'\"\n [mode]=\"'center'\"\n [height]=\"'auto'\"\n [width]=\"'598px'\"\n [ignoreCommunicateMicroEvent]=\"ignoreCommunicateMicroEvent()\"\n [headerConfig]=\"{ ignoreHeaderTheme: true }\"\n [zIndex]=\"zIndex()\"\n (outEvent)=\"handlerEvent($event)\">\n <div class=\"libs-ui-modal-body-custom\">\n <libs_ui-components-inputs-valid\n [labelConfig]=\"{ labelLeft: 'i18n_content', required: true }\"\n [(item)]=\"dataLink\"\n [fieldNameBind]=\"'title'\"\n [placeholder]=\"'i18n_import_content'\"\n [validRequired]=\"{ isRequired: true }\"\n (outFunctionsControl)=\"handlerFunctionsControl($event)\" />\n <div class=\"mt-[16px]\">\n <libs_ui-components-inputs-valid\n [labelConfig]=\"{ labelLeft: 'Link', required: true }\"\n [(item)]=\"dataLink\"\n [fieldNameBind]=\"'link'\"\n [placeholder]=\"'https://example.com'\"\n [validRequired]=\"{ isRequired: true }\"\n [validPattern]=\"[{ pattern: patternLink(), valuePatternShowError: false }]\"\n [keySelectedUnitLeft]=\"'1'\"\n [unitsLeft]=\"[{ id: '1', label: 'URL' }]\"\n (outFunctionsControl)=\"handlerFunctionsControl($event)\" />\n </div>\n </div>\n</libs_ui-components-modal>\n" }]
71
+ }] });
72
+
73
+ class LibsUiComponentsInputsQuillUploadImageComponent {
74
+ buttonFooter = signal([
75
+ {
76
+ type: 'button-third',
77
+ label: 'i18n_cancel',
78
+ action: async () => this.outClose.emit(),
79
+ },
80
+ {
81
+ label: 'i18n_save',
82
+ action: () => this.handlerSaveImage(),
83
+ },
84
+ ]);
85
+ disable = signal(false);
86
+ ignoreCommunicateMicroEvent = input();
87
+ // private mediaFile =signal<IMediaFile | undefined>(undefined);
88
+ // private mediaUploadFunctionControl =signal<IMediaUploadBaseFunctionControlEvent | undefined>(undefined);
89
+ uploadImageConfig = input.required();
90
+ outClose = output();
91
+ // protected handlerFunctionsControl(event: IMediaUploadBaseFunctionControlEvent) {
92
+ // this.mediaUploadFunctionControl.set(event);
93
+ // }
94
+ // protected handlerChangeFile(event: IChangeFile) {
95
+ // this.mediaFile.set(event.files[0]);
96
+ // }
97
+ async handlerSaveImage() {
98
+ // if (!await this.mediaUploadFunctionControl()?.checkIsValid()) {
99
+ // return;
100
+ // }
101
+ // if (this.mediaFile?.origin_url) {
102
+ // this.moClose.emit(this.mediaFile.origin_url);
103
+ // return;
104
+ // }
105
+ // const body: IOptionsUploadMedia = {
106
+ // do_not_delete: true,
107
+ // file: this.mediaFile?.file,
108
+ // option: 'thumb',
109
+ // size: [{ 'width': '200', 'height': '200' }]
110
+ // };
111
+ // this.disable = true;
112
+ // try {
113
+ // const params = new HttpParamsRequest({ fromObject: { pem: BUILD_PEM_OBJECT({ isCheck: 0, action: DefineConstants.PERMISSION_ACTION_VIEW, pathCheck: '/other' }) } });
114
+ // const res = await this.mediaService.uploadImage(params, body);
115
+ // this.moClose.emit(res.data?.url);
116
+ // } catch (error) {
117
+ // console.log(error);
118
+ // this.pushMessageService.showCompTypeText('i18n_notification_manipulation_not_success', undefined, undefined, { timeRemove: 2000, type: 'error' });
119
+ // this.moClose.emit();
120
+ // } finally {
121
+ // this.disable = false;
122
+ // }
123
+ }
124
+ handlerEventModal(event) {
125
+ if (event === 'close') {
126
+ this.outClose.emit();
127
+ }
128
+ }
129
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillUploadImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
130
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.14", type: LibsUiComponentsInputsQuillUploadImageComponent, isStandalone: true, selector: "libs_ui-components-inputs-quill-upload_image", inputs: { ignoreCommunicateMicroEvent: { classPropertyName: "ignoreCommunicateMicroEvent", publicName: "ignoreCommunicateMicroEvent", isSignal: true, isRequired: false, transformFunction: null }, uploadImageConfig: { classPropertyName: "uploadImageConfig", publicName: "uploadImageConfig", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { outClose: "outClose" }, ngImport: i0, 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", dependencies: [{ kind: "component", type: LibsUiComponentsModalComponent, selector: "libs_ui-components-modal", inputs: ["show", "mode", "isBackdropTransparent", "isBackgroundTransparentModal", "isSizeBackdropByWidthHeightInput", "hasShadowBoxWhenHiddenBackDropTransparent", "classIncludeModalWrapper", "zIndex", "width", "height", "maxWidth", "maxHeight", "minWidth", "isFullScreen", "disable", "ignoreCommunicateMicroEvent", "escapeKeyboardCloseModal", "headerConfig", "bodyConfig", "footerConfig", "buttonsFooter", "title", "titleUseXssFilter", "titleUseTooltip", "titleUseInnerText"], outputs: ["showChange", "widthChange", "heightChange", "maxWidthChange", "maxHeightChange", "minWidthChange", "disableChange", "buttonsFooterChange", "outScrollContent", "outEvent", "outFunctionControl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
131
+ }
132
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillUploadImageComponent, decorators: [{
133
+ type: Component,
134
+ 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" }]
135
+ }] });
136
+
137
+ const listDataAlign = () => {
138
+ return [
139
+ { key: '', icon: 'libs-ui-icon-align-left text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
140
+ { key: 'right', icon: 'libs-ui-icon-align-right text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
141
+ { key: 'center', icon: 'libs-ui-icon-align-center text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
142
+ { key: 'justify', icon: 'libs-ui-icon-align-justify text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
143
+ ];
144
+ };
145
+ const listFont = () => {
146
+ return [
147
+ { key: '', label: 'Sans Serif' },
148
+ { key: 'serif', label: 'Serif' },
149
+ { key: 'monospace', label: 'Monospace' },
150
+ ];
151
+ };
152
+ const listHeader = () => {
153
+ return [
154
+ { key: '', label: 'Normal' },
155
+ { key: '1', label: 'Heading 1' },
156
+ { key: '2', label: 'Heading 2' },
157
+ { key: '3', label: 'Heading 3' },
158
+ { key: '4', label: 'Heading 4' },
159
+ { key: '5', label: 'Heading 5' },
160
+ { key: '6', label: 'Heading 6' },
161
+ ];
162
+ };
163
+ const listConfigAlign = () => {
164
+ return {
165
+ type: 'text',
166
+ autoSelectFirstItem: true,
167
+ httpRequestData: signal({
168
+ objectInstance: returnListObject(listDataAlign()),
169
+ functionName: 'list',
170
+ argumentsValue: [],
171
+ }),
172
+ configTemplateText: signal({
173
+ fieldKey: 'key',
174
+ getClassItem: () => '!px-[8px] !py-[4px]',
175
+ getValue: (item) => `<i class='flex ${item.icon}'></i>`,
176
+ ignoreIconSelected: true,
177
+ }),
178
+ };
179
+ };
180
+ const listConfigFont = () => {
181
+ return {
182
+ type: 'text',
183
+ autoSelectFirstItem: true,
184
+ httpRequestData: signal({
185
+ objectInstance: returnListObject(listFont()),
186
+ functionName: 'list',
187
+ argumentsValue: [],
188
+ }),
189
+ configTemplateText: signal({
190
+ fieldKey: 'key',
191
+ getValue: (item) => item.label,
192
+ ignoreIconSelected: true,
193
+ }),
194
+ };
195
+ };
196
+ const listConfigHeader = () => {
197
+ return {
198
+ type: 'text',
199
+ autoSelectFirstItem: true,
200
+ httpRequestData: signal({
201
+ objectInstance: returnListObject(listHeader()),
202
+ functionName: 'list',
203
+ argumentsValue: [],
204
+ }),
205
+ configTemplateText: signal({
206
+ fieldKey: 'key',
207
+ getValue: (item) => {
208
+ switch (item?.key) {
209
+ case '':
210
+ return `<span class="libs-ui-font-h5r">${item?.label}</span>`;
211
+ case '1':
212
+ return `<span class="libs-ui-font-h1r">${item?.label}</span>`;
213
+ case '2':
214
+ return `<span class="libs-ui-font-h2r">${item?.label}</span>`;
215
+ case '3':
216
+ return `<span class="libs-ui-font-h3r">${item?.label}</span>`;
217
+ case '4':
218
+ return `<span class="libs-ui-font-h4r">${item?.label}</span>`;
219
+ case '5':
220
+ return `<span class="libs-ui-font-h5r">${item?.label}</span>`;
221
+ case '6':
222
+ return `<span class="libs-ui-font-h6r">${item?.label}</span>`;
223
+ }
224
+ return item?.label;
225
+ },
226
+ ignoreIconSelected: true,
227
+ }),
228
+ };
229
+ };
230
+ const toolBarOptions = (mode, hasIconImage, hasIconPersonalize) => {
231
+ return [
232
+ {
233
+ type: 'undo',
234
+ classInclude: 'mr-[8px]',
235
+ mode: ['all', 'default', 'basic'],
236
+ width: 24,
237
+ },
238
+ {
239
+ type: 'redo',
240
+ mode: ['all', 'default', 'basic'],
241
+ width: 32,
242
+ },
243
+ {
244
+ type: 'line',
245
+ mode: ['all', 'default', 'basic'],
246
+ width: 1,
247
+ },
248
+ {
249
+ type: 'header',
250
+ classInclude: 'ml-[8px]',
251
+ mode: ['all'],
252
+ width: 146,
253
+ },
254
+ {
255
+ type: 'fontFamily',
256
+ classInclude: 'ml-[8px]',
257
+ mode: ['all', 'default'],
258
+ width: 136,
259
+ },
260
+ {
261
+ type: 'fontSize',
262
+ classInclude: 'ml-[8px] mr-[8px]',
263
+ mode: ['all', 'default'],
264
+ width: 82,
265
+ },
266
+ {
267
+ type: 'line',
268
+ mode: ['all', 'default'],
269
+ width: 1,
270
+ },
271
+ {
272
+ type: 'color',
273
+ mode: ['all', 'default', 'basic'],
274
+ width: 32,
275
+ },
276
+ {
277
+ type: 'background',
278
+ mode: ['all', 'default'],
279
+ width: 32,
280
+ },
281
+ {
282
+ type: 'line',
283
+ mode: ['all', 'default', 'basic'],
284
+ width: 1,
285
+ },
286
+ {
287
+ type: 'bold',
288
+ mode: ['all', 'default', 'basic'],
289
+ width: 32,
290
+ },
291
+ {
292
+ type: 'italic',
293
+ mode: ['all', 'default', 'basic'],
294
+ width: 32,
295
+ },
296
+ {
297
+ type: 'underline',
298
+ mode: ['all', 'default', 'basic'],
299
+ width: 32,
300
+ },
301
+ {
302
+ type: 'strikeThrough',
303
+ mode: ['all', 'default'],
304
+ width: 32,
305
+ },
306
+ {
307
+ type: 'line',
308
+ mode: ['all', 'default', 'basic'],
309
+ width: 1,
310
+ },
311
+ {
312
+ type: 'script.sub',
313
+ mode: ['all'],
314
+ width: 32,
315
+ },
316
+ {
317
+ type: 'script.super',
318
+ mode: ['all'],
319
+ width: 32,
320
+ },
321
+ {
322
+ type: 'line',
323
+ mode: ['all'],
324
+ width: 1,
325
+ },
326
+ {
327
+ type: 'adjust',
328
+ mode: ['all', 'default'],
329
+ width: 52,
330
+ },
331
+ {
332
+ type: 'line',
333
+ mode: ['all', 'default'],
334
+ width: 32,
335
+ },
336
+ {
337
+ type: 'indentIncrease',
338
+ mode: ['all', 'default'],
339
+ width: 32,
340
+ },
341
+ {
342
+ type: 'indentDecrease',
343
+ mode: ['all', 'default'],
344
+ width: 32,
345
+ },
346
+ {
347
+ type: 'listBulleted',
348
+ mode: ['all', 'default'],
349
+ width: 32,
350
+ },
351
+ {
352
+ type: 'listNumbered',
353
+ mode: ['all', 'default'],
354
+ width: 32,
355
+ },
356
+ {
357
+ type: 'listChecked',
358
+ mode: ['all'],
359
+ width: 32,
360
+ },
361
+ {
362
+ type: 'line',
363
+ mode: ['all', 'default'],
364
+ width: 1,
365
+ },
366
+ {
367
+ type: 'personalize',
368
+ mode: ['all', 'default'],
369
+ width: 32,
370
+ },
371
+ {
372
+ type: 'blockquote',
373
+ mode: ['all', 'default'],
374
+ width: 32,
375
+ },
376
+ {
377
+ type: 'link',
378
+ mode: ['all', 'default', 'basic'],
379
+ width: 32,
380
+ },
381
+ {
382
+ type: 'unLink',
383
+ mode: ['all', 'default', 'basic'],
384
+ width: 32,
385
+ },
386
+ {
387
+ type: 'image',
388
+ mode: ['all', 'default'],
389
+ width: 32,
390
+ },
391
+ {
392
+ type: 'video',
393
+ mode: ['all'],
394
+ width: 32,
395
+ },
396
+ {
397
+ type: 'emoji',
398
+ classInclude: 'ml-[8px]',
399
+ mode: ['all', 'default'],
400
+ width: 24,
401
+ },
402
+ { type: 'table', width: 32, mode: ['all'] },
403
+ ].filter((item) => {
404
+ if (!item.mode?.includes(mode)) {
405
+ return false;
406
+ }
407
+ if (item.type === 'image') {
408
+ return hasIconImage;
409
+ }
410
+ if (item.type === 'personalize') {
411
+ return hasIconPersonalize;
412
+ }
413
+ return true;
414
+ });
415
+ };
416
+ const iconList = () => {
417
+ return [
418
+ {
419
+ key: 'bold',
420
+ icon: 'libs-ui-icon-editor-bold',
421
+ },
422
+ {
423
+ key: 'italic',
424
+ icon: 'libs-ui-icon-editor-italic',
425
+ },
426
+ {
427
+ key: 'underline',
428
+ icon: 'libs-ui-icon-editor-underlined',
429
+ },
430
+ {
431
+ key: 'image',
432
+ icon: 'libs-ui-icon-image-solid',
433
+ },
434
+ {
435
+ key: 'link',
436
+ icon: 'libs-ui-icon-link',
437
+ },
438
+ {
439
+ key: 'undo',
440
+ icon: 'libs-ui-icon-undo',
441
+ },
442
+ {
443
+ key: 'redo',
444
+ icon: 'libs-ui-icon-redo',
445
+ },
446
+ {
447
+ key: 'strike',
448
+ icon: 'libs-ui-icon-editor-strike-through',
449
+ },
450
+ {
451
+ key: 'blockquote',
452
+ icon: 'libs-ui-icon-quote',
453
+ },
454
+ {
455
+ key: 'ordered',
456
+ icon: 'libs-ui-icon-list-numbered',
457
+ },
458
+ {
459
+ key: 'bullet',
460
+ icon: 'libs-ui-icon-list-bulleted',
461
+ },
462
+ {
463
+ key: 'background',
464
+ icon: 'libs-ui-icon-editor-color-background',
465
+ },
466
+ {
467
+ key: 'color',
468
+ icon: 'libs-ui-icon-editor-color-text',
469
+ },
470
+ {
471
+ key: 'indent.-1',
472
+ icon: 'libs-ui-icon-indent-decrease',
473
+ },
474
+ {
475
+ key: 'indent.+1',
476
+ icon: 'libs-ui-icon-indent-increase',
477
+ },
478
+ {
479
+ key: 'align.',
480
+ icon: 'align-quill libs-ui-icon-align-left',
481
+ },
482
+ {
483
+ key: 'align.center',
484
+ icon: 'align-quill libs-ui-icon-align-center',
485
+ },
486
+ {
487
+ key: 'align.right',
488
+ icon: 'align-quill libs-ui-icon-align-right',
489
+ },
490
+ {
491
+ key: 'align.justify',
492
+ icon: 'align-quill libs-ui-icon-align-justify',
493
+ },
494
+ {
495
+ key: 'unLink',
496
+ icon: 'libs-ui-icon-link-broken',
497
+ },
498
+ {
499
+ key: 'emoji',
500
+ icon: 'libs-ui-icon-face-smile',
501
+ },
502
+ {
503
+ key: 'table',
504
+ icon: 'libs-ui-icon-table',
505
+ },
506
+ {
507
+ key: 'check',
508
+ icon: 'libs-ui-icon-task-list',
509
+ },
510
+ {
511
+ key: 'video',
512
+ icon: 'libs-ui-icon-video-outline',
513
+ },
514
+ ];
515
+ };
516
+ const uploadImageConfigDefault = () => {
517
+ return {
518
+ modeCustom: false,
519
+ zIndex: 1202,
520
+ showIcon: true,
521
+ maxImageSize: 5 * 1024 * 1024,
522
+ onlyAcceptImageHttpsLink: false,
523
+ functionUploadImage: async (files) => {
524
+ const data = [];
525
+ for (const file of files) {
526
+ data.push(await convertFileToBase64_ObjectUrl(file));
527
+ }
528
+ return data;
529
+ },
530
+ };
531
+ };
532
+ const linkDefault = () => {
533
+ return {
534
+ title: '',
535
+ url: '',
536
+ range: {
537
+ index: 0,
538
+ length: 0,
539
+ },
540
+ };
541
+ };
542
+ const fontSizeWhiteList = () => [
543
+ '8px',
544
+ '9px',
545
+ '10px',
546
+ '11px',
547
+ '12px',
548
+ '13px',
549
+ '14px',
550
+ '15px',
551
+ '16px',
552
+ '17px',
553
+ '18px',
554
+ '19px',
555
+ '20px',
556
+ '21px',
557
+ '22px',
558
+ '23px',
559
+ '24px',
560
+ '25px',
561
+ '26px',
562
+ '27px',
563
+ '28px',
564
+ '29px',
565
+ '30px',
566
+ '31px',
567
+ '32px',
568
+ '33px',
569
+ '34px',
570
+ '35px',
571
+ '36px',
572
+ '37px',
573
+ '38px',
574
+ '39px',
575
+ '40px',
576
+ '41px',
577
+ '42px',
578
+ '43px',
579
+ '44px',
580
+ '45px',
581
+ '46px',
582
+ '47px',
583
+ '48px',
584
+ '49px',
585
+ '50px',
586
+ '51px',
587
+ '52px',
588
+ '53px',
589
+ '54px',
590
+ '55px',
591
+ '56px',
592
+ '57px',
593
+ '58px',
594
+ '59px',
595
+ '60px',
596
+ '61px',
597
+ '62px',
598
+ '63px',
599
+ '64px',
600
+ '65px',
601
+ '66px',
602
+ '67px',
603
+ '68px',
604
+ '69px',
605
+ '70px',
606
+ '71px',
607
+ '72px',
608
+ ];
609
+
610
+ /* eslint-disable @typescript-eslint/no-explicit-any */
611
+ const Embed = Quill2x.import('blots/embed');
612
+ class QuillMention2xBlot extends Embed {
613
+ static blotName = 'mention';
614
+ static tagName = 'span';
615
+ static className = 'libs-ui-quill-mention';
616
+ static create(data) {
617
+ const node = super.create();
618
+ node.innerText += data.value;
619
+ node.setAttribute('id', data.id);
620
+ node.setAttribute('feId', data.feId);
621
+ node.setAttribute('value', data.value);
622
+ return node;
623
+ }
624
+ static value(domNode) {
625
+ return {
626
+ id: domNode.getAttribute('id'),
627
+ feId: domNode.getAttribute('feId'),
628
+ value: domNode.getAttribute('value'),
629
+ };
630
+ }
631
+ attach() {
632
+ super.attach();
633
+ if (!this.mounted) {
634
+ this.mounted = true;
635
+ this.clickHandler = this.getClickHandler;
636
+ this.domNode.addEventListener('click', this.clickHandler, false);
637
+ }
638
+ }
639
+ getClickHandler(event) {
640
+ event.stopPropagation();
641
+ }
642
+ }
643
+
644
+ const ImageFormat = Quill2x.import('formats/image');
645
+ class CustomImage2xBlot extends ImageFormat {
646
+ static create(value) {
647
+ const node = super.create(value);
648
+ node.classList.add('libs-ui-quill-format-image');
649
+ return node;
650
+ }
651
+ formats() {
652
+ return {}; // Tránh Quill tự động thêm thẻ `<span>`
653
+ }
654
+ }
655
+
656
+ /* eslint-disable @typescript-eslint/no-explicit-any */
657
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
658
+ let quill2x = null;
659
+ let timeout = undefined;
660
+ let timeout1 = undefined;
661
+ let startCell = null;
662
+ let endCell = null;
663
+ let isSelecting = false;
664
+ //region Function Table
665
+ const createTableSelector = (quill, anchorEl, translate) => {
666
+ // Đóng popup cũ nếu còn tồn tại
667
+ document.querySelectorAll('.manual-table-creator').forEach((el) => el.parentElement?.removeChild(el));
668
+ const popup = document.createElement('div');
669
+ popup.className = 'manual-table-creator';
670
+ popup.style.position = 'absolute';
671
+ popup.style.background = '#fff';
672
+ popup.style.border = '1px solid #ddd';
673
+ popup.style.borderRadius = '6px';
674
+ popup.style.boxShadow = '0 6px 16px rgba(0,0,0,0.12)';
675
+ popup.style.padding = '12px';
676
+ popup.style.zIndex = '10000';
677
+ popup.style.minWidth = '200px';
678
+ setStylesElement(popup, {
679
+ position: 'absolute',
680
+ background: '#fff',
681
+ border: '1px solid #ddd',
682
+ borderRadius: '6px',
683
+ boxShadow: '0 6px 16px rgba(0,0,0,0.12)',
684
+ padding: '12px',
685
+ zIndex: '10000',
686
+ minWidth: '200px',
687
+ });
688
+ const info = document.createElement('div');
689
+ info.textContent = translate.instant('i18n_select_cell');
690
+ setStylesElement(info, {
691
+ fontSize: '12px',
692
+ color: '#333',
693
+ marginBottom: '10px',
694
+ textAlign: 'center',
695
+ fontWeight: '600',
696
+ });
697
+ popup.appendChild(info);
698
+ const grid = document.createElement('div');
699
+ setStylesElement(grid, {
700
+ display: 'grid',
701
+ gridTemplateColumns: 'repeat(8, 18px)',
702
+ gap: '4px',
703
+ });
704
+ const max = 8;
705
+ for (let row = 1; row <= max; row++) {
706
+ for (let col = 1; col <= max; col++) {
707
+ const cell = document.createElement('div');
708
+ cell.style.width = '18px';
709
+ cell.style.height = '18px';
710
+ cell.style.border = '1px solid #e5e7eb';
711
+ cell.style.background = '#fff';
712
+ cell.style.cursor = 'pointer';
713
+ cell.addEventListener('mouseenter', () => {
714
+ info.textContent = `${row} × ${col}`;
715
+ Array.from(grid.children).forEach((el, idx) => {
716
+ const rr = Math.floor(idx / max) + 1;
717
+ const cc = (idx % max) + 1;
718
+ el.style.background = rr <= row && cc <= col ? '#dbeafe' : '#fff';
719
+ el.style.borderColor = rr <= row && cc <= col ? '#93c5fd' : '#e5e7eb';
720
+ });
721
+ });
722
+ cell.addEventListener('click', () => {
723
+ try {
724
+ quill.focus();
725
+ const tableModule = quill.getModule('table');
726
+ if (tableModule && typeof tableModule.insertTable === 'function') {
727
+ tableModule.insertTable(row, col);
728
+ getAllTablesWithSpan(quill);
729
+ }
730
+ }
731
+ finally {
732
+ if (document.body.contains(popup)) {
733
+ document.body.removeChild(popup);
734
+ }
735
+ document.removeEventListener('click', onOutsideClick, true);
736
+ }
737
+ });
738
+ grid.appendChild(cell);
739
+ }
740
+ }
741
+ popup.appendChild(grid);
742
+ const placePopup = () => {
743
+ const rect = anchorEl?.getBoundingClientRect();
744
+ const top = rect ? rect.bottom + window.scrollY + 6 : 100;
745
+ const left = rect ? rect.left + window.scrollX : 100;
746
+ popup.style.top = `${top}px`;
747
+ popup.style.left = `${left}px`;
748
+ };
749
+ const onOutsideClick = (ev) => {
750
+ if (!popup.contains(ev.target)) {
751
+ if (document.body.contains(popup)) {
752
+ document.body.removeChild(popup);
753
+ }
754
+ document.removeEventListener('click', onOutsideClick, true);
755
+ }
756
+ };
757
+ document.body.appendChild(popup);
758
+ placePopup();
759
+ setTimeout(() => document.addEventListener('click', onOutsideClick, true));
760
+ };
761
+ const createContextMenuOptionTable = (quill, translate) => {
762
+ let lastContextIndex = null;
763
+ const closeAnyMenu = () => {
764
+ document.querySelectorAll('.manual-table-menu').forEach((el) => el.parentElement?.removeChild(el));
765
+ };
766
+ const runWithSelection = (fn) => {
767
+ try {
768
+ quill.focus();
769
+ if (typeof lastContextIndex === 'number') {
770
+ quill.setSelection(lastContextIndex, 0, 'silent');
771
+ }
772
+ }
773
+ catch {
774
+ /* no-op: giữ yên nếu không thể set selection */
775
+ }
776
+ fn();
777
+ };
778
+ const createMenuItem = (label, onClick) => {
779
+ const item = document.createElement('div');
780
+ item.textContent = label;
781
+ item.style.padding = '6px 12px';
782
+ item.style.cursor = 'pointer';
783
+ item.addEventListener('click', () => {
784
+ runWithSelection(onClick);
785
+ closeAnyMenu();
786
+ });
787
+ item.addEventListener('mouseenter', () => {
788
+ item.style.background = '#f5f5f5';
789
+ });
790
+ item.addEventListener('mouseleave', () => {
791
+ item.style.background = 'transparent';
792
+ });
793
+ return item;
794
+ };
795
+ const openContextMenu = (pageX, pageY) => {
796
+ closeAnyMenu();
797
+ const menu = document.createElement('div');
798
+ menu.className = 'manual-table-menu';
799
+ menu.style.position = 'absolute';
800
+ menu.style.top = `${pageY}px`;
801
+ menu.style.left = `${pageX}px`;
802
+ menu.style.background = '#fff';
803
+ menu.style.border = '1px solid #ccc';
804
+ menu.style.borderRadius = '4px';
805
+ menu.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
806
+ menu.style.zIndex = '10001';
807
+ menu.style.padding = '4px';
808
+ menu.style.minWidth = '180px';
809
+ const tableModule = quill.getModule('table');
810
+ const safeCall = (method, ...args) => {
811
+ if (method === 'mergeCells') {
812
+ const [startCell, endCell] = args;
813
+ mergeCells(startCell, endCell);
814
+ closeAnyMenu();
815
+ return;
816
+ }
817
+ if (method === 'splitCells') {
818
+ const [startCell] = args;
819
+ splitCells(startCell);
820
+ closeAnyMenu();
821
+ return;
822
+ }
823
+ if (tableModule && typeof tableModule[method] === 'function') {
824
+ tableModule[method](...args);
825
+ getAllTablesWithSpan(quill);
826
+ }
827
+ };
828
+ menu.appendChild(createMenuItem(translate.instant('i18n_add_new_row_above'), () => safeCall('insertRowAbove')));
829
+ menu.appendChild(createMenuItem(translate.instant('i18n_add_new_row_below'), () => safeCall('insertRowBelow')));
830
+ menu.appendChild(createMenuItem(translate.instant('i18n_add_new_column_left'), () => safeCall('insertColumnLeft')));
831
+ menu.appendChild(createMenuItem(translate.instant('i18n_add_new_column_right'), () => safeCall('insertColumnRight')));
832
+ const divider = document.createElement('div');
833
+ divider.style.height = '1px';
834
+ divider.style.background = '#eee';
835
+ divider.style.margin = '4px 0';
836
+ menu.appendChild(divider);
837
+ if (startCell && endCell) {
838
+ const startCellStore = startCell;
839
+ const endCellStore = endCell;
840
+ const colspan = parseInt(startCellStore.getAttribute('colspan') || '1', 10);
841
+ const rowspan = parseInt(startCellStore.getAttribute('rowspan') || '1', 10);
842
+ let totalMenu = 0;
843
+ if (startCell !== endCell) {
844
+ menu.appendChild(createMenuItem(translate.instant('i18n_merge_cells'), () => safeCall('mergeCells', startCellStore, endCellStore)));
845
+ totalMenu++;
846
+ }
847
+ if (startCell === endCell && (colspan > 1 || rowspan > 1)) {
848
+ menu.appendChild(createMenuItem(translate.instant('i18n_split_cells'), () => safeCall('splitCells', startCellStore)));
849
+ totalMenu++;
850
+ }
851
+ if (totalMenu > 0) {
852
+ const divider2 = document.createElement('div');
853
+ divider2.style.height = '1px';
854
+ divider2.style.background = '#eee';
855
+ divider2.style.margin = '4px 0';
856
+ menu.appendChild(divider2);
857
+ }
858
+ }
859
+ menu.appendChild(createMenuItem(translate.instant('i18n_delete_row'), () => safeCall('deleteRow')));
860
+ menu.appendChild(createMenuItem(translate.instant('i18n_delete_column'), () => safeCall('deleteColumn')));
861
+ menu.appendChild(createMenuItem(translate.instant('i18n_delete_table'), () => safeCall('deleteTable')));
862
+ document.body.appendChild(menu);
863
+ const onOutside = (ev) => {
864
+ if (!menu.contains(ev.target) || ev.target.getElementsByTagName('table').length < 0) {
865
+ closeAnyMenu();
866
+ document.removeEventListener('click', onOutside, true);
867
+ }
868
+ };
869
+ setTimeout(() => document.addEventListener('click', onOutside, true));
870
+ const onScroll = () => {
871
+ closeAnyMenu();
872
+ window.removeEventListener('scroll', onScroll, true);
873
+ window.removeEventListener('resize', onScroll, true);
874
+ };
875
+ window.addEventListener('scroll', onScroll, true);
876
+ window.addEventListener('resize', onScroll, true);
877
+ };
878
+ fromEvent(quill.root, 'contextmenu').subscribe((ev) => {
879
+ const target = ev.target;
880
+ const isCell = !!target.closest('td,th,table,.ql-table');
881
+ if (!isCell)
882
+ return;
883
+ ev.preventDefault();
884
+ try {
885
+ // Ưu tiên lấy index từ blot tương ứng node
886
+ const QuillAny = Quill2x;
887
+ const blot = QuillAny?.find?.(target);
888
+ if (blot && typeof blot.offset === 'function') {
889
+ lastContextIndex = blot.offset(quill.scroll);
890
+ }
891
+ else {
892
+ // Fallback: đặt selection hiện tại nếu không xác định được
893
+ const sel = quill.getSelection(true);
894
+ lastContextIndex = sel ? sel.index : 0;
895
+ }
896
+ }
897
+ catch {
898
+ const sel = quill.getSelection(true);
899
+ lastContextIndex = sel ? sel.index : 0;
900
+ }
901
+ quill.focus();
902
+ openContextMenu(ev.pageX, ev.pageY);
903
+ });
904
+ };
905
+ // ===============================
906
+ // Utils: clear và select cells
907
+ // ===============================
908
+ const clearSelection = () => {
909
+ document.querySelectorAll('.selected-cell').forEach((c) => {
910
+ c.classList.remove('selected-cell');
911
+ });
912
+ };
913
+ const selectRangeCells = (start, end) => {
914
+ clearSelection();
915
+ const table = start.closest('table');
916
+ if (!table || table !== end.closest('table'))
917
+ return;
918
+ const rows = Array.from(table.querySelectorAll('tr'));
919
+ const startRow = rows.indexOf(start.parentElement);
920
+ const endRow = rows.indexOf(end.parentElement);
921
+ const minRow = Math.min(startRow, endRow);
922
+ const maxRow = Math.max(startRow, endRow);
923
+ const startCol = Array.from(start.parentElement.children).indexOf(start);
924
+ const endCol = Array.from(end.parentElement.children).indexOf(end);
925
+ const minCol = Math.min(startCol, endCol);
926
+ const maxCol = Math.max(startCol, endCol);
927
+ for (let r = minRow; r <= maxRow; r++) {
928
+ const row = rows[r];
929
+ const cells = Array.from(row.children);
930
+ for (let c = minCol; c <= maxCol; c++) {
931
+ cells[c].classList.add('selected-cell');
932
+ }
933
+ }
934
+ };
935
+ // ===============================
936
+ // Enable selection trên table
937
+ // ===============================
938
+ function enableTableSelection(container) {
939
+ container.addEventListener('mousedown', (e) => {
940
+ const target = e.target;
941
+ if ((target.tagName === 'TD' && e.button !== 2) || (e.button === 2 && !target.classList.contains('selected-cell'))) {
942
+ clearSelection();
943
+ isSelecting = true;
944
+ startCell = target;
945
+ endCell = target;
946
+ selectRangeCells(startCell, endCell);
947
+ }
948
+ });
949
+ container.addEventListener('mousemove', (e) => {
950
+ if (!isSelecting)
951
+ return;
952
+ const target = e.target;
953
+ if (target.tagName === 'TD' && startCell) {
954
+ endCell = target;
955
+ selectRangeCells(startCell, endCell);
956
+ }
957
+ });
958
+ container.addEventListener('mouseup', () => {
959
+ isSelecting = false;
960
+ });
961
+ }
962
+ const onOutside = (ev) => {
963
+ const target = ev.target;
964
+ if (target.tagName !== 'TD' && target.tagName !== 'TR' && target.tagName !== 'TBODY' && target.tagName !== 'THEAD' && target.tagName !== 'TABLE') {
965
+ isSelecting = false;
966
+ startCell = null;
967
+ endCell = null;
968
+ clearSelection();
969
+ }
970
+ };
971
+ document.addEventListener('click', onOutside, true);
972
+ // ===============================
973
+ // Merge cells
974
+ // ===============================
975
+ const mergeCells = (startCell, endCell) => {
976
+ const startCellIndex = startCell.getAttribute('index');
977
+ const endCellIndex = endCell.getAttribute('index');
978
+ const startCellRow = parseInt(startCellIndex?.split('-')[0] || '0', 10);
979
+ const endCellRow = parseInt(endCellIndex?.split('-')[0] || '0', 10);
980
+ const startCellCol = parseInt(startCellIndex?.split('-')[1] || '0', 10);
981
+ const endCellCol = parseInt(endCellIndex?.split('-')[1] || '0', 10);
982
+ const minRow = Math.min(startCellRow, endCellRow);
983
+ const maxRow = Math.max(startCellRow, endCellRow);
984
+ const minCol = Math.min(startCellCol, endCellCol);
985
+ const maxCol = Math.max(startCellCol, endCellCol);
986
+ const rows = startCell.closest('table')?.querySelectorAll('tr');
987
+ rows?.forEach((rowEl, rowIndex) => {
988
+ if (rowIndex < minRow || rowIndex > maxRow)
989
+ return;
990
+ const cells = rowEl.querySelectorAll('td, th');
991
+ cells.forEach((cellEl, colIndex) => {
992
+ if (colIndex < minCol || colIndex > maxCol)
993
+ return;
994
+ if (rowIndex === minRow && colIndex === minCol) {
995
+ cellEl.setAttribute('colspan', String(maxCol - minCol + 1));
996
+ cellEl.setAttribute('rowspan', String(maxRow - minRow + 1));
997
+ return;
998
+ }
999
+ cellEl.classList.add('hidden');
1000
+ });
1001
+ });
1002
+ };
1003
+ // ===============================
1004
+ // Split cells
1005
+ // ===============================
1006
+ function splitCells(cellSplit) {
1007
+ const index = cellSplit.getAttribute('index');
1008
+ let colSpan = parseInt(cellSplit.getAttribute('colspan') || '1', 10);
1009
+ let rowSpan = parseInt(cellSplit.getAttribute('rowspan') || '1', 10);
1010
+ const totalCellSplit = rowSpan * colSpan;
1011
+ let countCellSplit = 0;
1012
+ const rowStart = parseInt(index?.split('-')[0] || '0', 10);
1013
+ const colStart = parseInt(index?.split('-')[1] || '0', 10);
1014
+ const colIncrement = new Set();
1015
+ const rowIncrement = new Set();
1016
+ const rows = cellSplit.closest('table')?.querySelectorAll('tr');
1017
+ rows?.forEach((rowEl, rowIndex) => {
1018
+ if (rowIndex < rowStart)
1019
+ return;
1020
+ const cells = rowEl.querySelectorAll('td, th');
1021
+ cells.forEach((cellEl, colIndex) => {
1022
+ if (rowIndex < rowStart || colIndex < colStart || rowStart + rowSpan < rowIndex || colStart + colSpan < colIndex || countCellSplit >= totalCellSplit)
1023
+ return;
1024
+ if (rowIndex === rowStart && colIndex === colStart) {
1025
+ countCellSplit++;
1026
+ cellEl.setAttribute('colspan', '1');
1027
+ cellEl.setAttribute('rowspan', '1');
1028
+ return;
1029
+ }
1030
+ if (!cellEl.classList.contains('hidden')) {
1031
+ if (!rowIncrement.has(rowIndex)) {
1032
+ rowSpan++;
1033
+ }
1034
+ if (!colIncrement.has(colIndex)) {
1035
+ colSpan++;
1036
+ }
1037
+ rowIncrement.add(rowIndex);
1038
+ colIncrement.add(colIndex);
1039
+ return;
1040
+ }
1041
+ cellEl.classList.remove('hidden');
1042
+ countCellSplit++;
1043
+ });
1044
+ });
1045
+ }
1046
+ const getAllTablesWithSpan = (quill) => {
1047
+ const root = quill.root;
1048
+ // Tìm tất cả table trong quill
1049
+ const tableEls = root.querySelectorAll('table');
1050
+ tableEls.forEach((tableEl) => {
1051
+ const rows = tableEl.querySelectorAll('tr');
1052
+ rows.forEach((rowEl, rIndex) => {
1053
+ rowEl.setAttribute('index', `${rIndex}`);
1054
+ const cells = rowEl.querySelectorAll('td, th');
1055
+ cells.forEach((cellEl, cIndex) => {
1056
+ cellEl.setAttribute('index', `${rIndex}-${cIndex}`);
1057
+ const rowspan = parseInt(cellEl.getAttribute('rowspan') || '1', 10);
1058
+ const colspan = parseInt(cellEl.getAttribute('colspan') || '1', 10);
1059
+ cellEl.setAttribute('rowspan', String(rowspan));
1060
+ cellEl.setAttribute('colspan', String(colspan));
1061
+ });
1062
+ });
1063
+ });
1064
+ };
1065
+ //-------------------------------------------
1066
+ let register = false;
1067
+ const registerQuill2x = () => {
1068
+ if (register)
1069
+ return;
1070
+ register = true;
1071
+ const Quill = Quill2x;
1072
+ const size = Quill.import('attributors/style/size');
1073
+ const alignStyle = Quill.import('attributors/style/align');
1074
+ const italic = Quill.import('formats/italic');
1075
+ const bold = Quill.import('formats/bold');
1076
+ const icons = Quill.import('ui/icons');
1077
+ size.whitelist = fontSizeWhiteList();
1078
+ italic.tagName = 'i';
1079
+ bold.tagName = 'b';
1080
+ Quill.register(bold, true);
1081
+ Quill.register(italic, true);
1082
+ Quill.register(size, true);
1083
+ Quill.register(alignStyle, true);
1084
+ Quill.register(QuillMention2xBlot, true);
1085
+ Quill.register(CustomImage2xBlot, true);
1086
+ Quill.register('modules/resize', ResizeModule);
1087
+ [...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>`));
1088
+ };
1089
+ const isEmptyQuill2x = (quill) => {
1090
+ const rootElement = quill?.root;
1091
+ if (!rootElement)
1092
+ return true;
1093
+ const html = rootElement.innerHTML.trim();
1094
+ return html === '<p><br></p>' || html === '<p></p>' || html === '' || html === '<p><span class="ql-cursor"></span><br></p>' || rootElement.classList.contains('ql-blank');
1095
+ };
1096
+ const getHTMLFromDeltaOfQuill2x = (delta, options) => {
1097
+ if (!delta || !delta.ops || !delta.ops.length) {
1098
+ return '';
1099
+ }
1100
+ if (!quill2x) {
1101
+ quill2x = new Quill2x(document.createElement('div'));
1102
+ }
1103
+ const { replaceNewLineTo = '<br>', replaceTagBRTo, replaceTags, replaceBrToDiv } = options || {};
1104
+ if (options?.functionReplaceDelta) {
1105
+ options.functionReplaceDelta(delta);
1106
+ }
1107
+ delta.ops.forEach((op) => {
1108
+ if (op.insert) {
1109
+ if (typeof op.insert === 'string') {
1110
+ if (replaceNewLineTo) {
1111
+ op.insert = op.insert.replace(/\n/g, replaceNewLineTo);
1112
+ }
1113
+ if (replaceTagBRTo) {
1114
+ op.insert = op.insert.replace(/<br>/g, replaceTagBRTo);
1115
+ }
1116
+ if (replaceTags?.length) {
1117
+ for (const tag of replaceTags) {
1118
+ op.insert = op.insert.replace(new RegExp(`<${tag.tag}>`, 'g'), `<${tag.replaceTo}>`);
1119
+ op.insert = op.insert.replace(new RegExp(`</${tag.tag}>`, 'g'), `</${tag.replaceTo}>`);
1120
+ }
1121
+ }
1122
+ }
1123
+ }
1124
+ });
1125
+ quill2x.setContents(delta);
1126
+ let htmlText = options?.getRootHtml ? quill2x.root.innerHTML : quill2x.root.firstElementChild?.innerHTML;
1127
+ if (replaceBrToDiv) {
1128
+ htmlText = convertHtmlToDivBlocks(htmlText || '');
1129
+ }
1130
+ clearTimeout(timeout);
1131
+ timeout = setTimeout(() => {
1132
+ quill2x = null;
1133
+ }, 10000);
1134
+ return decodeEscapeHtml(htmlText || '');
1135
+ };
1136
+ const getDeltaOfQuill2xFromHTML = async (html) => {
1137
+ if (!quill2x) {
1138
+ quill2x = new Quill2x(document.createElement('div'));
1139
+ }
1140
+ quill2x.root.innerHTML = html;
1141
+ await lastValueFrom(timer(1000));
1142
+ clearTimeout(timeout1);
1143
+ timeout1 = setTimeout(() => {
1144
+ quill2x = null;
1145
+ }, 10000);
1146
+ return quill2x.getContents();
1147
+ };
1148
+ const convertHtmlToDivBlocks = (html) => {
1149
+ const BREAK_TOKEN = '<<<BREAK>>>';
1150
+ // Bước 1: thay <br> thành token tạm
1151
+ const normalizedHtml = html.replace(/<br\s*\/?>/gi, BREAK_TOKEN);
1152
+ // Bước 2: tách theo token
1153
+ const parts = normalizedHtml.split(BREAK_TOKEN);
1154
+ const parser = new DOMParser();
1155
+ const divs = [];
1156
+ for (const raw of parts) {
1157
+ const trimmed = raw.trim();
1158
+ if (!trimmed)
1159
+ continue;
1160
+ // parse mỗi phần nhỏ như một document riêng
1161
+ const doc = parser.parseFromString(trimmed, 'text/html');
1162
+ const body = doc.body;
1163
+ // Lấy lại nội dung bên trong body
1164
+ divs.push(`<div>${body.innerHTML}</div>`);
1165
+ }
1166
+ return divs.join('');
1167
+ };
1168
+ const processPasteData = async (e, config) => {
1169
+ const element = config.element;
1170
+ const files = e.clipboardData?.files;
1171
+ if (files?.length) {
1172
+ e.preventDefault();
1173
+ config.handlerPasteFile?.(files);
1174
+ config.callBack?.('file');
1175
+ return;
1176
+ }
1177
+ // Lưu selection TRƯỚC khi prevent default
1178
+ const selection = window.getSelection();
1179
+ let savedRange = null;
1180
+ if (selection && selection.rangeCount > 0) {
1181
+ const range = selection.getRangeAt(0);
1182
+ // Chỉ lưu nếu range nằm trong contentText element
1183
+ const container = range.commonAncestorContainer;
1184
+ const isInContentElement = element.contains(container.nodeType === Node.TEXT_NODE ? container.parentNode : container);
1185
+ if (isInContentElement) {
1186
+ savedRange = range.cloneRange();
1187
+ }
1188
+ }
1189
+ // Prevent default để tự xử lý paste
1190
+ e.preventDefault();
1191
+ const htmlContent = e.clipboardData?.getData('text/html') || '';
1192
+ const plainText = e.clipboardData?.getData('text/plain') || '';
1193
+ let contentToInsert = (plainText || '').replace(/\n/g, '<br>');
1194
+ if (htmlContent) {
1195
+ const delta = await getDeltaOfQuill2xFromHTML(htmlContent);
1196
+ contentToInsert = getHTMLFromDeltaOfQuill2x(delta);
1197
+ }
1198
+ if (!contentToInsert) {
1199
+ config.callBack?.('no-content');
1200
+ return;
1201
+ }
1202
+ if (savedRange) {
1203
+ insertContentWithRange(contentToInsert, savedRange, element);
1204
+ config.callBack?.('range');
1205
+ return;
1206
+ }
1207
+ element.innerHTML += contentToInsert;
1208
+ config.callBack?.('content');
1209
+ };
1210
+ const insertContentWithRange = (content, savedRange, element) => {
1211
+ const selection = window.getSelection();
1212
+ if (!selection) {
1213
+ // Fallback: append vào cuối
1214
+ element.innerHTML += content;
1215
+ return;
1216
+ }
1217
+ // Restore selection
1218
+ selection.removeAllRanges();
1219
+ selection.addRange(savedRange);
1220
+ // Xóa nội dung đã select (nếu có)
1221
+ savedRange.deleteContents();
1222
+ // Tạo document fragment từ HTML content
1223
+ const tempDiv = document.createElement('div');
1224
+ tempDiv.innerHTML = content;
1225
+ const fragment = document.createDocumentFragment();
1226
+ while (tempDiv.firstChild) {
1227
+ fragment.appendChild(tempDiv.firstChild);
1228
+ }
1229
+ // Insert fragment tại vị trí range
1230
+ savedRange.insertNode(fragment);
1231
+ // Di chuyển cursor đến cuối nội dung vừa insert
1232
+ if (fragment.lastChild) {
1233
+ savedRange.setStartAfter(fragment.lastChild);
1234
+ }
1235
+ savedRange.collapse(true);
1236
+ selection.removeAllRanges();
1237
+ selection.addRange(savedRange);
1238
+ };
1239
+
1240
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1241
+ class LibsUiComponentsInputsQuill2xComponent {
1242
+ //region Private
1243
+ displayEditor = signal(false);
1244
+ isPaste = signal(false);
1245
+ quill = null;
1246
+ addEditLinkComponentRef = signal(undefined);
1247
+ uploadImageComponentRef = signal(undefined);
1248
+ handlers = signal({
1249
+ link: this.handleShowUploadLink.bind(this),
1250
+ undo: this.handleUndo.bind(this),
1251
+ redo: this.handleRedo.bind(this),
1252
+ table: this.handleTable.bind(this),
1253
+ image: this.handlerShowUploadImage.bind(this),
1254
+ });
1255
+ timerSetFontSize = signal(undefined);
1256
+ timerCalculatorToolbar = signal(undefined);
1257
+ totalRecallCalculatorToolbar = signal(0);
1258
+ isCalculatorToolbarSuccess = signal(false);
1259
+ showMention = signal(false);
1260
+ handlerTextChangeBind = this.handlerTextChange.bind(this);
1261
+ handlerSelectionChangeBind = this.handlerSelectionChange.bind(this);
1262
+ //endregion
1263
+ //region Protected
1264
+ borderColor = computed(() => (this.messageError() && this.showErrorBorder() ? ' !border-[var(--libs-ui-color-border-error,#ee2d41)] ' : '!border-[#e5e7eb] '));
1265
+ containerClass = computed(() => `quill-container flex flex-col h-full w-full ${get(this.quillCustomConfig(), 'classContainer') || ''}`);
1266
+ containerToolbarClass = computed(() => `h-full border-t border-l border-r ${this.ignoreShowBorderErrorToolbar() ? '!border-[#e5e7eb] ' : this.borderColor()}rounded-t-[4px] ${this.readonly() ? ' libs-ui-readonly-background cursor-default pointer-events-none ' : ' '} ${get(this.quillCustomConfig(), 'toolbar.classCustomContainerToolbar') || ''} ${get(this.quillCustomConfig(), 'toolbar.positionFixed') ? ' fixed ' : ' '}`);
1267
+ containerEditorClass = computed(() => `${this.displayEditor() ? 'h-full' : 'hidden'} !border ${this.borderColor()} !rounded-b-[4px] ${this.readonly() ? ' libs-ui-readonly-background ' : ' '} ${get(this.quillCustomConfig(), 'editor.classCustomContainerEditor') || ''} ${get(this.quillCustomConfig(), 'toolbar.positionFixed') ? ' rounded-t-[4px] ' : ' '}`);
1268
+ showButtonMoreToolbar = computed(() => this.toolbarOptionsConfig().some((item) => item.display === false));
1269
+ displaySkeletonToolbar = computed(() => this.toolbarOptionsConfig().every((item) => item.display === undefined));
1270
+ labelFontSelectedComputed = computed(() => get(this.fontSelected(), 'item.label') || 'Sans Serif');
1271
+ fontSelected = signal(undefined);
1272
+ listConfigFont = signal(listConfigFont());
1273
+ listConfigHeader = signal(listConfigHeader());
1274
+ headerSelected = signal(undefined);
1275
+ labelHeaderSelectedComputed = computed(() => get(this.headerSelected(), 'item.label') || 'Normal');
1276
+ listConfigAlign = signal(listConfigAlign());
1277
+ alignSelected = signal(undefined);
1278
+ iconAlignSelectedComputed = computed(() => get(this.alignSelected(), 'item.icon'));
1279
+ colorSelected = signal(undefined);
1280
+ backgroundSelected = signal(undefined);
1281
+ listCheckboxSelected = signal(false);
1282
+ adjustSelected = signal(undefined);
1283
+ messageError = signal('');
1284
+ fontSize = signal({ value: 14 });
1285
+ link = signal(linkDefault());
1286
+ showMoreAction = signal(false);
1287
+ toolbarOptionsConfig = signal([]);
1288
+ loadingUploadImage = signal(false);
1289
+ uploadImageConfig = input(uploadImageConfigDefault(), { transform: (value) => value || uploadImageConfigDefault() });
1290
+ //region Inputs
1291
+ displayToolbar = input(true, { transform: (value) => value ?? true });
1292
+ placeholder = input('i18n_import_content', { transform: (value) => value || 'i18n_import_content' });
1293
+ blotsRegister = input();
1294
+ readonly = input(false, { transform: (value) => value ?? false });
1295
+ quillCustomConfig = input();
1296
+ label = input();
1297
+ item = model.required();
1298
+ fieldBind = input.required();
1299
+ autoUpdateValueWhenTextChange = input(true, { transform: (value) => value ?? true });
1300
+ dataConfigMention = input();
1301
+ templateToolBarPersonalize = input();
1302
+ zIndex = input(1250, { transform: (value) => value ?? 1250 });
1303
+ ignoreShowPopupEditLink = input();
1304
+ ignoreCommunicateMicroEventPopup = input();
1305
+ handlersExpand = input();
1306
+ resizeImagePlugin = input();
1307
+ fontSizeDefault = input(14, { transform: (value) => value ?? 14 });
1308
+ heightEditorContentDefault = input();
1309
+ minHeightEditorContentDefault = input();
1310
+ maxHeightEditorContentDefault = input();
1311
+ removeNearWhiteColorsOnPaste = input(true);
1312
+ resize = input('none', { transform: (value) => value ?? 'none' });
1313
+ autoFocus = input();
1314
+ focusTimerOnInit = input(750, { transform: (value) => value ?? 750 });
1315
+ focusBottom = input();
1316
+ validRequired = input();
1317
+ validMinLength = input();
1318
+ validMaxLength = input();
1319
+ showErrorLabel = input(true, { transform: (val) => val ?? true });
1320
+ showErrorBorder = input(true);
1321
+ ignoreShowBorderErrorToolbar = input();
1322
+ //endregion
1323
+ //region Outputs
1324
+ outShowPopupEditLink = output();
1325
+ outMessageError = output();
1326
+ outBlur = output();
1327
+ outFocus = output();
1328
+ outChange = output();
1329
+ outSelectionChange = output();
1330
+ outTextChange = output();
1331
+ outContextMenu = output();
1332
+ outFunctionsControl = output();
1333
+ //endregion
1334
+ //region ViewChild
1335
+ htmlContainer = viewChild.required('htmlContainer');
1336
+ quillBodyContainer = viewChild.required('snowContainer');
1337
+ toolbarContainer = viewChild.required('toolbarContainer');
1338
+ inputLinkRef = viewChild.required('inputLinkRef');
1339
+ //endregion
1340
+ //region Inject
1341
+ destroyRef = inject(DestroyRef);
1342
+ notificationService = inject(LibsUiNotificationService);
1343
+ translate = inject(TranslateService);
1344
+ cdr = inject(ChangeDetectorRef);
1345
+ dynamicComponentService = inject(LibsUiDynamicComponentService);
1346
+ //endregion
1347
+ constructor() {
1348
+ effect(() => {
1349
+ if (this.fontSizeDefault()) {
1350
+ untracked(() => {
1351
+ this.fontSize.set({ value: this.fontSizeDefault() });
1352
+ this.handlerValueChangeFontSize();
1353
+ });
1354
+ }
1355
+ });
1356
+ effect(() => {
1357
+ const elementEditor = this.quillBodyContainer().nativeElement.getElementsByClassName('ql-editor')?.[0];
1358
+ if (this.heightEditorContentDefault()) {
1359
+ untracked(() => {
1360
+ if (elementEditor) {
1361
+ elementEditor.style.height = `${this.heightEditorContentDefault()}px`;
1362
+ }
1363
+ });
1364
+ }
1365
+ if (this.minHeightEditorContentDefault()) {
1366
+ untracked(() => {
1367
+ if (elementEditor) {
1368
+ elementEditor.style.minHeight = `${this.minHeightEditorContentDefault()}px`;
1369
+ }
1370
+ });
1371
+ }
1372
+ if (this.maxHeightEditorContentDefault()) {
1373
+ untracked(() => {
1374
+ if (elementEditor) {
1375
+ elementEditor.style.maxHeight = `${this.maxHeightEditorContentDefault()}px`;
1376
+ }
1377
+ });
1378
+ }
1379
+ });
1380
+ }
1381
+ async ngOnInit() {
1382
+ const type = get(this.quillCustomConfig(), 'toolbar.type', 'default');
1383
+ const hasIconImage = this.uploadImageConfig().showIcon ?? false;
1384
+ const hasIconPersonalize = !!this.templateToolBarPersonalize();
1385
+ this.toolbarOptionsConfig.set(get(this.quillCustomConfig(), 'toolbar.options', toolBarOptions(type, hasIconImage, hasIconPersonalize)));
1386
+ this.calculatorToolbar();
1387
+ registerQuill2x();
1388
+ this.blotsRegister()?.forEach((item) => {
1389
+ Quill2x.register(item.component, true);
1390
+ });
1391
+ this.updateHandlerFunction();
1392
+ const options = {
1393
+ theme: 'snow',
1394
+ placeholder: this.translate.instant(this.placeholder()),
1395
+ readOnly: this.readonly(),
1396
+ modules: {
1397
+ toolbar: {
1398
+ container: this.toolbarContainer().nativeElement,
1399
+ handlers: this.handlers(),
1400
+ },
1401
+ resize: this.resizeImagePlugin()
1402
+ ? {
1403
+ locale: {
1404
+ altTip: this.translate.instant('i18n_keep_alt_key_to_change_the_ratio_when_changing_the_size'),
1405
+ inputTip: this.translate.instant('i18n_press_enter_to_confirm'),
1406
+ floatLeft: this.translate.instant('i18n_left_align'),
1407
+ floatRight: this.translate.instant('i18n_right_align'),
1408
+ center: this.translate.instant('i18n_center_align'),
1409
+ restore: this.translate.instant('i18n_restore_to_default'),
1410
+ },
1411
+ }
1412
+ : undefined,
1413
+ table: true,
1414
+ clipboard: {
1415
+ matchers: [
1416
+ [Node.TEXT_NODE, (node) => new Delta().insert(node.data)],
1417
+ this.removeNearWhiteColorsOnPaste()
1418
+ ? [
1419
+ Node.ELEMENT_NODE,
1420
+ (node, delta) => {
1421
+ const style = node.getAttribute('style');
1422
+ if (style && this.isPaste()) {
1423
+ const newOps = delta.ops.map((op) => {
1424
+ if (op.attributes) {
1425
+ const newAttrs = { ...op.attributes };
1426
+ const color = newAttrs.color;
1427
+ const background = newAttrs.background;
1428
+ if (color && isNearWhite(color)) {
1429
+ delete newAttrs.color;
1430
+ }
1431
+ if (background && isNearWhite(background)) {
1432
+ delete newAttrs.background;
1433
+ }
1434
+ return { ...op, attributes: Object.keys(newAttrs).length ? newAttrs : undefined };
1435
+ }
1436
+ return op;
1437
+ });
1438
+ return new Delta(newOps);
1439
+ }
1440
+ return delta;
1441
+ },
1442
+ ]
1443
+ : [],
1444
+ ],
1445
+ },
1446
+ keyboard: {
1447
+ bindings: {
1448
+ backspace: {
1449
+ key: UtilsKeyCodeConstant.BACKSPACE,
1450
+ handler: (range) => this.noPreventEmbedDeletion(range),
1451
+ },
1452
+ delete: {
1453
+ key: UtilsKeyCodeConstant.DELETE,
1454
+ handler: (range) => this.noPreventEmbedDeletion(range),
1455
+ },
1456
+ enter: {
1457
+ key: 13,
1458
+ handler: () => (this.showMention() ? false : true),
1459
+ },
1460
+ },
1461
+ },
1462
+ history: {
1463
+ delay: 2000,
1464
+ maxStack: 500,
1465
+ userOnly: true,
1466
+ },
1467
+ },
1468
+ };
1469
+ this.quill = new Quill2x(this.quillBodyContainer().nativeElement, options);
1470
+ this.quill?.on('text-change', this.handlerTextChangeBind);
1471
+ this.quill?.on('selection-change', this.handlerSelectionChangeBind);
1472
+ fromEvent(this.quill?.root, 'copy').pipe(takeUntilDestroyed(this.destroyRef)).subscribe(this.handlerCopy.bind(this));
1473
+ fromEvent(this.quill?.root, 'paste', { capture: true }).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(this.handlerPaste.bind(this));
1474
+ fromEvent(this.quillBodyContainer()?.nativeElement, 'contextmenu')
1475
+ .pipe(takeUntilDestroyed(this.destroyRef))
1476
+ .subscribe((e) => this.outContextMenu.emit(e));
1477
+ fromEvent(window, 'resize')
1478
+ .pipe(takeUntilDestroyed(this.destroyRef))
1479
+ .subscribe(() => this.calculatorToolbar(true));
1480
+ this.outFunctionsControl.emit(this.FunctionsControl);
1481
+ await this.setContent(this.item()[this.fieldBind()]);
1482
+ timer(this.autoFocus() ? 0 : 250)
1483
+ .pipe(takeUntilDestroyed(this.destroyRef))
1484
+ .subscribe(() => this.displayEditor.set(true));
1485
+ this.setStyleForContent();
1486
+ if (this.autoFocus()) {
1487
+ timer(this.focusTimerOnInit() + 250)
1488
+ .pipe(takeUntilDestroyed(this.destroyRef))
1489
+ .subscribe(() => this.focus());
1490
+ }
1491
+ createContextMenuOptionTable(this.quill, this.translate);
1492
+ enableTableSelection(this.quill.root);
1493
+ getAllTablesWithSpan(this.quill);
1494
+ const rootElement = this.quill?.root;
1495
+ if (!rootElement) {
1496
+ return;
1497
+ }
1498
+ merge(fromEvent(rootElement, 'compositionstart'), fromEvent(rootElement, 'compositionend'))
1499
+ .pipe(takeUntilDestroyed(this.destroyRef))
1500
+ .subscribe((e) => {
1501
+ if (e.type === 'compositionstart') {
1502
+ rootElement.classList.remove('ql-blank');
1503
+ return;
1504
+ }
1505
+ if (isEmptyQuill2x(this.quill)) {
1506
+ rootElement.classList.add('ql-blank');
1507
+ return;
1508
+ }
1509
+ rootElement.classList.remove('ql-blank');
1510
+ });
1511
+ }
1512
+ get FunctionsControl() {
1513
+ return {
1514
+ quill: () => this.quill,
1515
+ checkIsValid: this.validate.bind(this),
1516
+ refreshItemValue: this.updateValueByRootQuillHtml.bind(this),
1517
+ setContent: this.setContent.bind(this),
1518
+ insertText: this.insertText.bind(this),
1519
+ insertLink: this.insertLink.bind(this),
1520
+ insertImage: this.insertImage.bind(this),
1521
+ setFontSize: async (size) => this.quill?.format('size', `${size}px`),
1522
+ setColor: async (color) => this.quill?.format('color', color),
1523
+ setBackground: async (color) => this.quill?.format('background', color),
1524
+ setMessageError: async (message) => this.messageError.set(message),
1525
+ reCalculatorToolbar: async () => this.calculatorToolbar(true),
1526
+ };
1527
+ }
1528
+ //region handler Event HTML
1529
+ handlerChangeShowMoreAction(event) {
1530
+ event.stopPropagation();
1531
+ this.showMoreAction.update((showMoreAction) => !showMoreAction);
1532
+ }
1533
+ handleSelectFont(event) {
1534
+ this.fontSelected.set(event);
1535
+ this.quill?.format('font', event?.key);
1536
+ }
1537
+ handleSelectHeader(event) {
1538
+ this.headerSelected.set(event);
1539
+ this.quill?.format('header', event?.key);
1540
+ }
1541
+ handlerValueChangeFontSize(event) {
1542
+ if (!event || event?.name === 'blur') {
1543
+ this.quill?.format('size', `${this.fontSize().value}px`);
1544
+ }
1545
+ }
1546
+ handleSelectAlign(event) {
1547
+ this.alignSelected.set(event);
1548
+ this.quill?.format('align', event?.key);
1549
+ }
1550
+ handlerSetFormat(type, data) {
1551
+ if (!data.alpha) {
1552
+ this.quill?.format(type, undefined);
1553
+ return;
1554
+ }
1555
+ this.quill?.format(type, data.hex);
1556
+ }
1557
+ handlerInsertEmoji(emoji) {
1558
+ this.insertText(emoji, undefined, true);
1559
+ }
1560
+ handlerUnlink(event) {
1561
+ event.stopPropagation();
1562
+ const link = this.link();
1563
+ this.quill?.formatText(link.range.index, link.range.length, { link: false });
1564
+ this.inputLinkRef().FunctionsControl?.removePopoverOverlay();
1565
+ }
1566
+ handlerEditLink(event) {
1567
+ event.stopPropagation();
1568
+ const link = this.link();
1569
+ if (this.ignoreShowPopupEditLink()) {
1570
+ this.outShowPopupEditLink.emit({
1571
+ dataLink: link,
1572
+ callback: async (linkEdit) => {
1573
+ if (link instanceof Object && link.url) {
1574
+ this.quill?.deleteText(link.range.index, link.range.length, Quill2x.sources.USER);
1575
+ this.quill?.insertText(link.range.index, linkEdit.title, 'link', linkEdit.link);
1576
+ return;
1577
+ }
1578
+ this.insertLink(linkEdit.title, linkEdit.link);
1579
+ },
1580
+ });
1581
+ return;
1582
+ }
1583
+ this.handleShowUploadLink(link);
1584
+ this.inputLinkRef().FunctionsControl?.removePopoverOverlay();
1585
+ }
1586
+ handleToggleMention(show) {
1587
+ this.showMention.set(show);
1588
+ }
1589
+ handlerInsertMention(data) {
1590
+ this.insertMention(data);
1591
+ }
1592
+ //endregion
1593
+ //region Private Method
1594
+ //region Quill Methods
1595
+ handleTable() {
1596
+ const button = this.htmlContainer().nativeElement.querySelector?.('button.ql-table');
1597
+ createTableSelector(this.quill, button, this.translate);
1598
+ }
1599
+ handleUndo() {
1600
+ this.quill?.history?.undo();
1601
+ }
1602
+ handleRedo() {
1603
+ this.quill?.history?.redo();
1604
+ }
1605
+ handlerShowUploadImage() {
1606
+ if (this.uploadImageConfig().modeCustom) {
1607
+ this.uploadImageComponentRef.set(this.dynamicComponentService.resolveComponentFactory(LibsUiComponentsInputsQuillUploadImageComponent));
1608
+ const instance = this.uploadImageComponentRef()?.instance;
1609
+ this.uploadImageComponentRef()?.setInput('uploadImageConfig', this.uploadImageConfig());
1610
+ this.uploadImageComponentRef()?.setInput('ignoreCommunicateMicroEvent', this.ignoreCommunicateMicroEventPopup());
1611
+ instance?.outClose.subscribe((link) => {
1612
+ this.dynamicComponentService.remove(this.uploadImageComponentRef());
1613
+ if (link) {
1614
+ this.insertImage(link);
1615
+ }
1616
+ });
1617
+ this.dynamicComponentService.addToBody(this.uploadImageComponentRef());
1618
+ return;
1619
+ }
1620
+ const input = document.createElement('input');
1621
+ input.setAttribute('type', 'file');
1622
+ input.setAttribute('accept', 'image/*');
1623
+ input.click();
1624
+ input.onchange = async () => {
1625
+ await this.uploadImagesAndInsert(input?.files);
1626
+ };
1627
+ }
1628
+ async insertText(content, index, focusLastValueInsert) {
1629
+ index = index ?? this.quill?.getSelection(true).index;
1630
+ if (!isNil(index)) {
1631
+ this.quill?.insertText(index, content, Quill2x.sources.USER);
1632
+ if (focusLastValueInsert) {
1633
+ await lastValueFrom(timer(0));
1634
+ this.quill?.setSelection(index + content.length, 0, Quill2x.sources.USER);
1635
+ }
1636
+ }
1637
+ }
1638
+ handlerSelectionChange(range, oldRange, source) {
1639
+ if (this.readonly()) {
1640
+ return;
1641
+ }
1642
+ if (range === null && oldRange !== null) {
1643
+ this.outBlur.emit();
1644
+ return;
1645
+ }
1646
+ if (range !== null && oldRange === null) {
1647
+ this.outFocus.emit();
1648
+ return;
1649
+ }
1650
+ const format = this.updateStateToolbarByFormatInCursor();
1651
+ this.outSelectionChange.emit({
1652
+ quill: this.quill,
1653
+ range,
1654
+ oldRange,
1655
+ source,
1656
+ });
1657
+ if (range.length !== 0 || !format?.link || source !== 'user') {
1658
+ return;
1659
+ }
1660
+ const selection = document.getSelection();
1661
+ const anchorNode = selection?.anchorNode;
1662
+ let linkNode = anchorNode;
1663
+ while (linkNode && linkNode.tagName !== 'A') {
1664
+ linkNode = linkNode.parentNode;
1665
+ }
1666
+ if (!linkNode || linkNode.tagName !== 'A') {
1667
+ return;
1668
+ }
1669
+ const linkText = linkNode.innerText;
1670
+ const linksIndices = [];
1671
+ let indexReturn = -1;
1672
+ while (!isNil(indexReturn) && indexReturn < range.index) {
1673
+ const textContent = this.quill?.getText();
1674
+ const linkIndex = textContent?.indexOf(linkText, linksIndices.length > 0 ? linksIndices[linksIndices.length - 1] + linkText.length : 0);
1675
+ if (!isNil(linkIndex)) {
1676
+ if (linkIndex !== -1) {
1677
+ linksIndices.push(linkIndex);
1678
+ indexReturn = linkIndex + linkText.length;
1679
+ }
1680
+ if (linkIndex === -1) {
1681
+ indexReturn = undefined;
1682
+ }
1683
+ }
1684
+ }
1685
+ if (linksIndices && linksIndices.length) {
1686
+ const rangeIndex = linksIndices[linksIndices.length - 1];
1687
+ const countImage = this.adjustIndexForImages(rangeIndex);
1688
+ setTimeout(() => {
1689
+ this.link.set({
1690
+ title: linkText,
1691
+ url: linkNode.href,
1692
+ range: {
1693
+ index: rangeIndex + countImage,
1694
+ length: linkText.length,
1695
+ },
1696
+ });
1697
+ this.inputLinkRef().FunctionsControl?.showPopover(linkNode);
1698
+ });
1699
+ }
1700
+ }
1701
+ handlerCopy(e) {
1702
+ if (!this.quill)
1703
+ return;
1704
+ const sel = this.quill.getSelection();
1705
+ if (!sel || sel.length === 0)
1706
+ return;
1707
+ e.preventDefault();
1708
+ const delta = this.quill.getContents(sel.index, sel.length);
1709
+ const html2x = getHTMLFromDeltaOfQuill2x(delta);
1710
+ if (e.clipboardData) {
1711
+ e.clipboardData.setData('text/html', html2x);
1712
+ }
1713
+ }
1714
+ async handlerPaste(event) {
1715
+ this.isPaste.set(true);
1716
+ setTimeout(() => this.isPaste.set(false), 0);
1717
+ if (!event || !event.clipboardData) {
1718
+ return;
1719
+ }
1720
+ const files = event.clipboardData.files;
1721
+ if (!files.length) {
1722
+ return;
1723
+ }
1724
+ event.preventDefault();
1725
+ await this.uploadImagesAndInsert(files);
1726
+ }
1727
+ handlerTextChange(event, _, source) {
1728
+ this.outTextChange.emit({
1729
+ quill: this.quill,
1730
+ delta: event,
1731
+ });
1732
+ setTimeout(() => {
1733
+ this.detectLink(source);
1734
+ this.updateStateToolbarByFormatInCursor();
1735
+ if (event?.ops[1]?.insert === '\n') {
1736
+ const range = { index: event?.ops[0].retain };
1737
+ const currentLeaf = this.quill?.getLeaf(range.index)[0];
1738
+ const nextLeaf = this.quill?.getLeaf(range.index + 1)[0];
1739
+ this.quill?.insertEmbed(range.index, 'break', true, 'user');
1740
+ if (nextLeaf === null || currentLeaf.parent !== nextLeaf.parent) {
1741
+ this.quill?.insertEmbed(range.index, 'break', true, 'user');
1742
+ }
1743
+ this.quill?.setSelection((range.index || 0) + 1, 0, Quill2x.sources.SILENT);
1744
+ }
1745
+ });
1746
+ if (!this.autoUpdateValueWhenTextChange()) {
1747
+ return;
1748
+ }
1749
+ this.updateValueByRootQuillHtml();
1750
+ }
1751
+ //endregion
1752
+ updateStateToolbarByFormatInCursor() {
1753
+ try {
1754
+ const format = this.quill?.getFormat();
1755
+ if (format) {
1756
+ this.setFontSizeSelected(format);
1757
+ this.setFontSelected(format);
1758
+ this.setColorSelected(format);
1759
+ this.listCheckboxSelected.set(format?.list === 'unchecked' || format?.list === 'checked');
1760
+ this.adjustSelected.set(format?.align);
1761
+ }
1762
+ return format;
1763
+ }
1764
+ catch (error) {
1765
+ console.log(error);
1766
+ return undefined;
1767
+ }
1768
+ }
1769
+ async validate() {
1770
+ this.messageError.set('');
1771
+ const validRequired = this.validRequired();
1772
+ const item = this.item();
1773
+ const fieldNameBind = this.fieldBind();
1774
+ const value = get(item, fieldNameBind);
1775
+ if (validRequired && validRequired.isRequired && !this.readonly() && isEmptyQuill2x(this.quill)) {
1776
+ this.messageError.set(validRequired.message || ERROR_MESSAGE_EMPTY_VALID);
1777
+ this.outMessageError.emit(this.messageError());
1778
+ return false;
1779
+ }
1780
+ const validMinLength = this.validMinLength();
1781
+ if (validMinLength && fieldNameBind && value && value.trim().length < validMinLength.length) {
1782
+ this.messageError.set(validMinLength.message || ERROR_MESSAGE_MIN_LENGTH);
1783
+ this.outMessageError.emit(this.messageError());
1784
+ return false;
1785
+ }
1786
+ const validMaxLength = this.validMaxLength();
1787
+ if (validMaxLength && fieldNameBind && value && value.trim().length > this.validMaxLength.length) {
1788
+ this.messageError.set(validMaxLength.message || ERROR_MESSAGE_MAX_LENGTH);
1789
+ this.outMessageError.emit(this.messageError());
1790
+ return false;
1791
+ }
1792
+ this.outMessageError.emit(this.messageError());
1793
+ return true;
1794
+ }
1795
+ updateHandlerFunction() {
1796
+ const handlersExpand = this.handlersExpand();
1797
+ if (handlersExpand && handlersExpand.length) {
1798
+ handlersExpand.forEach((element) => {
1799
+ if (element.title && element.action) {
1800
+ this.handlers.update((item) => ({ ...item, [element.title]: element.action }));
1801
+ }
1802
+ });
1803
+ }
1804
+ }
1805
+ noPreventEmbedDeletion(range) {
1806
+ if (!this.quill)
1807
+ return false;
1808
+ const delta = this.quill?.getContents(range.index, 1);
1809
+ if (delta?.ops.some((option) => this.blotsRegister()?.find((item) => get(option.insert, item.component['blotName']) && item.ignoreDelete))) {
1810
+ return false; // Ngăn không cho Quill xử lý xóa
1811
+ }
1812
+ return true;
1813
+ }
1814
+ setFontSizeSelected(format) {
1815
+ const fontSize = format?.size?.toString()?.replace('px', '');
1816
+ if (isNaN(parseFloat(fontSize))) {
1817
+ this.fontSize.set({ value: this.fontSizeDefault() });
1818
+ // this.handlerValueChangeFontSize();
1819
+ return;
1820
+ }
1821
+ this.fontSize.set({ value: parseFloat(fontSize) });
1822
+ }
1823
+ setFontSelected(format) {
1824
+ const font = listFont().find((item) => item.key === format.font);
1825
+ if (!font) {
1826
+ this.fontSelected.set({ key: '', item: listFont().find((item) => item.key === '') });
1827
+ return;
1828
+ }
1829
+ this.fontSelected.set({ key: format.font, item: font });
1830
+ }
1831
+ setColorSelected(format) {
1832
+ this.colorSelected.set(format?.color);
1833
+ this.backgroundSelected.set(format?.background);
1834
+ }
1835
+ async calculatorToolbar(resetTotalRecall) {
1836
+ clearTimeout(this.timerCalculatorToolbar());
1837
+ if (resetTotalRecall) {
1838
+ this.totalRecallCalculatorToolbar.set(0);
1839
+ }
1840
+ this.cdr.detectChanges();
1841
+ const toolbarWidth = this.toolbarContainer().nativeElement.offsetWidth || 0;
1842
+ let totalWidth = 84;
1843
+ if (!this.isCalculatorToolbarSuccess() && this.totalRecallCalculatorToolbar() < 10 && toolbarWidth < get(this.quillCustomConfig(), 'toolbar.lessWidthToolbarRecallCalculator', totalWidth)) {
1844
+ this.totalRecallCalculatorToolbar.update((val) => val + 1);
1845
+ this.timerCalculatorToolbar.set(setTimeout(() => {
1846
+ this.calculatorToolbar();
1847
+ }, 250));
1848
+ return;
1849
+ }
1850
+ this.isCalculatorToolbarSuccess.set(true);
1851
+ this.toolbarOptionsConfig.update((configs) => {
1852
+ configs.forEach((item, index) => {
1853
+ item.display = true;
1854
+ totalWidth += item.width;
1855
+ if (item.type === 'line') {
1856
+ const nextItemWidth = totalWidth + this.toolbarOptionsConfig()[+index + 1].width;
1857
+ const preItem = this.toolbarOptionsConfig()[+index - 1];
1858
+ if (nextItemWidth > toolbarWidth && preItem.display === true) {
1859
+ item.display = undefined;
1860
+ return;
1861
+ }
1862
+ }
1863
+ if (totalWidth > toolbarWidth) {
1864
+ item.display = false;
1865
+ return;
1866
+ }
1867
+ });
1868
+ return [...configs];
1869
+ });
1870
+ this.cdr.detectChanges();
1871
+ }
1872
+ setStyleForContent() {
1873
+ const styleArray = [
1874
+ { selector: '.ql-font-Arial', style: 'font-family: Arial' },
1875
+ { selector: '.ql-font-sans-serif', style: 'font-family: sans-serif' },
1876
+ { selector: '.ql-font-serif', style: 'font-family: serif' },
1877
+ { selector: '.ql-font-monospace', style: 'font-family: monospace' },
1878
+ { selector: '.ql-font-Helvetica', style: 'font-family: Helvetica' },
1879
+ { selector: '.libs-ui-quill-format-image', style: 'max-width: 100%; height: auto', overrideStyle: true },
1880
+ { selector: '.libs-ui-quill-mention', style: 'font-weight: 600;-moz-osx-font-smoothing: grayscale;-webkit-font-smoothing: antialiased; color: #7239EA', overrideStyle: true },
1881
+ { selector: 'blockquote', style: 'border-left: 4px solid #ccc; margin-bottom: 5px; margin-top: 5px; padding-left: 16px', overrideStyle: true },
1882
+ ];
1883
+ this.blotsRegister()?.forEach((item) => {
1884
+ styleArray.push({ selector: `.${item.className}`, style: item.style, overrideStyle: true });
1885
+ });
1886
+ styleArray.forEach((item) => {
1887
+ const elements = this.quill?.root.querySelectorAll(item.selector);
1888
+ elements?.forEach((element) => {
1889
+ if (item.overrideStyle) {
1890
+ element.setAttribute('style', item.style);
1891
+ return;
1892
+ }
1893
+ // Fixbug issue: https://admin-cv.mobio.vn/issues/49092
1894
+ let styleExist = element.getAttribute('style');
1895
+ if (styleExist) {
1896
+ ['font-family: Arial', 'font-family: sans-serif', 'font-family: serif', 'font-family: monospace', 'font-family: Helvetica'].forEach((font) => {
1897
+ styleExist = (styleExist?.includes(`${font};`) ? styleExist?.replace(`${font};`, '') : styleExist?.replace(font, ''));
1898
+ });
1899
+ }
1900
+ element.setAttribute('style', styleExist ? `${styleExist}${styleExist[styleExist.length - 1] === ';' ? '' : ';'}${item.style};` : item.style);
1901
+ });
1902
+ });
1903
+ }
1904
+ async setContent(content) {
1905
+ if (!this.quill) {
1906
+ return;
1907
+ }
1908
+ const contentXssFilter = await xssFilter(this.removeUnwantedPart(content || ''));
1909
+ this.quill.clipboard.dangerouslyPasteHTML(contentXssFilter, Quill2x.sources.SILENT);
1910
+ this.quill.blur();
1911
+ }
1912
+ adjustIndexForImages(index) {
1913
+ const content = this.quill?.root;
1914
+ let imageCount = 0;
1915
+ let adjustedIndex = 0;
1916
+ const adjustTextIndex = (node) => {
1917
+ if (adjustedIndex > index) {
1918
+ return;
1919
+ }
1920
+ if (node.nodeType === Node.TEXT_NODE) {
1921
+ adjustedIndex += node.textContent?.length || 0;
1922
+ return;
1923
+ }
1924
+ if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'IMG') {
1925
+ if (adjustedIndex <= index) {
1926
+ imageCount++;
1927
+ }
1928
+ return;
1929
+ }
1930
+ if (node.nodeType === Node.ELEMENT_NODE) {
1931
+ Array.from(node.childNodes).forEach(adjustTextIndex);
1932
+ }
1933
+ };
1934
+ adjustTextIndex(content);
1935
+ return imageCount;
1936
+ }
1937
+ async uploadImagesAndInsert(files) {
1938
+ if (!files) {
1939
+ return;
1940
+ }
1941
+ const maxImageSize = this.uploadImageConfig()?.maxImageSize || 5 * 1024 * 1024;
1942
+ const images = Array.from(files).filter((file) => file && file.type.split('/')[0] === 'image' && (!this.uploadImageConfig().maxImageSize || file.size <= maxImageSize));
1943
+ if (!images.length) {
1944
+ this.notificationService.showCompTypeTextWarning(this.translate.instant('i18n_note_upload_image_volume', { value: getLabelBySizeFile(maxImageSize) }), { timeRemove: 8000 });
1945
+ return;
1946
+ }
1947
+ try {
1948
+ this.loadingUploadImage.set(true);
1949
+ const indexInsert = this.quill?.getSelection(true).index;
1950
+ const links = await this.uploadImageConfig().functionUploadImage?.(images);
1951
+ if (!links?.length) {
1952
+ return;
1953
+ }
1954
+ links.forEach((link) => {
1955
+ if (!link || (this.uploadImageConfig().onlyAcceptImageHttpsLink && (link instanceof ArrayBuffer || !new RegExp(patternUrl()).test(link)))) {
1956
+ return;
1957
+ }
1958
+ this.insertImage(link, indexInsert);
1959
+ });
1960
+ }
1961
+ catch (error) {
1962
+ console.error(error);
1963
+ }
1964
+ finally {
1965
+ this.loadingUploadImage.set(false);
1966
+ }
1967
+ return;
1968
+ }
1969
+ async insertImage(content, index) {
1970
+ const selection = index ?? this.quill?.getSelection(true).index;
1971
+ if (!isNil(selection)) {
1972
+ this.quill?.insertEmbed(selection, 'image', content);
1973
+ this.quill?.setSelection(selection + 1, 0);
1974
+ }
1975
+ }
1976
+ handlerPopoverEvent(event) {
1977
+ setTimeout(() => {
1978
+ if (event === 'remove') {
1979
+ this.link.set(linkDefault());
1980
+ }
1981
+ }, 100);
1982
+ }
1983
+ async insertLink(text, url, index) {
1984
+ const selection = this.quill?.getSelection(true);
1985
+ const indexInsert = index ?? selection?.index;
1986
+ if (selection?.length) {
1987
+ this.quill?.deleteText(selection.index, selection.length, Quill2x.sources.USER);
1988
+ }
1989
+ if (!isNil(indexInsert)) {
1990
+ this.quill?.insertText(indexInsert, text, {
1991
+ link: url,
1992
+ color: false,
1993
+ });
1994
+ }
1995
+ }
1996
+ handleShowUploadLink(event) {
1997
+ this.addEditLinkComponentRef.set(this.dynamicComponentService.resolveComponentFactory(LibsUiComponentsInputsQuillLinkComponent));
1998
+ const instance = this.addEditLinkComponentRef()?.instance;
1999
+ const selection = this.quill?.getSelection();
2000
+ const textSelected = selection?.length ? this.quill?.getText(selection?.index, selection?.length) : '';
2001
+ const data = event;
2002
+ this.addEditLinkComponentRef()?.setInput('zIndex', this.zIndex());
2003
+ this.addEditLinkComponentRef()?.setInput('title', data?.title || textSelected);
2004
+ this.addEditLinkComponentRef()?.setInput('link', data?.url || '');
2005
+ this.addEditLinkComponentRef()?.setInput('ignoreCommunicateMicroEvent', this.ignoreCommunicateMicroEventPopup());
2006
+ instance?.outSaveLink.subscribe((item) => {
2007
+ if (data instanceof Object && data.url) {
2008
+ this.quill?.deleteText(data.range.index, data.range.length, Quill2x.sources.USER);
2009
+ this.quill?.insertText(data.range.index, item.title, 'link', item.link);
2010
+ return;
2011
+ }
2012
+ this.insertLink(item.title, item.link);
2013
+ });
2014
+ instance?.outClose.subscribe(() => this.dynamicComponentService.remove(this.addEditLinkComponentRef()));
2015
+ this.dynamicComponentService.addToBody(this.addEditLinkComponentRef());
2016
+ }
2017
+ insertMention(event) {
2018
+ const range = this.quill?.getSelection();
2019
+ if (!range) {
2020
+ return;
2021
+ }
2022
+ const indexSubtract = this.quill?.getText(range.index - 1, 1)?.includes('@') ? 0 : 1;
2023
+ const cursorPos = range.index - indexSubtract;
2024
+ const startPosInsert = cursorPos - event.lengthKey;
2025
+ this.quill?.deleteText(startPosInsert, event.lengthKey, Quill2x.sources.SILENT);
2026
+ this.quill?.insertEmbed(startPosInsert, 'mention', event.data, Quill2x.sources.SILENT);
2027
+ this.insertText(' ', startPosInsert + 1);
2028
+ if (indexSubtract) {
2029
+ this.quill?.deleteText(startPosInsert + 3, 1, Quill2x.sources.SILENT);
2030
+ }
2031
+ this.quill?.setSelection(startPosInsert + 2, 0, Quill2x.sources.SILENT);
2032
+ }
2033
+ updateValueByRootQuillHtml() {
2034
+ if (!this.quill?.root.getElementsByTagName('img').length && !this.quill?.getText()) {
2035
+ set(this.item(), this.fieldBind(), '');
2036
+ this.validate();
2037
+ this.outChange.emit(get(this.item, this.fieldBind()));
2038
+ return;
2039
+ }
2040
+ this.convertInnerHTMLAndSetValue();
2041
+ this.validate();
2042
+ this.outChange.emit(get(this.item, this.fieldBind()));
2043
+ }
2044
+ convertInnerHTMLAndSetValue() {
2045
+ this.setStyleForContent();
2046
+ set(this.item(), this.fieldBind(), this.removeUnwantedPart(this.quill?.root.innerHTML || ''));
2047
+ }
2048
+ removeUnwantedPart(html) {
2049
+ if (!html) {
2050
+ return '';
2051
+ }
2052
+ return html
2053
+ .replace(/^\s*<p><br><\/p>/, '')
2054
+ .replace(/<div><br><\/div>/gi, '')
2055
+ .replace(/[\u200B-\u200D\uFEFF]/g, '');
2056
+ }
2057
+ detectLink(source) {
2058
+ if (source !== 'user') {
2059
+ return;
2060
+ }
2061
+ const fullText = this.quill?.getText();
2062
+ const indexes = this.getIndexUrlMatch(fullText || '');
2063
+ if (isNil(fullText) || !indexes.length) {
2064
+ return;
2065
+ }
2066
+ indexes.forEach((item) => {
2067
+ const textSelection = this.quill?.getText(item.start, item.end - item.start);
2068
+ if (!textSelection) {
2069
+ return;
2070
+ }
2071
+ const indexof = textSelection.search(patterProtocolUrl());
2072
+ if (indexof < 0) {
2073
+ return;
2074
+ }
2075
+ this.quill?.formatText(item.start + indexof, item.text.length, { link: item.text, color: false }, Quill2x.sources.SILENT);
2076
+ });
2077
+ }
2078
+ getIndexUrlMatch(fullText) {
2079
+ const textMatch = fullText.match(new RegExp(patternUrl().toString().replace('/^', '').replace('$/', ''), 'ig'));
2080
+ const indexes = [];
2081
+ let index = 0;
2082
+ textMatch?.forEach((text) => {
2083
+ const indexOfTextMatch = fullText.indexOf(text, index);
2084
+ const indexPreCharacter = indexOfTextMatch - 1;
2085
+ index = indexOfTextMatch + text.length;
2086
+ if (indexOfTextMatch < 0 || !new RegExp(/^[\s]|^[\t]|^[\n]/).test(fullText[indexPreCharacter] || '')) {
2087
+ return;
2088
+ }
2089
+ indexes.push({ start: indexOfTextMatch, end: indexOfTextMatch + text.length, text, preCharacter: fullText[indexPreCharacter] });
2090
+ });
2091
+ return indexes;
2092
+ }
2093
+ focus() {
2094
+ if (this.focusBottom()) {
2095
+ this.quill?.setSelection(this.quill.root.innerHTML.length, 0, Quill2x.sources.SILENT);
2096
+ return;
2097
+ }
2098
+ this.quill?.setSelection(0, 0, Quill2x.sources.SILENT);
2099
+ }
2100
+ //endregion
2101
+ ngOnDestroy() {
2102
+ clearTimeout(this.timerSetFontSize());
2103
+ clearTimeout(this.timerCalculatorToolbar());
2104
+ this.dynamicComponentService.delete(this.addEditLinkComponentRef());
2105
+ this.dynamicComponentService.delete(this.uploadImageComponentRef());
2106
+ this.quill?.off('text-change', this.handlerTextChangeBind);
2107
+ this.quill?.off('selection-change', this.handlerSelectionChangeBind);
2108
+ const editorDom = this.quill?.root.parentElement;
2109
+ if (editorDom && editorDom.parentElement) {
2110
+ editorDom.parentElement.removeChild(editorDom);
2111
+ }
2112
+ this.quill = null;
2113
+ }
2114
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuill2xComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2115
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LibsUiComponentsInputsQuill2xComponent, isStandalone: true, selector: "libs_ui-components-inputs-quill2x", inputs: { uploadImageConfig: { classPropertyName: "uploadImageConfig", publicName: "uploadImageConfig", isSignal: true, isRequired: false, transformFunction: null }, displayToolbar: { classPropertyName: "displayToolbar", publicName: "displayToolbar", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, blotsRegister: { classPropertyName: "blotsRegister", publicName: "blotsRegister", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, quillCustomConfig: { classPropertyName: "quillCustomConfig", publicName: "quillCustomConfig", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, fieldBind: { classPropertyName: "fieldBind", publicName: "fieldBind", isSignal: true, isRequired: true, transformFunction: null }, autoUpdateValueWhenTextChange: { classPropertyName: "autoUpdateValueWhenTextChange", publicName: "autoUpdateValueWhenTextChange", isSignal: true, isRequired: false, transformFunction: null }, dataConfigMention: { classPropertyName: "dataConfigMention", publicName: "dataConfigMention", isSignal: true, isRequired: false, transformFunction: null }, templateToolBarPersonalize: { classPropertyName: "templateToolBarPersonalize", publicName: "templateToolBarPersonalize", isSignal: true, isRequired: false, transformFunction: null }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, ignoreShowPopupEditLink: { classPropertyName: "ignoreShowPopupEditLink", publicName: "ignoreShowPopupEditLink", isSignal: true, isRequired: false, transformFunction: null }, ignoreCommunicateMicroEventPopup: { classPropertyName: "ignoreCommunicateMicroEventPopup", publicName: "ignoreCommunicateMicroEventPopup", isSignal: true, isRequired: false, transformFunction: null }, handlersExpand: { classPropertyName: "handlersExpand", publicName: "handlersExpand", isSignal: true, isRequired: false, transformFunction: null }, resizeImagePlugin: { classPropertyName: "resizeImagePlugin", publicName: "resizeImagePlugin", isSignal: true, isRequired: false, transformFunction: null }, fontSizeDefault: { classPropertyName: "fontSizeDefault", publicName: "fontSizeDefault", isSignal: true, isRequired: false, transformFunction: null }, heightEditorContentDefault: { classPropertyName: "heightEditorContentDefault", publicName: "heightEditorContentDefault", isSignal: true, isRequired: false, transformFunction: null }, minHeightEditorContentDefault: { classPropertyName: "minHeightEditorContentDefault", publicName: "minHeightEditorContentDefault", isSignal: true, isRequired: false, transformFunction: null }, maxHeightEditorContentDefault: { classPropertyName: "maxHeightEditorContentDefault", publicName: "maxHeightEditorContentDefault", isSignal: true, isRequired: false, transformFunction: null }, removeNearWhiteColorsOnPaste: { classPropertyName: "removeNearWhiteColorsOnPaste", publicName: "removeNearWhiteColorsOnPaste", isSignal: true, isRequired: false, transformFunction: null }, resize: { classPropertyName: "resize", publicName: "resize", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, focusTimerOnInit: { classPropertyName: "focusTimerOnInit", publicName: "focusTimerOnInit", isSignal: true, isRequired: false, transformFunction: null }, focusBottom: { classPropertyName: "focusBottom", publicName: "focusBottom", isSignal: true, isRequired: false, transformFunction: null }, validRequired: { classPropertyName: "validRequired", publicName: "validRequired", isSignal: true, isRequired: false, transformFunction: null }, validMinLength: { classPropertyName: "validMinLength", publicName: "validMinLength", isSignal: true, isRequired: false, transformFunction: null }, validMaxLength: { classPropertyName: "validMaxLength", publicName: "validMaxLength", isSignal: true, isRequired: false, transformFunction: null }, showErrorLabel: { classPropertyName: "showErrorLabel", publicName: "showErrorLabel", isSignal: true, isRequired: false, transformFunction: null }, showErrorBorder: { classPropertyName: "showErrorBorder", publicName: "showErrorBorder", isSignal: true, isRequired: false, transformFunction: null }, ignoreShowBorderErrorToolbar: { classPropertyName: "ignoreShowBorderErrorToolbar", publicName: "ignoreShowBorderErrorToolbar", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { item: "itemChange", outShowPopupEditLink: "outShowPopupEditLink", outMessageError: "outMessageError", outBlur: "outBlur", outFocus: "outFocus", outChange: "outChange", outSelectionChange: "outSelectionChange", outTextChange: "outTextChange", outContextMenu: "outContextMenu", outFunctionsControl: "outFunctionsControl" }, viewQueries: [{ propertyName: "htmlContainer", first: true, predicate: ["htmlContainer"], descendants: true, isSignal: true }, { propertyName: "quillBodyContainer", first: true, predicate: ["snowContainer"], descendants: true, isSignal: true }, { propertyName: "toolbarContainer", first: true, predicate: ["toolbarContainer"], descendants: true, isSignal: true }, { propertyName: "inputLinkRef", first: true, predicate: ["inputLinkRef"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #htmlContainer\n [class]=\"containerClass()\"\n [style.--resize]=\"resize()\">\n @if (label(); as label) {\n <libs_ui-components-label\n [classInclude]=\"label.classInclude\"\n [required]=\"label.required\"\n [labelLeft]=\"label.labelLeft\" />\n }\n <div\n #toolbarContainer\n class=\"relative h-[44px]\"\n [class.hidden]=\"!displayToolbar()\"\n [ngStyle]=\"quillCustomConfig()?.toolbar?.()?.styles?.() || {}\">\n <ng-container *ngTemplateOutlet=\"toolbarTemplate\" />\n </div>\n <div\n #snowContainer\n LibsUiComponentsScrollOverlayDirective\n LibsUiComponentsInputsMentionDirective\n [options]=\"{ ignoreTransparentScrollBarColorDefault: resize() !== 'none' }\"\n [mentionConfig]=\"dataConfigMention()\"\n (outToggle)=\"handleToggleMention($event)\"\n (outInsertMention)=\"handlerInsertMention($event)\"\n [class]=\"containerEditorClass()\"></div>\n @if (messageError() && showErrorLabel()) {\n <div class=\"flex items-center mt-[8px]\">\n <span class=\"text-[#ff5454] libs-ui-font-h7r\">{{ messageError() | translate }}</span>\n </div>\n }\n</div>\n\n<ng-template #toolbarTemplate>\n <div\n [class]=\"containerToolbarClass()\"\n #toolbarItems>\n @if (displaySkeletonToolbar()) {\n <libs_ui-components-skeleton\n class=\"w-full h-full\"\n [config]=\"{\n rows: [\n {\n item: { classIncludeItem: '!rounded-none' },\n },\n ],\n }\" />\n }\n <div class=\"ql-formats items-center px-[16px] py-[4px] !flex\">\n @for (option of toolbarOptionsConfig(); track option) {\n <div\n class=\"items-center\"\n [class.hidden]=\"!option.display\"\n [class.flex]=\"option.display\">\n <ng-container *ngTemplateOutlet=\"itemToolbarTemplate; context: { option, $index, $last }\" />\n </div>\n }\n <div class=\"relative\">\n @if (showButtonMoreToolbar()) {\n <libs_ui-components-buttons-button\n [type]=\"showMoreAction() ? 'button-secondary' : 'button-third'\"\n [iconOnlyType]=\"true\"\n [classInclude]=\"'!p-[1px] !h-[20px] ml-[8px]'\"\n [classIconLeft]=\"'libs-ui-icon-move-right rotate-90'\"\n (outClick)=\"handlerChangeShowMoreAction($event)\" />\n }\n <!-- Build toolbar khi click xem th\u00EAm-->\n <div\n [class.hidden]=\"!showMoreAction()\"\n class=\"absolute bg-[#ffffff] shadow-[0px_4px_16px_0px_rgba(0,20,51,0.10)] z-[1] flex items-center pl-[8px] pr-[16px] py-[4px] rounded-[8px] top-[31px] right-[0px]\">\n @for (option of toolbarOptionsConfig(); track option) {\n <div\n class=\"items-center\"\n [class.hidden]=\"option.display !== false\"\n [class.flex]=\"option.display === false\">\n <ng-container *ngTemplateOutlet=\"itemToolbarTemplate; context: { option, $index, $last }\" />\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</ng-template>\n\n<ng-template\n #itemToolbarTemplate\n let-$index=\"$index\"\n let-$last=\"$last\"\n let-option=\"option\">\n @if (option.type === 'undo') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_undo', zIndex: zIndex() }\"\n class=\"ql-undo\"></button>\n </div>\n }\n @if (option.type === 'redo') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_redo', zIndex: zIndex() }\"\n class=\"ql-redo\"></button>\n </div>\n }\n @if (option.type === 'fontFamily') {\n <div\n class=\"relative w-[132px] h-full rounded-[4px] libs-ui-border-general {{ option.classInclude || 'mx-[8px]' }}\"\n [class.bg-white]=\"!readonly() && !loadingUploadImage()\">\n <libs_ui-components-dropdown\n [isNgContent]=\"true\"\n [labelPopoverFullWidth]=\"true\"\n [listConfig]=\"listConfigFont()\"\n [listHasButtonUnSelectOption]=\"false\"\n [listHiddenInputSearch]=\"true\"\n [readonly]=\"readonly()\"\n [zIndex]=\"1250\"\n (outSelectKey)=\"handleSelectFont($event)\">\n @if (fontSelected(); as fontSelected) {\n <div class=\"flex items-center cursor-pointer py-[7px] libs-ui-font-h5r pl-[16px] pr-[40px]\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [ignoreStopPropagationEvent]=\"true\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ labelFontSelectedComputed() }}\n </libs_ui-components-popover>\n <i class=\"libs-ui-icon-move-right rotate-90 absolute right-[12px] text-[#6a7383] text-[16px]\"></i>\n </div>\n }\n </libs_ui-components-dropdown>\n </div>\n }\n @if (option.type === 'header') {\n <div\n class=\"relative w-[138px] h-full rounded-[4px] libs-ui-border-general {{ option.classInclude || 'mx-[8px]' }}\"\n [class.bg-white]=\"!readonly() && !loadingUploadImage()\">\n <libs_ui-components-dropdown\n [isNgContent]=\"true\"\n [labelPopoverFullWidth]=\"true\"\n [popoverCustomConfig]=\"{ widthByParent: false, width: 200, classInclude: 'h-[254px]', classIncludeOverlayBody: 'h-full', position: { mode: 'start', distance: -8 } }\"\n [listConfig]=\"listConfigHeader()\"\n [listHasButtonUnSelectOption]=\"false\"\n [listHiddenInputSearch]=\"true\"\n [readonly]=\"readonly()\"\n [listMaxItemShow]=\"-1\"\n [zIndex]=\"1250\"\n (outSelectKey)=\"handleSelectHeader($event)\">\n @if (headerSelected(); as headerSelected) {\n <div class=\"flex items-center cursor-pointer py-[7px] libs-ui-font-h5r pl-[16px] pr-[40px] {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [ignoreStopPropagationEvent]=\"true\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ labelHeaderSelectedComputed() }}\n </libs_ui-components-popover>\n <i class=\"libs-ui-icon-move-right rotate-90 absolute right-[12px] text-[#6a7383] text-[16px]\"></i>\n </div>\n }\n </libs_ui-components-dropdown>\n </div>\n }\n @if (option.type === 'fontSize') {\n <div class=\" {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-inputs-valid\n #inputSizeRef\n [dataType]=\"'int'\"\n [(item)]=\"fontSize\"\n [fieldNameBind]=\"'value'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"72\"\n [minValueNumber]=\"8\"\n [readonly]=\"readonly() || loadingUploadImage()\"\n [classContainerInput]=\"'w-[72px] h-[32px]'\"\n (outChangeValueByButtonUpDown)=\"handlerValueChangeFontSize()\"\n (outFocusAndBlur)=\"handlerValueChangeFontSize($event)\" />\n </div>\n }\n @if (option.type === 'color') {\n <div class=\"ql-color ql-picker ql-color-picker !flex items-center {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover [config]=\"{ content: 'i18n_text_color', zIndex: zIndex() }\">\n <libs_ui-components-buttons-select_color\n [zIndex]=\"zIndex()\"\n [direction]=\"'top'\"\n [externalContent]=\"true\"\n [customOptions]=\"{\n showAlpha: true,\n }\"\n [applyNow]=\"false\"\n (outColorChangeMultipleType)=\"handlerSetFormat('color', $event)\">\n <div\n class=\"libs-ui-icon-editor-color-text libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"\n [class.libs-ui-icon-editor-color-text-active]=\"!!colorSelected()\"\n [style.--color-selected]=\"colorSelected()\"></div>\n </libs_ui-components-buttons-select_color>\n </libs_ui-components-popover>\n </div>\n }\n @if (option.type === 'background') {\n <div class=\"ql-background ql-picker ql-color-picker !flex items-center {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover [config]=\"{ content: 'i18n_background_color', zIndex: zIndex() }\">\n <libs_ui-components-buttons-select_color\n [zIndex]=\"zIndex()\"\n [direction]=\"'top'\"\n [externalContent]=\"true\"\n [applyNow]=\"false\"\n [customOptions]=\"{\n showAlpha: true,\n }\"\n (outColorChangeMultipleType)=\"handlerSetFormat('background', $event)\">\n <div\n class=\"libs-ui-icon-editor-color-background libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"\n [class.libs-ui-icon-editor-color-background-active]=\"!!backgroundSelected()\"\n [style.--background-selected]=\"backgroundSelected()\"></div>\n </libs_ui-components-buttons-select_color>\n </libs_ui-components-popover>\n </div>\n }\n @if (option.type === 'bold') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_font_weight_bold', zIndex: zIndex() }\"\n class=\"ql-bold !flex\"></button>\n </div>\n }\n @if (option.type === 'italic') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_font_style_italic', zIndex: zIndex() }\"\n class=\"ql-italic !flex\"></button>\n </div>\n }\n @if (option.type === 'underline') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_text_decoration_underline', zIndex: zIndex() }\"\n class=\"ql-underline !flex\"></button>\n </div>\n }\n @if (option.type === 'strikeThrough') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_text_line_through', zIndex: zIndex() }\"\n class=\"ql-strike !flex\"></button>\n </div>\n }\n @if (option.type === 'script.sub') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_script_sub', zIndex: zIndex() }\"\n class=\"ql-script flex\"\n value=\"sub\"></button>\n </div>\n }\n @if (option.type === 'script.super') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_script_super', zIndex: zIndex() }\"\n class=\"ql-script flex\"\n value=\"super\"></button>\n </div>\n }\n @if (option.type === 'adjust') {\n <div class=\"relative !flex items-center ql-align ql-picker ql-icon-picker !w-max {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover [config]=\"{ content: 'i18n_adjust', zIndex: zIndex() }\">\n <libs_ui-components-dropdown\n [isNgContent]=\"true\"\n [ignoreStopPropagationEvent]=\"true\"\n [labelPopoverFullWidth]=\"true\"\n [listConfig]=\"listConfigAlign()\"\n [listHiddenInputSearch]=\"true\"\n [popoverCustomConfig]=\"{ ignoreArrow: true, classInclude: '!w-[32px] overflow-hidden !py-[4px]', position: { mode: 'start', distance: 0 }, paddingLeftItem: false }\"\n [listHasButtonUnSelectOption]=\"false\"\n [zIndex]=\"zIndex() + 1\"\n [listKeySelected]=\"adjustSelected()\"\n (outSelectKey)=\"handleSelectAlign($event)\">\n @if (alignSelected(); as alignSelected) {\n <div class=\"flex items-center cursor-pointer\">\n <i\n [class]=\"iconAlignSelectedComputed()\"\n [class.text-[var(--libs-ui-color-light-1)]]=\"adjustSelected() !== undefined\"></i>\n <i class=\"libs-ui-icon-move-right rotate-90 text-[#6a7383] text-[16px] ml-[4px]\"></i>\n </div>\n }\n </libs_ui-components-dropdown>\n </libs_ui-components-popover>\n </div>\n }\n @if (option.type === 'indentIncrease') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_indent_increase', zIndex: zIndex() }\"\n class=\"ql-indent\"\n value=\"+1\"></button>\n </div>\n }\n @if (option.type === 'indentDecrease') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_indent_decrease', zIndex: zIndex() }\"\n class=\"ql-indent\"\n value=\"-1\"></button>\n </div>\n }\n @if (option.type === 'listBulleted') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_list_bulleted', zIndex: zIndex() }\"\n class=\"ql-list ql-bullet\"\n value=\"bullet\"></button>\n </div>\n }\n @if (option.type === 'listNumbered') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_list_numbered', zIndex: zIndex() }\"\n class=\"ql-list ql-ordered\"\n value=\"ordered\"></button>\n </div>\n }\n @if (option.type === 'listChecked') {\n <div\n [class]=\"option.classInclude || 'mx-[8px]'\"\n [class.ql-active]=\"listCheckboxSelected()\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_list_numbered', zIndex: zIndex() }\"\n class=\"ql-list ql-check\"\n value=\"check\"></button>\n </div>\n }\n @if (option.type === 'personalize' && templateToolBarPersonalize(); as templateToolBarPersonalize) {\n <div class=\"flex items-center {{ option.classInclude || 'mx-[8px]' }}\">\n <ng-container *ngTemplateOutlet=\"templateToolBarPersonalize\"></ng-container>\n </div>\n }\n @if (option.type === 'blockquote') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_quote', zIndex: zIndex() }\"\n class=\"ql-blockquote\"></button>\n </div>\n }\n @if (option.type === 'link') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_insert_link', zIndex: zIndex() }\"\n class=\"ql-link\"></button>\n </div>\n }\n @if (option.type === 'unLink') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_unlink', zIndex: zIndex() }\"\n class=\"ql-unLink\"\n (click)=\"handlerUnlink($event)\"></button>\n </div>\n }\n @if (option.type === 'image') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_image', zIndex: zIndex() }\"\n class=\"ql-image\"></button>\n </div>\n }\n @if (option.type === 'video') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_video', zIndex: zIndex() }\"\n class=\"ql-video\"></button>\n </div>\n }\n @if (option.type === 'emoji') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <libs_ui-components-emoji\n [zIndex]=\"zIndex()\"\n [isNgContent]=\"true\"\n (outEventEmoji)=\"handlerInsertEmoji($event)\">\n <button class=\"ql-emoji\"></button>\n </libs_ui-components-emoji>\n </div>\n }\n @if (option.type === 'table') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_table', zIndex: zIndex() }\"\n class=\"ql-table\"></button>\n </div>\n }\n @if (option.type === 'line') {\n <div class=\"w-[1px] h-[16px] libs-ui-border-right-general\"></div>\n }\n</ng-template>\n\n<libs_ui-components-popover\n #inputLinkRef\n [mode]=\"'click'\"\n [ignoreHiddenPopoverContentWhenMouseLeave]=\"true\"\n [config]=\"{ template: viewLink, whiteTheme: true, ignoreArrow: true, zIndex: zIndex() }\"\n (outEvent)=\"handlerPopoverEvent($event)\" />\n\n<ng-template #viewLink>\n <div class=\"flex justify-between items-center px-[16px] py-[8px] rounded-[8px]\">\n <div\n class=\"w-full min-w-0 libs-ui-font-h5r\"\n [type]=\"'text'\"\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: link().url, zIndex: zIndex() + 1 }\">\n {{ link().url }}\n </div>\n <div class=\"flex items-center ml-[16px]\">\n <libs_ui-components-buttons-button\n [type]=\"'button-link-primary'\"\n [iconOnlyType]=\"true\"\n [classIconLeft]=\"'libs-ui-icon-edit-line'\"\n [classInclude]=\"'!p-0'\"\n (outClick)=\"handlerEditLink($event)\" />\n <libs_ui-components-buttons-button\n [type]=\"'button-link-custom'\"\n [buttonCustom]=\"{\n configStepColor: {\n text: '#071631',\n text_hover: '#ee2d41',\n text_active: '#ee2d41',\n text_disable: '071631',\n },\n rootColor: '#071631',\n }\"\n [classInclude]=\"'!p-0 ml-[12px]'\"\n [iconOnlyType]=\"true\"\n [classIconLeft]=\"'libs-ui-icon-link-broken'\"\n (outClick)=\"handlerUnlink($event)\" />\n </div>\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host ::ng-deep .quill-container .ql-container{font-family:var(--libs-ui-font-family-name),\"Arial\"!important;font-weight:400}:host ::ng-deep .quill-container .ql-container .ql-editor{font-family:var(--libs-ui-font-family-name),\"Arial\"!important;font-weight:400;height:auto;min-height:100%;resize:var(--resize)}:host ::ng-deep .quill-container .ql-container .ql-editor ol,:host ::ng-deep .quill-container .ql-container .ql-editor ul{list-style:none!important;padding-left:0!important;margin-left:0!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li,:host ::ng-deep .quill-container .ql-container .ql-editor ul li{list-style-type:none!important}:host ::ng-deep .quill-container .ql-container .ql-editor li,:host ::ng-deep .quill-container .ql-container .ql-editor ol li,:host ::ng-deep .quill-container .ql-container .ql-editor ul li,:host ::ng-deep .quill-container .ql-container .ql-editor li[data-list]{counter-reset:none!important;counter-increment:none!important;counter-set:none!important}@supports (counter-set: none){:host ::ng-deep .quill-container .ql-container .ql-editor li[data-list]{counter-set:none!important}}:host ::ng-deep .quill-container .ql-container .ql-editor ol li:not(.ql-direction-rtl){counter-increment:none!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol{counter-reset:my-counter 0!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol>li[data-list=ordered]{counter-increment:my-counter 1!important;position:relative;padding-left:8px}:host ::ng-deep .quill-container .ql-container .ql-editor ol>li[data-list=ordered]:before{content:counter(my-counter) \". \"}:host ::ng-deep .quill-container .ql-container .ql-editor ol li span:before{content:\"\"!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li[data-list=bullet]:before{content:\"\\2022 \"!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li[data-list=unchecked]:before{content:\"\\2610 \"!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li[data-list=checked]:before{content:\"\\2611 \"!important}:host ::ng-deep .quill-container .ql-container .ql-blank:before{font-style:normal!important;font-size:12px!important;color:#9ca2ad!important;font-family:var(--libs-ui-font-family-name),arial!important}:host ::ng-deep .quill-container .ql-active span:before{color:var(--libs-ui-color-light-1)!important}:host ::ng-deep .quill-container .ql-active .ql-fill{fill:var(--libs-ui-color-light-1)!important}:host ::ng-deep .quill-container .ql-script :hover .ql-fill{fill:var(--libs-ui-color-light-1)!important}:host ::ng-deep .quill-container .ql-toolbar{padding:0!important;border:none!important}:host ::ng-deep .quill-container .ql-toolbar button{padding:0!important;height:16px!important;width:16px!important}:host ::ng-deep .quill-container .ql-tooltip{display:none!important}:host ::ng-deep .quill-container #editor-resizer .toolbar{top:0!important;left:0!important;transform:none!important;font-family:var(--libs-ui-font-family-name),\"Arial\"!important;font-weight:400;font-size:12px}:host ::ng-deep .quill-container .libs-ui-icon-editor-color-text-active{color:var(--color-selected)!important}:host ::ng-deep .quill-container .libs-ui-icon-editor-color-background-active{color:var(--background-selected)!important}:host ::ng-deep .quill-container .ql-snow .ql-picker{font-size:12px!important;height:32px;float:none;width:auto}:host ::ng-deep .selected-cell{background-color:#0096ff4d}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: LibsUiComponentsDropdownComponent, selector: "libs_ui-components-dropdown", inputs: ["useXssFilter", "popoverElementRefCustom", "classInclude", "ignoreStopPropagationEvent", "flagMouse", "flagMouseContent", "popoverCustomConfig", "isNgContent", "zIndex", "convertItemSelected", "getPopoverItemSelected", "httpRequestDetailItemById", "lengthKeys", "textDisplayWhenNoSelect", "textDisplayWhenMultiSelect", "classIncludeTextDisplayWhenNoSelect", "fieldLabel", "fieldGetLabel", "labelPopoverConfig", "labelPopoverFullWidth", "hasContentUnitRight", "listSearchNoDataTemplateRef", "fieldGetImage", "imageSize", "typeShape", "fieldGetIcon", "fieldGetTextAvatar", "fieldGetColorAvatar", "classAvatarInclude", "getLastTextAfterSpace", "linkImageError", "showError", "showBorderError", "disable", "readonly", "labelConfig", "disableLabel", "listSearchConfig", "isSearchOnline", "listHiddenInputSearch", "listSearchPadding", "listKeySearch", "listDividerClassInclude", "listConfig", "listButtonsOther", "listHasButtonUnSelectOption", "listClickExactly", "listBackgroundCustom", "listMaxItemShow", "listKeySelected", "listMultiKeySelected", "listKeysDisable", "listKeysHidden", "validRequired", "validMaxItemSelected", "changeValidUndefinedResetError", "allowSelectItemMultiple", "focusInputSearch", "onlyEmitDataWhenReset", "resetKeyWhenSelectAllKey", "listConfigHasDivider", "classIncludeIcon", "classIncludeContent", "listIgnoreClassDisableDefaultWhenUseKeysDisableItem", "tabKeyActive", "tabsConfig", "ignoreBorderBottom"], outputs: ["flagMouseChange", "flagMouseContentChange", "lengthKeysChange", "showBorderErrorChange", "listKeySelectedChange", "listMultiKeySelectedChange", "tabKeyActiveChange", "outSelectKey", "outSelectMultiKey", "outFunctionsControl", "outValidEvent", "outChangStageFlagMouse", "outDataChange", "outClickButtonOther", "outShowList", "outChangeTabKeyActive"] }, { kind: "component", type: LibsUiComponentsButtonsButtonComponent, selector: "libs_ui-components-buttons-button", inputs: ["flagMouse", "type", "buttonCustom", "sizeButton", "label", "disable", "isPending", "imageLeft", "classInclude", "classIconLeft", "classIconRight", "classLabel", "iconOnlyType", "popover", "ignoreStopPropagationEvent", "zIndex", "widthLabelPopover", "styleIconLeft", "styleButton", "ignoreFocusWhenInputTab", "ignoreSetClickWhenShowPopover", "ignorePointerEvent", "isActive", "isHandlerEnterDocumentClickButton"], outputs: ["outClick", "outPopoverEvent", "outFunctionsControl"] }, { kind: "component", type: LibsUiComponentsSkeletonComponent, selector: "libs_ui-components-skeleton", inputs: ["config"] }, { kind: "directive", type: LibsUiComponentsScrollOverlayDirective, selector: "[LibsUiComponentsScrollOverlayDirective]", inputs: ["debugMode", "ignoreInit", "classContainer", "options", "elementCheckScrollX", "elementCheckScrollY", "elementScroll"], outputs: ["outScroll", "outScrollX", "outScrollY", "outScrollTop", "outScrollBottom"] }, { kind: "component", type: LibsUiComponentsEmojiComponent, selector: "libs_ui-components-emoji", inputs: ["configPopover", "isNgContent", "zIndex", "modePopoverPosition", "classPopup", "classInclude", "classIconInclude"], outputs: ["outEventEmoji", "outFunctionsControl"] }, { kind: "component", type: LibsUiComponentsPopoverComponent, selector: "libs_ui-components-popover,[LibsUiComponentsPopoverDirective]", inputs: ["debugId", "flagMouse", "type", "mode", "config", "ignoreShowPopover", "elementRefCustom", "initEventInElementRefCustom", "classInclude", "ignoreHiddenPopoverContentWhenMouseLeave", "ignoreStopPropagationEvent", "ignoreCursorPointerModeLikeClick", "isAddContentToParentDocument", "ignoreClickOutside"], outputs: ["outEvent", "outChangStageFlagMouse", "outEventPopoverContent", "outFunctionsControl"] }, { kind: "component", type: LibsUiComponentsInputsValidComponent, selector: "libs_ui-components-inputs-valid", inputs: ["item", "labelConfig", "emitEmptyInDataTypeNumber", "ignoreBlockInputMaxValue", "fieldNameBind", "showCount", "typeComponentSelectItem", "valueComponentSelectItem", "disableComponentSelectItem", "tagInput", "dataType", "typeInput", "modeInput", "resetAutoCompletePassword", "textAreaEnterNotNewLine", "fixedFloat", "acceptNegativeValue", "valueUpDownNumber", "ignoreWidthInput100", "classIncludeInput", "classContainerInput", "readonly", "disable", "noBorder", "backgroundNone", "useColorModeExist", "placeholder", "keepPlaceholderOnly", "classContainerBottomInput", "autoRemoveEmoji", "defaultHeight", "maxHeightTextArea", "minHeightTextArea", "ignoreShowError", "borderError", "iconLeftClass", "popoverContentIconLeft", "iconRightClass", "popoverContentIconRight", "zIndexPopoverContent", "unitsLeft", "configUnitLeft", "keySelectedUnitLeft", "unitsRight", "configUnitRight", "keySelectedUnitRight", "maxValueNumber", "minValueNumber", "ignoreContentLeft", "ignoreContentRight", "isBaselineStyle", "valuePatternShowError", "validPattern", "validRequired", "validMinLength", "validMinValue", "validMaxValue", "validMaxLength", "functionValid", "maxLength", "positionMessageErrorStartInput", "classInclude", "resize", "templateLeftBottomInput", "templateRightBottomInput", "onlyAcceptNegativeValue", "autoAddZeroLessThan10InTypeInt", "maxLengthNumberCount", "classMessageErrorInclude", "ignoreStopPropagationEvent", "ignoreUnitRightClassReadOnly", "paddingRightCustomSpecific", "focusTimeOut", "debounceTimeValidate"], outputs: ["itemChange", "outValueChange", "outSelect", "outIconLeft", "outIconRight", "outClickButtonLabel", "outSwitchEventLabel", "outLabelRightClick", "outEnterInputEvent", "outHeightAreaChange", "outFunctionsControl", "outFocusAndBlur", "outChangeValueByButtonUpDown"] }, { kind: "component", type: LibsUiComponentsButtonsSelectColorComponent, selector: "libs_ui-components-buttons-select_color", inputs: ["zIndex", "customOptions", "externalContent", "direction", "button", "applyNow"], outputs: ["zIndexChange", "outColorChange", "outColorChangeMultipleType"] }, { kind: "component", type: LibsUiComponentsLabelComponent, selector: "libs_ui-components-label", inputs: ["iconPopoverClass", "classInclude", "labelLeft", "labelLeftClass", "labelLeftBehindToggleButton", "popover", "required", "buttonsLeft", "disableButtonsLeft", "buttonsRight", "disableButtonsRight", "labelRight", "labelRightClass", "labelRightRequired", "hasToggle", "toggleSize", "toggleActive", "toggleDisable", "description", "descriptionClass", "buttonsDescription", "disableButtonsDescription", "buttonsDescriptionContainerClass", "onlyShowCount", "zIndexPopover", "timerDestroyPopover", "count", "limitLength"], outputs: ["outClickButton", "outSwitchEvent", "outLabelRightClick", "outLabelLeftClick"] }, { kind: "directive", type: LibsUiComponentsInputsMentionDirective, selector: "[LibsUiComponentsInputsMentionDirective]", inputs: ["timeDelayInit", "mentionConfig", "mentionListTemplate"], outputs: ["outSearchTerm", "outItemSelected", "outToggle", "outInsertMention", "outFunctionControl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2116
+ }
2117
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuill2xComponent, decorators: [{
2118
+ type: Component,
2119
+ args: [{ selector: 'libs_ui-components-inputs-quill2x', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
2120
+ NgStyle,
2121
+ TranslateModule,
2122
+ NgTemplateOutlet,
2123
+ LibsUiComponentsDropdownComponent,
2124
+ LibsUiComponentsButtonsButtonComponent,
2125
+ LibsUiComponentsSkeletonComponent,
2126
+ LibsUiComponentsScrollOverlayDirective,
2127
+ LibsUiComponentsEmojiComponent,
2128
+ LibsUiComponentsPopoverComponent,
2129
+ LibsUiComponentsInputsValidComponent,
2130
+ LibsUiComponentsButtonsSelectColorComponent,
2131
+ LibsUiComponentsLabelComponent,
2132
+ LibsUiComponentsInputsMentionDirective,
2133
+ ], template: "<div\n #htmlContainer\n [class]=\"containerClass()\"\n [style.--resize]=\"resize()\">\n @if (label(); as label) {\n <libs_ui-components-label\n [classInclude]=\"label.classInclude\"\n [required]=\"label.required\"\n [labelLeft]=\"label.labelLeft\" />\n }\n <div\n #toolbarContainer\n class=\"relative h-[44px]\"\n [class.hidden]=\"!displayToolbar()\"\n [ngStyle]=\"quillCustomConfig()?.toolbar?.()?.styles?.() || {}\">\n <ng-container *ngTemplateOutlet=\"toolbarTemplate\" />\n </div>\n <div\n #snowContainer\n LibsUiComponentsScrollOverlayDirective\n LibsUiComponentsInputsMentionDirective\n [options]=\"{ ignoreTransparentScrollBarColorDefault: resize() !== 'none' }\"\n [mentionConfig]=\"dataConfigMention()\"\n (outToggle)=\"handleToggleMention($event)\"\n (outInsertMention)=\"handlerInsertMention($event)\"\n [class]=\"containerEditorClass()\"></div>\n @if (messageError() && showErrorLabel()) {\n <div class=\"flex items-center mt-[8px]\">\n <span class=\"text-[#ff5454] libs-ui-font-h7r\">{{ messageError() | translate }}</span>\n </div>\n }\n</div>\n\n<ng-template #toolbarTemplate>\n <div\n [class]=\"containerToolbarClass()\"\n #toolbarItems>\n @if (displaySkeletonToolbar()) {\n <libs_ui-components-skeleton\n class=\"w-full h-full\"\n [config]=\"{\n rows: [\n {\n item: { classIncludeItem: '!rounded-none' },\n },\n ],\n }\" />\n }\n <div class=\"ql-formats items-center px-[16px] py-[4px] !flex\">\n @for (option of toolbarOptionsConfig(); track option) {\n <div\n class=\"items-center\"\n [class.hidden]=\"!option.display\"\n [class.flex]=\"option.display\">\n <ng-container *ngTemplateOutlet=\"itemToolbarTemplate; context: { option, $index, $last }\" />\n </div>\n }\n <div class=\"relative\">\n @if (showButtonMoreToolbar()) {\n <libs_ui-components-buttons-button\n [type]=\"showMoreAction() ? 'button-secondary' : 'button-third'\"\n [iconOnlyType]=\"true\"\n [classInclude]=\"'!p-[1px] !h-[20px] ml-[8px]'\"\n [classIconLeft]=\"'libs-ui-icon-move-right rotate-90'\"\n (outClick)=\"handlerChangeShowMoreAction($event)\" />\n }\n <!-- Build toolbar khi click xem th\u00EAm-->\n <div\n [class.hidden]=\"!showMoreAction()\"\n class=\"absolute bg-[#ffffff] shadow-[0px_4px_16px_0px_rgba(0,20,51,0.10)] z-[1] flex items-center pl-[8px] pr-[16px] py-[4px] rounded-[8px] top-[31px] right-[0px]\">\n @for (option of toolbarOptionsConfig(); track option) {\n <div\n class=\"items-center\"\n [class.hidden]=\"option.display !== false\"\n [class.flex]=\"option.display === false\">\n <ng-container *ngTemplateOutlet=\"itemToolbarTemplate; context: { option, $index, $last }\" />\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</ng-template>\n\n<ng-template\n #itemToolbarTemplate\n let-$index=\"$index\"\n let-$last=\"$last\"\n let-option=\"option\">\n @if (option.type === 'undo') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_undo', zIndex: zIndex() }\"\n class=\"ql-undo\"></button>\n </div>\n }\n @if (option.type === 'redo') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_redo', zIndex: zIndex() }\"\n class=\"ql-redo\"></button>\n </div>\n }\n @if (option.type === 'fontFamily') {\n <div\n class=\"relative w-[132px] h-full rounded-[4px] libs-ui-border-general {{ option.classInclude || 'mx-[8px]' }}\"\n [class.bg-white]=\"!readonly() && !loadingUploadImage()\">\n <libs_ui-components-dropdown\n [isNgContent]=\"true\"\n [labelPopoverFullWidth]=\"true\"\n [listConfig]=\"listConfigFont()\"\n [listHasButtonUnSelectOption]=\"false\"\n [listHiddenInputSearch]=\"true\"\n [readonly]=\"readonly()\"\n [zIndex]=\"1250\"\n (outSelectKey)=\"handleSelectFont($event)\">\n @if (fontSelected(); as fontSelected) {\n <div class=\"flex items-center cursor-pointer py-[7px] libs-ui-font-h5r pl-[16px] pr-[40px]\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [ignoreStopPropagationEvent]=\"true\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ labelFontSelectedComputed() }}\n </libs_ui-components-popover>\n <i class=\"libs-ui-icon-move-right rotate-90 absolute right-[12px] text-[#6a7383] text-[16px]\"></i>\n </div>\n }\n </libs_ui-components-dropdown>\n </div>\n }\n @if (option.type === 'header') {\n <div\n class=\"relative w-[138px] h-full rounded-[4px] libs-ui-border-general {{ option.classInclude || 'mx-[8px]' }}\"\n [class.bg-white]=\"!readonly() && !loadingUploadImage()\">\n <libs_ui-components-dropdown\n [isNgContent]=\"true\"\n [labelPopoverFullWidth]=\"true\"\n [popoverCustomConfig]=\"{ widthByParent: false, width: 200, classInclude: 'h-[254px]', classIncludeOverlayBody: 'h-full', position: { mode: 'start', distance: -8 } }\"\n [listConfig]=\"listConfigHeader()\"\n [listHasButtonUnSelectOption]=\"false\"\n [listHiddenInputSearch]=\"true\"\n [readonly]=\"readonly()\"\n [listMaxItemShow]=\"-1\"\n [zIndex]=\"1250\"\n (outSelectKey)=\"handleSelectHeader($event)\">\n @if (headerSelected(); as headerSelected) {\n <div class=\"flex items-center cursor-pointer py-[7px] libs-ui-font-h5r pl-[16px] pr-[40px] {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover\n [type]=\"'text'\"\n [ignoreStopPropagationEvent]=\"true\"\n [config]=\"{ zIndex: zIndex() }\">\n {{ labelHeaderSelectedComputed() }}\n </libs_ui-components-popover>\n <i class=\"libs-ui-icon-move-right rotate-90 absolute right-[12px] text-[#6a7383] text-[16px]\"></i>\n </div>\n }\n </libs_ui-components-dropdown>\n </div>\n }\n @if (option.type === 'fontSize') {\n <div class=\" {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-inputs-valid\n #inputSizeRef\n [dataType]=\"'int'\"\n [(item)]=\"fontSize\"\n [fieldNameBind]=\"'value'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"72\"\n [minValueNumber]=\"8\"\n [readonly]=\"readonly() || loadingUploadImage()\"\n [classContainerInput]=\"'w-[72px] h-[32px]'\"\n (outChangeValueByButtonUpDown)=\"handlerValueChangeFontSize()\"\n (outFocusAndBlur)=\"handlerValueChangeFontSize($event)\" />\n </div>\n }\n @if (option.type === 'color') {\n <div class=\"ql-color ql-picker ql-color-picker !flex items-center {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover [config]=\"{ content: 'i18n_text_color', zIndex: zIndex() }\">\n <libs_ui-components-buttons-select_color\n [zIndex]=\"zIndex()\"\n [direction]=\"'top'\"\n [externalContent]=\"true\"\n [customOptions]=\"{\n showAlpha: true,\n }\"\n [applyNow]=\"false\"\n (outColorChangeMultipleType)=\"handlerSetFormat('color', $event)\">\n <div\n class=\"libs-ui-icon-editor-color-text libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"\n [class.libs-ui-icon-editor-color-text-active]=\"!!colorSelected()\"\n [style.--color-selected]=\"colorSelected()\"></div>\n </libs_ui-components-buttons-select_color>\n </libs_ui-components-popover>\n </div>\n }\n @if (option.type === 'background') {\n <div class=\"ql-background ql-picker ql-color-picker !flex items-center {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover [config]=\"{ content: 'i18n_background_color', zIndex: zIndex() }\">\n <libs_ui-components-buttons-select_color\n [zIndex]=\"zIndex()\"\n [direction]=\"'top'\"\n [externalContent]=\"true\"\n [applyNow]=\"false\"\n [customOptions]=\"{\n showAlpha: true,\n }\"\n (outColorChangeMultipleType)=\"handlerSetFormat('background', $event)\">\n <div\n class=\"libs-ui-icon-editor-color-background libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"\n [class.libs-ui-icon-editor-color-background-active]=\"!!backgroundSelected()\"\n [style.--background-selected]=\"backgroundSelected()\"></div>\n </libs_ui-components-buttons-select_color>\n </libs_ui-components-popover>\n </div>\n }\n @if (option.type === 'bold') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_font_weight_bold', zIndex: zIndex() }\"\n class=\"ql-bold !flex\"></button>\n </div>\n }\n @if (option.type === 'italic') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_font_style_italic', zIndex: zIndex() }\"\n class=\"ql-italic !flex\"></button>\n </div>\n }\n @if (option.type === 'underline') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_text_decoration_underline', zIndex: zIndex() }\"\n class=\"ql-underline !flex\"></button>\n </div>\n }\n @if (option.type === 'strikeThrough') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_text_line_through', zIndex: zIndex() }\"\n class=\"ql-strike !flex\"></button>\n </div>\n }\n @if (option.type === 'script.sub') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_script_sub', zIndex: zIndex() }\"\n class=\"ql-script flex\"\n value=\"sub\"></button>\n </div>\n }\n @if (option.type === 'script.super') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_script_super', zIndex: zIndex() }\"\n class=\"ql-script flex\"\n value=\"super\"></button>\n </div>\n }\n @if (option.type === 'adjust') {\n <div class=\"relative !flex items-center ql-align ql-picker ql-icon-picker !w-max {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-popover [config]=\"{ content: 'i18n_adjust', zIndex: zIndex() }\">\n <libs_ui-components-dropdown\n [isNgContent]=\"true\"\n [ignoreStopPropagationEvent]=\"true\"\n [labelPopoverFullWidth]=\"true\"\n [listConfig]=\"listConfigAlign()\"\n [listHiddenInputSearch]=\"true\"\n [popoverCustomConfig]=\"{ ignoreArrow: true, classInclude: '!w-[32px] overflow-hidden !py-[4px]', position: { mode: 'start', distance: 0 }, paddingLeftItem: false }\"\n [listHasButtonUnSelectOption]=\"false\"\n [zIndex]=\"zIndex() + 1\"\n [listKeySelected]=\"adjustSelected()\"\n (outSelectKey)=\"handleSelectAlign($event)\">\n @if (alignSelected(); as alignSelected) {\n <div class=\"flex items-center cursor-pointer\">\n <i\n [class]=\"iconAlignSelectedComputed()\"\n [class.text-[var(--libs-ui-color-light-1)]]=\"adjustSelected() !== undefined\"></i>\n <i class=\"libs-ui-icon-move-right rotate-90 text-[#6a7383] text-[16px] ml-[4px]\"></i>\n </div>\n }\n </libs_ui-components-dropdown>\n </libs_ui-components-popover>\n </div>\n }\n @if (option.type === 'indentIncrease') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_indent_increase', zIndex: zIndex() }\"\n class=\"ql-indent\"\n value=\"+1\"></button>\n </div>\n }\n @if (option.type === 'indentDecrease') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_indent_decrease', zIndex: zIndex() }\"\n class=\"ql-indent\"\n value=\"-1\"></button>\n </div>\n }\n @if (option.type === 'listBulleted') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_list_bulleted', zIndex: zIndex() }\"\n class=\"ql-list ql-bullet\"\n value=\"bullet\"></button>\n </div>\n }\n @if (option.type === 'listNumbered') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_list_numbered', zIndex: zIndex() }\"\n class=\"ql-list ql-ordered\"\n value=\"ordered\"></button>\n </div>\n }\n @if (option.type === 'listChecked') {\n <div\n [class]=\"option.classInclude || 'mx-[8px]'\"\n [class.ql-active]=\"listCheckboxSelected()\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_list_numbered', zIndex: zIndex() }\"\n class=\"ql-list ql-check\"\n value=\"check\"></button>\n </div>\n }\n @if (option.type === 'personalize' && templateToolBarPersonalize(); as templateToolBarPersonalize) {\n <div class=\"flex items-center {{ option.classInclude || 'mx-[8px]' }}\">\n <ng-container *ngTemplateOutlet=\"templateToolBarPersonalize\"></ng-container>\n </div>\n }\n @if (option.type === 'blockquote') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_quote', zIndex: zIndex() }\"\n class=\"ql-blockquote\"></button>\n </div>\n }\n @if (option.type === 'link') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_insert_link', zIndex: zIndex() }\"\n class=\"ql-link\"></button>\n </div>\n }\n @if (option.type === 'unLink') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_unlink', zIndex: zIndex() }\"\n class=\"ql-unLink\"\n (click)=\"handlerUnlink($event)\"></button>\n </div>\n }\n @if (option.type === 'image') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_image', zIndex: zIndex() }\"\n class=\"ql-image\"></button>\n </div>\n }\n @if (option.type === 'video') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_video', zIndex: zIndex() }\"\n class=\"ql-video\"></button>\n </div>\n }\n @if (option.type === 'emoji') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <libs_ui-components-emoji\n [zIndex]=\"zIndex()\"\n [isNgContent]=\"true\"\n (outEventEmoji)=\"handlerInsertEmoji($event)\">\n <button class=\"ql-emoji\"></button>\n </libs_ui-components-emoji>\n </div>\n }\n @if (option.type === 'table') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <button\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: 'i18n_table', zIndex: zIndex() }\"\n class=\"ql-table\"></button>\n </div>\n }\n @if (option.type === 'line') {\n <div class=\"w-[1px] h-[16px] libs-ui-border-right-general\"></div>\n }\n</ng-template>\n\n<libs_ui-components-popover\n #inputLinkRef\n [mode]=\"'click'\"\n [ignoreHiddenPopoverContentWhenMouseLeave]=\"true\"\n [config]=\"{ template: viewLink, whiteTheme: true, ignoreArrow: true, zIndex: zIndex() }\"\n (outEvent)=\"handlerPopoverEvent($event)\" />\n\n<ng-template #viewLink>\n <div class=\"flex justify-between items-center px-[16px] py-[8px] rounded-[8px]\">\n <div\n class=\"w-full min-w-0 libs-ui-font-h5r\"\n [type]=\"'text'\"\n LibsUiComponentsPopoverDirective\n [config]=\"{ content: link().url, zIndex: zIndex() + 1 }\">\n {{ link().url }}\n </div>\n <div class=\"flex items-center ml-[16px]\">\n <libs_ui-components-buttons-button\n [type]=\"'button-link-primary'\"\n [iconOnlyType]=\"true\"\n [classIconLeft]=\"'libs-ui-icon-edit-line'\"\n [classInclude]=\"'!p-0'\"\n (outClick)=\"handlerEditLink($event)\" />\n <libs_ui-components-buttons-button\n [type]=\"'button-link-custom'\"\n [buttonCustom]=\"{\n configStepColor: {\n text: '#071631',\n text_hover: '#ee2d41',\n text_active: '#ee2d41',\n text_disable: '071631',\n },\n rootColor: '#071631',\n }\"\n [classInclude]=\"'!p-0 ml-[12px]'\"\n [iconOnlyType]=\"true\"\n [classIconLeft]=\"'libs-ui-icon-link-broken'\"\n (outClick)=\"handlerUnlink($event)\" />\n </div>\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host ::ng-deep .quill-container .ql-container{font-family:var(--libs-ui-font-family-name),\"Arial\"!important;font-weight:400}:host ::ng-deep .quill-container .ql-container .ql-editor{font-family:var(--libs-ui-font-family-name),\"Arial\"!important;font-weight:400;height:auto;min-height:100%;resize:var(--resize)}:host ::ng-deep .quill-container .ql-container .ql-editor ol,:host ::ng-deep .quill-container .ql-container .ql-editor ul{list-style:none!important;padding-left:0!important;margin-left:0!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li,:host ::ng-deep .quill-container .ql-container .ql-editor ul li{list-style-type:none!important}:host ::ng-deep .quill-container .ql-container .ql-editor li,:host ::ng-deep .quill-container .ql-container .ql-editor ol li,:host ::ng-deep .quill-container .ql-container .ql-editor ul li,:host ::ng-deep .quill-container .ql-container .ql-editor li[data-list]{counter-reset:none!important;counter-increment:none!important;counter-set:none!important}@supports (counter-set: none){:host ::ng-deep .quill-container .ql-container .ql-editor li[data-list]{counter-set:none!important}}:host ::ng-deep .quill-container .ql-container .ql-editor ol li:not(.ql-direction-rtl){counter-increment:none!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol{counter-reset:my-counter 0!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol>li[data-list=ordered]{counter-increment:my-counter 1!important;position:relative;padding-left:8px}:host ::ng-deep .quill-container .ql-container .ql-editor ol>li[data-list=ordered]:before{content:counter(my-counter) \". \"}:host ::ng-deep .quill-container .ql-container .ql-editor ol li span:before{content:\"\"!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li[data-list=bullet]:before{content:\"\\2022 \"!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li[data-list=unchecked]:before{content:\"\\2610 \"!important}:host ::ng-deep .quill-container .ql-container .ql-editor ol li[data-list=checked]:before{content:\"\\2611 \"!important}:host ::ng-deep .quill-container .ql-container .ql-blank:before{font-style:normal!important;font-size:12px!important;color:#9ca2ad!important;font-family:var(--libs-ui-font-family-name),arial!important}:host ::ng-deep .quill-container .ql-active span:before{color:var(--libs-ui-color-light-1)!important}:host ::ng-deep .quill-container .ql-active .ql-fill{fill:var(--libs-ui-color-light-1)!important}:host ::ng-deep .quill-container .ql-script :hover .ql-fill{fill:var(--libs-ui-color-light-1)!important}:host ::ng-deep .quill-container .ql-toolbar{padding:0!important;border:none!important}:host ::ng-deep .quill-container .ql-toolbar button{padding:0!important;height:16px!important;width:16px!important}:host ::ng-deep .quill-container .ql-tooltip{display:none!important}:host ::ng-deep .quill-container #editor-resizer .toolbar{top:0!important;left:0!important;transform:none!important;font-family:var(--libs-ui-font-family-name),\"Arial\"!important;font-weight:400;font-size:12px}:host ::ng-deep .quill-container .libs-ui-icon-editor-color-text-active{color:var(--color-selected)!important}:host ::ng-deep .quill-container .libs-ui-icon-editor-color-background-active{color:var(--background-selected)!important}:host ::ng-deep .quill-container .ql-snow .ql-picker{font-size:12px!important;height:32px;float:none;width:auto}:host ::ng-deep .selected-cell{background-color:#0096ff4d}\n"] }]
2134
+ }], ctorParameters: () => [] });
2135
+
2136
+ /**
2137
+ * Generated bundle index. Do not edit.
2138
+ */
2139
+
2140
+ export { LibsUiComponentsInputsQuill2xComponent, getDeltaOfQuill2xFromHTML, getHTMLFromDeltaOfQuill2x, isEmptyQuill2x };
2141
+ //# sourceMappingURL=libs-ui-components-inputs-quill2x.mjs.map