@libs-ui/components-inputs-quill 0.1.1-1

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,1836 @@
1
+ import { NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { signal, input, output, ChangeDetectionStrategy, Component, computed, inject, model, viewChild, ChangeDetectorRef, effect, untracked, DestroyRef } from '@angular/core';
4
+ import { LibsUiComponentsButtonsButtonComponent } from '@libs-ui/components-buttons-button';
5
+ import { LibsUiComponentsButtonsSelectColorComponent } from '@libs-ui/components-buttons-select-color';
6
+ import { LibsUiComponentsDropdownComponent } from '@libs-ui/components-dropdown';
7
+ import { LibsUiComponentsEmojiComponent } from '@libs-ui/components-inputs-emoji';
8
+ import { LibsUiComponentsInputsMentionDirective } from '@libs-ui/components-inputs-mention';
9
+ import { LibsUiComponentsInputsValidComponent } from '@libs-ui/components-inputs-valid';
10
+ import { LibsUiComponentsLabelComponent } from '@libs-ui/components-label';
11
+ import { LibsUiComponentsPopoverComponent } from '@libs-ui/components-popover';
12
+ import { LibsUiComponentsScrollOverlayDirective } from '@libs-ui/components-scroll-overlay';
13
+ import { LibsUiComponentsSkeletonComponent } from '@libs-ui/components-skeleton';
14
+ import { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';
15
+ import { LibsUiDynamicComponentService } from '@libs-ui/services-dynamic-component';
16
+ import { LibsUiNotificationService } from '@libs-ui/services-notification';
17
+ import { patternUrl, convertFileToBase64_ObjectUrl, get, set, isNil, xssFilter, ERROR_MESSAGE_EMPTY_VALID, ERROR_MESSAGE_MIN_LENGTH, ERROR_MESSAGE_MAX_LENGTH, getLabelBySizeFile, setStylesElement, UtilsKeyCodeConstant } from '@libs-ui/utils';
18
+ import * as i1 from '@ngx-translate/core';
19
+ import { TranslateService, TranslateModule } from '@ngx-translate/core';
20
+ import Quill from 'quill';
21
+ import { Subject, lastValueFrom, timer, fromEvent, takeUntil } from 'rxjs';
22
+ import { LibsUiComponentsModalComponent } from '@libs-ui/components-modal';
23
+ import { returnListObject } from '@libs-ui/services-http-request';
24
+ import Quill2x from 'quill2x';
25
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
26
+
27
+ const Block = Quill.import('blots/block');
28
+ class QuillDivBlot extends Block {
29
+ static tagName = 'div';
30
+ }
31
+
32
+ const ImageFormat = Quill.import('formats/image');
33
+ class CustomImageBlot extends ImageFormat {
34
+ static create(value) {
35
+ const node = super.create(value);
36
+ node.classList.add('libs-ui-quill-format-image');
37
+ return node;
38
+ }
39
+ formats() {
40
+ return {}; // Tránh Quill tự động thêm thẻ `<span>`
41
+ }
42
+ }
43
+
44
+ const Embed = Quill.import('blots/embed');
45
+ class QuillMentionBlot extends Embed {
46
+ static blotName = 'mention';
47
+ static tagName = 'span';
48
+ static className = 'libs-ui-quill-mention';
49
+ static create(data) {
50
+ const node = super.create();
51
+ node.innerText += data.value;
52
+ node.setAttribute('id', data.id);
53
+ node.setAttribute('feId', data.feId);
54
+ node.setAttribute('value', data.value);
55
+ return node;
56
+ }
57
+ static value(domNode) {
58
+ return {
59
+ id: domNode.getAttribute('id'),
60
+ feId: domNode.getAttribute('feId'),
61
+ value: domNode.getAttribute('value'),
62
+ };
63
+ }
64
+ attach() {
65
+ super.attach();
66
+ if (!this.mounted) {
67
+ this.mounted = true;
68
+ this.clickHandler = this.getClickHandler;
69
+ this.domNode.addEventListener('click', this.clickHandler, false);
70
+ }
71
+ }
72
+ getClickHandler(event) {
73
+ event.stopPropagation();
74
+ }
75
+ }
76
+
77
+ const parchment = Quill.import('parchment');
78
+ const pixelLevels = [1, 2, 3, 4, 5, 6, 7, 8];
79
+ const tabMultiplier = 3;
80
+ class IndentAttributor extends parchment.Attributor.Style {
81
+ constructor(formatName, styleProperty, attributorOptions) {
82
+ super(formatName, styleProperty, attributorOptions);
83
+ }
84
+ add(node, value) {
85
+ return super.add(node, `${+value * tabMultiplier}em`);
86
+ }
87
+ value(node) {
88
+ return parseFloat(super.value(node)) / tabMultiplier || undefined; // Don't return NaN
89
+ }
90
+ }
91
+ const indentStyle = new IndentAttributor('indent', 'margin-left', {
92
+ scope: parchment.Scope.BLOCK,
93
+ whitelist: pixelLevels.map((value) => `${value * tabMultiplier}em`),
94
+ });
95
+
96
+ class LibsUiComponentsInputsQuillLinkComponent {
97
+ dataLink = signal({ title: '', link: '' });
98
+ patternLink = signal(patternUrl());
99
+ inputValidFunctionControl = signal([]);
100
+ zIndex = input(1200);
101
+ title = input();
102
+ link = input();
103
+ ignoreCommunicateMicroEvent = input();
104
+ outClose = output();
105
+ outSaveLink = output();
106
+ ngOnInit() {
107
+ const title = this.title();
108
+ const link = this.link();
109
+ if (title || link) {
110
+ this.dataLink.update((item) => ({ ...item, title: title || '', link: link || '' }));
111
+ }
112
+ }
113
+ async handlerFunctionsControl(event) {
114
+ this.inputValidFunctionControl.update((items) => [...items, event]);
115
+ }
116
+ async validate() {
117
+ let valid = true;
118
+ for (const control of this.inputValidFunctionControl()) {
119
+ if (!(await control.checkIsValid())) {
120
+ valid = false;
121
+ }
122
+ }
123
+ return valid;
124
+ }
125
+ async handlerEvent(event) {
126
+ if (event !== 'agree') {
127
+ this.outClose.emit();
128
+ return;
129
+ }
130
+ if (await this.validate()) {
131
+ this.outSaveLink.emit(this.dataLink());
132
+ this.outClose.emit();
133
+ }
134
+ }
135
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillLinkComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
136
+ 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:/\u2026'\"\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", "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 });
137
+ }
138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillLinkComponent, decorators: [{
139
+ type: Component,
140
+ 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:/\u2026'\"\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" }]
141
+ }] });
142
+
143
+ class LibsUiComponentsInputsQuillUploadImageComponent {
144
+ buttonFooter = signal([
145
+ {
146
+ type: 'button-third',
147
+ label: 'i18n_cancel',
148
+ action: async () => this.outClose.emit(),
149
+ },
150
+ {
151
+ label: 'i18n_save',
152
+ action: () => this.handlerSaveImage(),
153
+ },
154
+ ]);
155
+ disable = signal(false);
156
+ ignoreCommunicateMicroEvent = input();
157
+ // private mediaFile =signal<IMediaFile | undefined>(undefined);
158
+ // private mediaUploadFunctionControl =signal<IMediaUploadBaseFunctionControlEvent | undefined>(undefined);
159
+ uploadImageConfig = input.required();
160
+ outClose = output();
161
+ // protected handlerFunctionsControl(event: IMediaUploadBaseFunctionControlEvent) {
162
+ // this.mediaUploadFunctionControl.set(event);
163
+ // }
164
+ // protected handlerChangeFile(event: IChangeFile) {
165
+ // this.mediaFile.set(event.files[0]);
166
+ // }
167
+ async handlerSaveImage() {
168
+ // if (!await this.mediaUploadFunctionControl()?.checkIsValid()) {
169
+ // return;
170
+ // }
171
+ // if (this.mediaFile?.origin_url) {
172
+ // this.moClose.emit(this.mediaFile.origin_url);
173
+ // return;
174
+ // }
175
+ // const body: IOptionsUploadMedia = {
176
+ // do_not_delete: true,
177
+ // file: this.mediaFile?.file,
178
+ // option: 'thumb',
179
+ // size: [{ 'width': '200', 'height': '200' }]
180
+ // };
181
+ // this.disable = true;
182
+ // try {
183
+ // const params = new HttpParamsRequest({ fromObject: { pem: BUILD_PEM_OBJECT({ isCheck: 0, action: DefineConstants.PERMISSION_ACTION_VIEW, pathCheck: '/other' }) } });
184
+ // const res = await this.mediaService.uploadImage(params, body);
185
+ // this.moClose.emit(res.data?.url);
186
+ // } catch (error) {
187
+ // console.log(error);
188
+ // this.pushMessageService.showCompTypeText('i18n_notification_manipulation_not_success', undefined, undefined, { timeRemove: 2000, type: 'error' });
189
+ // this.moClose.emit();
190
+ // } finally {
191
+ // this.disable = false;
192
+ // }
193
+ }
194
+ handlerEventModal(event) {
195
+ if (event === 'close') {
196
+ this.outClose.emit();
197
+ }
198
+ }
199
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillUploadImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
200
+ 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", "headerConfig", "bodyConfig", "footerConfig", "buttonsFooter", "title", "titleUseXssFilter", "titleUseTooltip", "titleUseInnerText"], outputs: ["showChange", "widthChange", "heightChange", "maxWidthChange", "maxHeightChange", "minWidthChange", "disableChange", "buttonsFooterChange", "outScrollContent", "outEvent", "outFunctionControl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
201
+ }
202
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillUploadImageComponent, decorators: [{
203
+ type: Component,
204
+ 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" }]
205
+ }] });
206
+
207
+ const listDataAlign = () => {
208
+ return [
209
+ { key: '', icon: 'libs-ui-icon-align-left text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
210
+ { key: 'right', icon: 'libs-ui-icon-align-right text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
211
+ { key: 'center', icon: 'libs-ui-icon-align-center text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
212
+ { key: 'justify', icon: 'libs-ui-icon-align-justify text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]' },
213
+ ];
214
+ };
215
+ const listFont = () => {
216
+ return [
217
+ { key: '', label: 'Sans Serif' },
218
+ { key: 'serif', label: 'Serif' },
219
+ { key: 'monospace', label: 'Monospace' },
220
+ ];
221
+ };
222
+ const listHeader = () => {
223
+ return [
224
+ { key: '', label: 'Normal' },
225
+ { key: '1', label: 'Heading 1' },
226
+ { key: '2', label: 'Heading 2' },
227
+ { key: '3', label: 'Heading 3' },
228
+ { key: '4', label: 'Heading 4' },
229
+ { key: '5', label: 'Heading 5' },
230
+ { key: '6', label: 'Heading 6' },
231
+ ];
232
+ };
233
+ const listConfigAlign = () => {
234
+ return {
235
+ type: 'text',
236
+ autoSelectFirstItem: true,
237
+ httpRequestData: signal({
238
+ objectInstance: returnListObject(listDataAlign()),
239
+ functionName: 'list',
240
+ argumentsValue: [],
241
+ }),
242
+ configTemplateText: signal({
243
+ fieldKey: 'key',
244
+ getClassItem: () => '!px-[8px] !py-[4px]',
245
+ getValue: (item) => `<i class='flex ${item.icon}'></i>`,
246
+ ignoreIconSelected: true,
247
+ }),
248
+ };
249
+ };
250
+ const listConfigFont = () => {
251
+ return {
252
+ type: 'text',
253
+ autoSelectFirstItem: true,
254
+ httpRequestData: signal({
255
+ objectInstance: returnListObject(listFont()),
256
+ functionName: 'list',
257
+ argumentsValue: [],
258
+ }),
259
+ configTemplateText: signal({
260
+ fieldKey: 'key',
261
+ getValue: (item) => item.label,
262
+ ignoreIconSelected: true,
263
+ }),
264
+ };
265
+ };
266
+ const listConfigHeader = () => {
267
+ return {
268
+ type: 'text',
269
+ autoSelectFirstItem: true,
270
+ httpRequestData: signal({
271
+ objectInstance: returnListObject(listHeader()),
272
+ functionName: 'list',
273
+ argumentsValue: [],
274
+ }),
275
+ configTemplateText: signal({
276
+ fieldKey: 'key',
277
+ getValue: (item) => item.label,
278
+ ignoreIconSelected: true,
279
+ }),
280
+ };
281
+ };
282
+ const toolBarOptions = (mode, hasIconImage, hasIconPersonalize) => {
283
+ return [
284
+ {
285
+ type: 'undo',
286
+ classInclude: 'mr-[8px]',
287
+ mode: ['default', 'basic'],
288
+ width: 24,
289
+ },
290
+ {
291
+ type: 'redo',
292
+ mode: ['default', 'basic'],
293
+ width: 32,
294
+ },
295
+ {
296
+ type: 'line',
297
+ mode: ['default', 'basic'],
298
+ width: 1,
299
+ },
300
+ {
301
+ type: 'header',
302
+ classInclude: 'ml-[8px]',
303
+ mode: [],
304
+ width: 146,
305
+ },
306
+ {
307
+ type: 'fontFamily',
308
+ classInclude: 'ml-[8px]',
309
+ mode: ['default'],
310
+ width: 136,
311
+ },
312
+ {
313
+ type: 'fontSize',
314
+ classInclude: 'ml-[8px] mr-[8px]',
315
+ mode: ['default'],
316
+ width: 82,
317
+ },
318
+ {
319
+ type: 'line',
320
+ mode: ['default'],
321
+ width: 1,
322
+ },
323
+ {
324
+ type: 'color',
325
+ mode: ['default', 'basic'],
326
+ width: 32,
327
+ },
328
+ {
329
+ type: 'background',
330
+ mode: ['default'],
331
+ width: 32,
332
+ },
333
+ {
334
+ type: 'line',
335
+ mode: ['default', 'basic'],
336
+ width: 1,
337
+ },
338
+ {
339
+ type: 'bold',
340
+ mode: ['default', 'basic'],
341
+ width: 32,
342
+ },
343
+ {
344
+ type: 'italic',
345
+ mode: ['default', 'basic'],
346
+ width: 32,
347
+ },
348
+ {
349
+ type: 'underline',
350
+ mode: ['default', 'basic'],
351
+ width: 32,
352
+ },
353
+ {
354
+ type: 'strikeThrough',
355
+ mode: ['default'],
356
+ width: 32,
357
+ },
358
+ {
359
+ type: 'line',
360
+ mode: ['default', 'basic'],
361
+ width: 1,
362
+ },
363
+ {
364
+ type: 'adjust',
365
+ mode: ['default'],
366
+ width: 52,
367
+ },
368
+ {
369
+ type: 'line',
370
+ mode: ['default'],
371
+ width: 32,
372
+ },
373
+ {
374
+ type: 'indentIncrease',
375
+ mode: ['default'],
376
+ width: 32,
377
+ },
378
+ {
379
+ type: 'indentDecrease',
380
+ mode: ['default'],
381
+ width: 32,
382
+ },
383
+ {
384
+ type: 'listBulleted',
385
+ mode: ['default'],
386
+ width: 32,
387
+ },
388
+ {
389
+ type: 'listNumbered',
390
+ mode: ['default'],
391
+ width: 32,
392
+ },
393
+ {
394
+ type: 'line',
395
+ mode: ['default'],
396
+ width: 1,
397
+ },
398
+ {
399
+ type: 'personalize',
400
+ mode: ['default'],
401
+ width: 32,
402
+ },
403
+ {
404
+ type: 'blockquote',
405
+ mode: ['default'],
406
+ width: 32,
407
+ },
408
+ {
409
+ type: 'link',
410
+ mode: ['default', 'basic'],
411
+ width: 32,
412
+ },
413
+ {
414
+ type: 'unLink',
415
+ mode: ['default', 'basic'],
416
+ width: 32,
417
+ },
418
+ {
419
+ type: 'image',
420
+ mode: ['default'],
421
+ width: 32,
422
+ },
423
+ {
424
+ type: 'emoji',
425
+ classInclude: 'ml-[8px]',
426
+ mode: ['default'],
427
+ width: 24,
428
+ },
429
+ {
430
+ type: 'insertTable',
431
+ mode: ['default'],
432
+ width: 32,
433
+ },
434
+ ].filter((item) => {
435
+ if (!item.mode?.includes(mode)) {
436
+ return false;
437
+ }
438
+ if (item.type === 'image') {
439
+ return hasIconImage;
440
+ }
441
+ if (item.type === 'personalize') {
442
+ return hasIconPersonalize;
443
+ }
444
+ return true;
445
+ });
446
+ };
447
+ const iconList = () => {
448
+ return [
449
+ {
450
+ key: 'bold',
451
+ icon: 'libs-ui-icon-editor-bold',
452
+ },
453
+ {
454
+ key: 'italic',
455
+ icon: 'libs-ui-icon-editor-italic',
456
+ },
457
+ {
458
+ key: 'underline',
459
+ icon: 'libs-ui-icon-editor-underlined',
460
+ },
461
+ {
462
+ key: 'image',
463
+ icon: 'libs-ui-icon-image-solid',
464
+ },
465
+ {
466
+ key: 'link',
467
+ icon: 'libs-ui-icon-link',
468
+ },
469
+ {
470
+ key: 'undo',
471
+ icon: 'libs-ui-icon-undo',
472
+ },
473
+ {
474
+ key: 'redo',
475
+ icon: 'libs-ui-icon-undo scale-x-[-1]',
476
+ },
477
+ {
478
+ key: 'strike',
479
+ icon: 'libs-ui-icon-editor-strike-through',
480
+ },
481
+ {
482
+ key: 'blockquote',
483
+ icon: 'libs-ui-icon-quote',
484
+ },
485
+ {
486
+ key: 'ordered',
487
+ icon: 'libs-ui-icon-list-numbered',
488
+ },
489
+ {
490
+ key: 'bullet',
491
+ icon: 'libs-ui-icon-list-bulleted',
492
+ },
493
+ {
494
+ key: 'background',
495
+ icon: 'libs-ui-icon-editor-color-background',
496
+ },
497
+ {
498
+ key: 'color',
499
+ icon: 'libs-ui-icon-editor-color-text',
500
+ },
501
+ {
502
+ key: 'indent.-1',
503
+ icon: 'libs-ui-icon-indent-decrease',
504
+ },
505
+ {
506
+ key: 'indent.+1',
507
+ icon: 'libs-ui-icon-indent-increase',
508
+ },
509
+ {
510
+ key: 'align.',
511
+ icon: 'align-quill libs-ui-icon-align-left',
512
+ },
513
+ {
514
+ key: 'align.center',
515
+ icon: 'align-quill libs-ui-icon-align-center',
516
+ },
517
+ {
518
+ key: 'align.right',
519
+ icon: 'align-quill libs-ui-icon-align-right',
520
+ },
521
+ {
522
+ key: 'align.justify',
523
+ icon: 'align-quill libs-ui-icon-align-justify',
524
+ },
525
+ {
526
+ key: 'unLink',
527
+ icon: 'libs-ui-icon-link-broken',
528
+ },
529
+ {
530
+ key: 'emoji',
531
+ icon: 'libs-ui-icon-face-smile',
532
+ },
533
+ {
534
+ key: 'insertTable',
535
+ icon: 'libs-ui-icon-face-smile',
536
+ },
537
+ ];
538
+ };
539
+ const uploadImageConfigDefault = () => {
540
+ return {
541
+ modeCustom: false,
542
+ zIndex: 1202,
543
+ showIcon: true,
544
+ maxImageSize: 5 * 1024 * 1024,
545
+ onlyAcceptImageHttpsLink: false,
546
+ functionUploadImage: async (files) => {
547
+ const data = [];
548
+ for (const file of files) {
549
+ data.push(await convertFileToBase64_ObjectUrl(file));
550
+ }
551
+ return data;
552
+ },
553
+ };
554
+ };
555
+ const linkDefault = () => {
556
+ return {
557
+ title: '',
558
+ url: '',
559
+ range: {
560
+ index: 0,
561
+ length: 0,
562
+ },
563
+ };
564
+ };
565
+ const fontSizeWhiteList = () => [
566
+ '8px',
567
+ '9px',
568
+ '10px',
569
+ '11px',
570
+ '12px',
571
+ '13px',
572
+ '14px',
573
+ '15px',
574
+ '16px',
575
+ '17px',
576
+ '18px',
577
+ '19px',
578
+ '20px',
579
+ '21px',
580
+ '22px',
581
+ '23px',
582
+ '24px',
583
+ '25px',
584
+ '26px',
585
+ '27px',
586
+ '28px',
587
+ '29px',
588
+ '30px',
589
+ '31px',
590
+ '32px',
591
+ '33px',
592
+ '34px',
593
+ '35px',
594
+ '36px',
595
+ '37px',
596
+ '38px',
597
+ '39px',
598
+ '40px',
599
+ '41px',
600
+ '42px',
601
+ '43px',
602
+ '44px',
603
+ '45px',
604
+ '46px',
605
+ '47px',
606
+ '48px',
607
+ '49px',
608
+ '50px',
609
+ '51px',
610
+ '52px',
611
+ '53px',
612
+ '54px',
613
+ '55px',
614
+ '56px',
615
+ '57px',
616
+ '58px',
617
+ '59px',
618
+ '60px',
619
+ '61px',
620
+ '62px',
621
+ '63px',
622
+ '64px',
623
+ '65px',
624
+ '66px',
625
+ '67px',
626
+ '68px',
627
+ '69px',
628
+ '70px',
629
+ '71px',
630
+ '72px',
631
+ ];
632
+
633
+ /* eslint-disable @typescript-eslint/no-explicit-any */
634
+ class LibsUiComponentsInputsQuillComponent {
635
+ qlEditorElement = signal(undefined);
636
+ timeoutScrollToSelectionWithElementScrollHeightAuto = signal(undefined);
637
+ loadingUploadImage = signal(false);
638
+ timeDelayInit = signal(20);
639
+ messageError = signal('');
640
+ display = signal(false);
641
+ listConfigAlign = signal(listConfigAlign());
642
+ alignSelected = signal(undefined);
643
+ iconAlignSelectedComputed = computed(() => get(this.alignSelected(), 'item.icon'));
644
+ listConfigFont = signal(listConfigFont());
645
+ fontSelected = signal(undefined);
646
+ labelFontSelectedComputed = computed(() => get(this.fontSelected(), 'item.label') || 'Sans Serif');
647
+ listConfigHeader = signal(listConfigHeader());
648
+ headerSelected = signal(undefined);
649
+ labelHeaderSelectedComputed = computed(() => get(this.headerSelected(), 'item.label') || 'Normal');
650
+ size = signal({ value: 14 });
651
+ showMoreAction = signal(false);
652
+ link = signal(linkDefault());
653
+ toolbarOptionsConfig = signal([]);
654
+ displayShowMoreToolbar = computed(() => this.toolbarOptionsConfig().some((item) => item.display === false));
655
+ displaySkeleton = computed(() => this.toolbarOptionsConfig().every((item) => item.display === undefined));
656
+ timeCalculationToolBar = signal(undefined);
657
+ popoverFunctionControl = signal(undefined);
658
+ quill = signal(undefined);
659
+ handlers = signal({ undo: this.handleUndo.bind(this), redo: this.handleRedo.bind(this) });
660
+ showMention = signal(false);
661
+ addEditLinkComponentRef = signal(undefined);
662
+ uploadImageComponentRef = signal(undefined);
663
+ onDestroy = new Subject();
664
+ dynamicComponentService = inject(LibsUiDynamicComponentService);
665
+ translate = inject(TranslateService);
666
+ isShowToolBar = input(true, { transform: (val) => val ?? true });
667
+ isToolbarPositionFixed = input(false, { transform: (val) => val ?? false });
668
+ classIncludeToolbar = input('');
669
+ stylesIncludeToolbar = input();
670
+ toolbarConfig = input();
671
+ placeholder = input('i18n_import_content', { transform: (value) => value || 'i18n_import_content' });
672
+ label = input();
673
+ item = model();
674
+ /**đặt cờ này bằng false để tối ưu hiệu năng, và chỉ lấy dữ liệu ra khi có yêu cầu lấy dữ liệu để gửi lên server , gọi refreshItemValue để cập nhật giá tri thủ công*/
675
+ autoUpdateValueWhenTextChange = input(true, { transform: (value) => value ?? true });
676
+ fieldNameBind = input('value');
677
+ readonly = input();
678
+ showErrorLabel = input(true);
679
+ showErrorBorder = input(false);
680
+ onlyShowErrorBorderInContent = input(false);
681
+ classIncludeTemplate = input();
682
+ classInclude = input();
683
+ handlersExpand = input();
684
+ validRequired = input();
685
+ validMinLength = input();
686
+ validMaxLength = input();
687
+ zIndex = input(1250, { transform: (value) => value || 1250 });
688
+ dataConfigMention = input();
689
+ blotsRegister = input();
690
+ autoFocus = input();
691
+ focusBottom = input();
692
+ blockUndoRedoKeyboard = input();
693
+ templateToolBarPersonalize = input();
694
+ template = input();
695
+ uploadImageConfig = input(uploadImageConfigDefault(), { transform: (value) => value || uploadImageConfigDefault() });
696
+ heightAuto = input();
697
+ elementScrollHeightAuto = input();
698
+ ignoreShowPopupEditLink = input();
699
+ ignoreCommunicateMicroEventPopup = input();
700
+ outShowPopupEditLink = output();
701
+ outMessageError = output();
702
+ outBlur = output();
703
+ outFocus = output();
704
+ outChange = output();
705
+ outFunctionsControl = output();
706
+ outSelectionChange = output();
707
+ outTextChange = output();
708
+ outContextMenu = output();
709
+ wrapperEditorEl = viewChild.required('wrapperEditor');
710
+ editorEl = viewChild.required('editor');
711
+ quillOptionEl = viewChild.required('quillOption');
712
+ quillEditorEl = viewChild.required('quillEditor');
713
+ toolbarItemsEl = viewChild.required('toolbarItems');
714
+ notificationService = inject(LibsUiNotificationService);
715
+ cdr = inject(ChangeDetectorRef);
716
+ constructor() {
717
+ effect(() => {
718
+ setTimeout(() => {
719
+ const classes = `${this.classInclude() || ''} h-full w-full`
720
+ .split(' ')
721
+ .map((item) => item.trim())
722
+ .filter((item) => item);
723
+ this.getQlEditorElement()?.classList.add(...classes);
724
+ }, this.timeDelayInit() + 10);
725
+ });
726
+ effect(() => {
727
+ const type = this.toolbarConfig()?.type || 'default';
728
+ const hasIconImage = this.uploadImageConfig().showIcon ?? false;
729
+ const hasIconPersonalize = !!this.templateToolBarPersonalize();
730
+ untracked(() => {
731
+ this.toolbarOptionsConfig.set(this.toolbarConfig()?.config || toolBarOptions(type, hasIconImage, hasIconPersonalize));
732
+ this.cdr.detectChanges();
733
+ this.calculatorToolbar();
734
+ });
735
+ });
736
+ }
737
+ ngOnInit() {
738
+ this.outFunctionsControl.emit(this.FunctionsControl);
739
+ }
740
+ get FunctionsControl() {
741
+ return {
742
+ checkIsValid: this.validate.bind(this),
743
+ refreshItemValue: this.updateValueByRootQuillHtml.bind(this),
744
+ setContent: this.setContent.bind(this),
745
+ insertText: this.insertText.bind(this),
746
+ insertLink: this.insertLink.bind(this),
747
+ insertImage: this.insertImage.bind(this),
748
+ setFontSize: this.setFontSize.bind(this),
749
+ setColor: async (color) => this.setStyle('color', color),
750
+ setBackground: async (color) => this.setStyle('background', color),
751
+ quill: () => this.quill(),
752
+ scrollToSelectionWithElementScrollHeightAuto: this.scrollToSelectionWithElementScrollHeightAuto.bind(this),
753
+ insertEmbed: async (range, type, data, sources) => {
754
+ this.quill()?.insertEmbed(range.index, type, data, sources || Quill.sources.USER);
755
+ this.quill()?.setSelection(range.indexSelect, range.length || 0, Quill.sources.USER);
756
+ this.scrollToSelectionWithElementScrollHeightAuto(range.indexSelect);
757
+ return;
758
+ },
759
+ reCalculatorToolbar: this.calculatorToolbar.bind(this),
760
+ };
761
+ }
762
+ async ngAfterViewInit() {
763
+ this.setHandlerFunction();
764
+ await lastValueFrom(timer(this.timeDelayInit()));
765
+ this.initQuill();
766
+ this.setContent(get(this.item(), this.fieldNameBind(), ''));
767
+ this.handlerTextChange();
768
+ this.handlerSelectionChange();
769
+ this.handlerPaste();
770
+ // gọi hàm này để quill đánh dấu là đã focus trong khi quill chưa khởi tạo xong để không bị scroll xuống dưới.sau khi quill khởi tạo xong và check nếu đang focus đang được bật thì nó không gọi lại hàm focus nữa
771
+ this.quill()?.focus();
772
+ if (this.autoFocus()) {
773
+ this.display.set(true);
774
+ this.quill()?.blur();
775
+ }
776
+ fromEvent(window, 'resize').pipe(takeUntil(this.onDestroy)).subscribe(this.calculatorToolbar.bind(this));
777
+ await lastValueFrom(timer(400));
778
+ this.display.set(true);
779
+ const cursorSpan = this.quill()?.root.querySelector('.ql-cursor');
780
+ if (cursorSpan) {
781
+ cursorSpan.remove();
782
+ }
783
+ }
784
+ initQuill() {
785
+ const size = Quill.import('attributors/style/size');
786
+ const alignStyle = Quill.import('attributors/style/align');
787
+ const icons = Quill.import('ui/icons');
788
+ const italic = Quill.import('formats/italic');
789
+ const bold = Quill.import('formats/bold');
790
+ size.whitelist = [
791
+ '8px',
792
+ '9px',
793
+ '10px',
794
+ '11px',
795
+ '12px',
796
+ '13px',
797
+ '14px',
798
+ '15px',
799
+ '16px',
800
+ '17px',
801
+ '18px',
802
+ '19px',
803
+ '20px',
804
+ '21px',
805
+ '22px',
806
+ '23px',
807
+ '24px',
808
+ '25px',
809
+ '26px',
810
+ '27px',
811
+ '28px',
812
+ '29px',
813
+ '30px',
814
+ '31px',
815
+ '32px',
816
+ '33px',
817
+ '34px',
818
+ '35px',
819
+ '36px',
820
+ '37px',
821
+ '38px',
822
+ '39px',
823
+ '40px',
824
+ '41px',
825
+ '42px',
826
+ '43px',
827
+ '44px',
828
+ '45px',
829
+ '46px',
830
+ '47px',
831
+ '48px',
832
+ '49px',
833
+ '50px',
834
+ '51px',
835
+ '52px',
836
+ '53px',
837
+ '54px',
838
+ '55px',
839
+ '56px',
840
+ '57px',
841
+ '58px',
842
+ '59px',
843
+ '60px',
844
+ '61px',
845
+ '62px',
846
+ '63px',
847
+ '64px',
848
+ '65px',
849
+ '66px',
850
+ '67px',
851
+ '68px',
852
+ '69px',
853
+ '70px',
854
+ '71px',
855
+ '72px',
856
+ ];
857
+ italic.tagName = 'i';
858
+ bold.tagName = 'b';
859
+ Quill.register(bold, true);
860
+ Quill.register(italic, true);
861
+ Quill.register(size, true);
862
+ Quill.register(CustomImageBlot, true);
863
+ Quill.register(QuillDivBlot, true);
864
+ Quill.register(alignStyle, true);
865
+ Quill.register(QuillMentionBlot, true);
866
+ this.blotsRegister()?.forEach((item) => {
867
+ Quill.register(item.component, true);
868
+ });
869
+ Quill.register({ 'formats/indent': indentStyle }, true);
870
+ fromEvent(this.quillEditorEl()?.nativeElement, 'contextmenu')
871
+ .pipe(takeUntil(this.onDestroy))
872
+ .subscribe((e) => this.outContextMenu.emit(e));
873
+ iconList().forEach((element) => set(icons, element.key, `<span class="${element.icon} hover:text-[var(--libs-ui-color-light-1)] text-[16px] text-[#6a7383]"></span>`));
874
+ this.quill.set(new Quill(this.quillEditorEl()?.nativeElement, {
875
+ modules: {
876
+ toolbar: {
877
+ container: this.quillOptionEl()?.nativeElement,
878
+ handlers: this.handlers(),
879
+ },
880
+ clipboard: {
881
+ matchVisual: false,
882
+ },
883
+ history: {
884
+ delay: 1000,
885
+ maxStack: 100,
886
+ userOnly: false,
887
+ },
888
+ keyboard: {
889
+ bindings: {
890
+ enter: {
891
+ key: 13,
892
+ handler: () => (this.showMention() ? false : true),
893
+ },
894
+ },
895
+ },
896
+ },
897
+ readOnly: this.readonly(),
898
+ placeholder: this.translate.instant(this.placeholder()),
899
+ theme: 'snow',
900
+ bounds: this.heightAuto() ? this.wrapperEditorEl()?.nativeElement : this.quillEditorEl()?.nativeElement,
901
+ scrollingContainer: this.heightAuto() ? this.wrapperEditorEl()?.nativeElement : null,
902
+ }));
903
+ this.quill()?.keyboard.addBinding({ key: 'Backspace' }, this.noPreventEmbedDeletion.bind(this));
904
+ this.quill()?.keyboard.addBinding({ key: 'Delete' }, this.noPreventEmbedDeletion.bind(this));
905
+ this.setupUndoRedoBlocking();
906
+ }
907
+ setupUndoRedoBlocking() {
908
+ const editorRoot = this.quill()?.root;
909
+ if (editorRoot && this.blockUndoRedoKeyboard()) {
910
+ const keydownHandler = (e) => {
911
+ const isUndo = (e.key === 'z' || e.key === 'Z') && (e.ctrlKey || e.metaKey) && !e.shiftKey;
912
+ const isRedo = ((e.key === 'z' || e.key === 'Z') && (e.ctrlKey || e.metaKey) && e.shiftKey) || ((e.key === 'y' || e.key === 'Y') && e.ctrlKey);
913
+ if (isUndo || isRedo) {
914
+ e.preventDefault();
915
+ e.stopPropagation();
916
+ console.log(`Blocking ${isUndo ? 'undo' : 'redo'} keyboard shortcut`);
917
+ }
918
+ };
919
+ editorRoot.addEventListener('keydown', keydownHandler, true);
920
+ this.onDestroy.subscribe(() => {
921
+ editorRoot.removeEventListener('keydown', keydownHandler, true);
922
+ });
923
+ }
924
+ }
925
+ noPreventEmbedDeletion(range) {
926
+ if (!this.quill())
927
+ return false;
928
+ const delta = this.quill()?.getContents(range.index, 1);
929
+ if (delta?.ops.some((option) => this.blotsRegister()?.find((item) => get(option.insert, item.component['blotName']) && item.ignoreDelete))) {
930
+ return false; // Ngăn không cho Quill xử lý xóa
931
+ }
932
+ return true;
933
+ }
934
+ setHandlerFunction() {
935
+ this.handlers.update((item) => ({
936
+ ...item,
937
+ image: this.handlerShowUploadImage.bind(this),
938
+ emoji: () => {
939
+ return;
940
+ },
941
+ }));
942
+ let link = false;
943
+ const handlersExpand = this.handlersExpand();
944
+ if (handlersExpand && handlersExpand.length) {
945
+ handlersExpand.forEach((element) => {
946
+ if (element.title && element.action) {
947
+ if (element.title === 'link') {
948
+ link = true;
949
+ }
950
+ this.handlers.update((item) => ({ ...item, [element.title]: element.action }));
951
+ }
952
+ });
953
+ }
954
+ if (!link) {
955
+ this.handlers.update((item) => ({ ...item, link: this.handleShowUploadLink.bind(this), unLink: this.handlerUnInsertLink.bind(this) }));
956
+ }
957
+ }
958
+ adjustIndexForImages(index) {
959
+ const content = this.quill()?.root;
960
+ let imageCount = 0;
961
+ let adjustedIndex = 0;
962
+ const adjustTextIndex = (node) => {
963
+ if (adjustedIndex > index) {
964
+ return;
965
+ }
966
+ if (node.nodeType === Node.TEXT_NODE) {
967
+ adjustedIndex += node.textContent?.length || 0;
968
+ return;
969
+ }
970
+ if (node.nodeType === Node.ELEMENT_NODE && node.nodeName === 'IMG') {
971
+ if (adjustedIndex <= index) {
972
+ imageCount++;
973
+ }
974
+ return;
975
+ }
976
+ if (node.nodeType === Node.ELEMENT_NODE) {
977
+ Array.from(node.childNodes).forEach(adjustTextIndex);
978
+ }
979
+ };
980
+ adjustTextIndex(content);
981
+ return imageCount;
982
+ }
983
+ handlerSelectionChange() {
984
+ this.quill()?.on('selection-change', (range, oldRange, source) => {
985
+ if (this.readonly()) {
986
+ return;
987
+ }
988
+ if (range === null && oldRange !== null) {
989
+ this.outBlur.emit();
990
+ return;
991
+ }
992
+ if (range !== null && oldRange === null) {
993
+ this.outFocus.emit();
994
+ return;
995
+ }
996
+ const format = this.quill()?.getFormat();
997
+ if (format) {
998
+ this.getFont(format);
999
+ this.getFontSize(format);
1000
+ }
1001
+ this.outSelectionChange.emit({
1002
+ quill: this.quill(),
1003
+ range,
1004
+ oldRange,
1005
+ source,
1006
+ });
1007
+ if (range.length === 0 && format?.link && source === 'user') {
1008
+ const selection = document.getSelection();
1009
+ const anchorNode = selection?.anchorNode;
1010
+ let linkNode = anchorNode;
1011
+ while (linkNode && linkNode.tagName !== 'A') {
1012
+ linkNode = linkNode.parentNode;
1013
+ }
1014
+ if (linkNode && linkNode.tagName === 'A') {
1015
+ const linkText = linkNode.innerText;
1016
+ const linksIndices = [];
1017
+ let indexReturn = -1;
1018
+ while (!isNil(indexReturn) && indexReturn < range.index) {
1019
+ const textContent = this.quill()?.getText();
1020
+ const linkIndex = textContent?.indexOf(linkText, linksIndices.length > 0 ? linksIndices[linksIndices.length - 1] + linkText.length : 0);
1021
+ if (!isNil(linkIndex)) {
1022
+ if (linkIndex !== -1) {
1023
+ linksIndices.push(linkIndex);
1024
+ indexReturn = linkIndex + linkText.length;
1025
+ }
1026
+ if (linkIndex === -1) {
1027
+ indexReturn = undefined;
1028
+ }
1029
+ }
1030
+ }
1031
+ if (linksIndices && linksIndices.length) {
1032
+ const rangeIndex = linksIndices[linksIndices.length - 1];
1033
+ const countImage = this.adjustIndexForImages(rangeIndex);
1034
+ setTimeout(() => {
1035
+ this.link.set({
1036
+ title: linkText,
1037
+ url: linkNode.href,
1038
+ range: {
1039
+ index: rangeIndex + countImage,
1040
+ length: linkText.length,
1041
+ },
1042
+ });
1043
+ this.popoverFunctionControl()?.showPopover(linkNode);
1044
+ }, 250);
1045
+ }
1046
+ }
1047
+ }
1048
+ });
1049
+ }
1050
+ handlerPaste() {
1051
+ set(this.quill, 'root.onpaste', async (event) => {
1052
+ if (!event || !event.clipboardData) {
1053
+ return;
1054
+ }
1055
+ const files = event.clipboardData.files;
1056
+ if (!files.length) {
1057
+ if (event.clipboardData.getData('text')) {
1058
+ this.scrollToSelectionWithElementScrollHeightAuto();
1059
+ }
1060
+ return;
1061
+ }
1062
+ event.preventDefault();
1063
+ await this.uploadImagesAndInsert(files);
1064
+ });
1065
+ }
1066
+ handlerTextChange() {
1067
+ this.quill()?.on('text-change', (event) => {
1068
+ this.outTextChange.emit({
1069
+ quill: this.quill(),
1070
+ delta: event,
1071
+ });
1072
+ setTimeout(() => {
1073
+ if (event?.ops[1]?.insert === '\n') {
1074
+ const range = { index: event?.ops[0].retain };
1075
+ const currentLeaf = this.quill()?.getLeaf(range.index)[0];
1076
+ const nextLeaf = this.quill()?.getLeaf(range.index + 1)[0];
1077
+ this.quill()?.insertEmbed(range.index, 'break', true, 'user');
1078
+ if (nextLeaf === null || currentLeaf.parent !== nextLeaf.parent) {
1079
+ this.quill()?.insertEmbed(range.index, 'break', true, 'user');
1080
+ }
1081
+ this.quill()?.setSelection((range.index || 0) + 1, 0, Quill.sources.SILENT);
1082
+ }
1083
+ });
1084
+ if (!this.autoUpdateValueWhenTextChange()) {
1085
+ return;
1086
+ }
1087
+ this.updateValueByRootQuillHtml();
1088
+ });
1089
+ }
1090
+ updateValueByRootQuillHtml() {
1091
+ const html = this.removeUnwantedPart(this.quill()?.root.innerHTML || '');
1092
+ if (!this.quill()?.root.getElementsByTagName('img').length && !this.quill()?.getText()) {
1093
+ set(this.item(), this.fieldNameBind(), '');
1094
+ this.validate();
1095
+ this.outChange.emit(get(this.item(), this.fieldNameBind()));
1096
+ return;
1097
+ }
1098
+ this.convertInnerHTMLAndSetValue(html);
1099
+ this.validate();
1100
+ this.outChange.emit(get(this.item(), this.fieldNameBind()));
1101
+ }
1102
+ handlerPopoverFunctionControl(event) {
1103
+ this.popoverFunctionControl.set(event);
1104
+ }
1105
+ handlerPopoverEvent(event) {
1106
+ if (event === 'remove') {
1107
+ this.link.set(linkDefault());
1108
+ }
1109
+ }
1110
+ handlerUnlink(event) {
1111
+ event.stopPropagation();
1112
+ const link = this.link();
1113
+ this.quill()?.formatText(link.range.index, link.range.length, { link: false });
1114
+ this.popoverFunctionControl()?.removePopoverOverlay();
1115
+ }
1116
+ handlerEdit(event) {
1117
+ event.stopPropagation();
1118
+ const link = this.link();
1119
+ if (this.ignoreShowPopupEditLink()) {
1120
+ this.outShowPopupEditLink.emit({
1121
+ dataLink: link,
1122
+ callback: async (linkEdit) => {
1123
+ if (link instanceof Object && link.url) {
1124
+ this.quill()?.deleteText(link.range.index, link.range.length, Quill.sources.USER);
1125
+ this.quill()?.insertText(link.range.index, linkEdit.title, 'link', linkEdit.link);
1126
+ return;
1127
+ }
1128
+ this.insertLink(linkEdit.title, linkEdit.link);
1129
+ },
1130
+ });
1131
+ return;
1132
+ }
1133
+ this.handleShowUploadLink(link);
1134
+ this.popoverFunctionControl()?.removePopoverOverlay();
1135
+ }
1136
+ async calculatorToolbar() {
1137
+ this.cdr.detectChanges();
1138
+ this.timeCalculationToolBar.set(setTimeout(() => {
1139
+ const toolbarWidth = this.toolbarItemsEl().nativeElement.offsetWidth || 0;
1140
+ let totalWidth = 32 + 28;
1141
+ this.toolbarOptionsConfig.update((configs) => {
1142
+ configs.forEach((item, index) => {
1143
+ item.display = true;
1144
+ totalWidth += item.width;
1145
+ if (item.type === 'line') {
1146
+ const nextItemWidth = totalWidth + this.toolbarOptionsConfig()[+index + 1].width;
1147
+ const preItem = this.toolbarOptionsConfig()[+index - 1];
1148
+ if (nextItemWidth > toolbarWidth && preItem.display === true) {
1149
+ item.display = undefined;
1150
+ return;
1151
+ }
1152
+ }
1153
+ if (totalWidth > toolbarWidth) {
1154
+ item.display = false;
1155
+ return;
1156
+ }
1157
+ });
1158
+ return [...configs];
1159
+ });
1160
+ this.cdr.detectChanges();
1161
+ }, 600));
1162
+ }
1163
+ setStyle(type, data) {
1164
+ this.quill()?.format(type, data);
1165
+ }
1166
+ getFontSize(format) {
1167
+ const fontSize = format?.size?.toString()?.replace('px', '');
1168
+ if (isNaN(parseFloat(fontSize))) {
1169
+ this.size.set({ value: 14 });
1170
+ return;
1171
+ }
1172
+ this.size.set({ value: parseFloat(fontSize) });
1173
+ }
1174
+ getFont(format) {
1175
+ const font = listFont().find((item) => item.key === format.font);
1176
+ if (!font) {
1177
+ this.fontSelected.set({ key: '', item: listFont().find((item) => item.key === '') });
1178
+ return;
1179
+ }
1180
+ this.fontSelected.set({ key: format.font, item: font });
1181
+ }
1182
+ handleUndo() {
1183
+ this.quill()?.history?.undo();
1184
+ }
1185
+ handleRedo() {
1186
+ this.quill()?.history?.redo();
1187
+ }
1188
+ async setContent(content) {
1189
+ const contentXssFilter = await xssFilter(this.removeUnwantedPart(content || ''));
1190
+ await lastValueFrom(timer(1));
1191
+ (this.quill()?.root).innerHTML = contentXssFilter;
1192
+ if (!this.autoFocus()) {
1193
+ return;
1194
+ }
1195
+ await lastValueFrom(timer(100));
1196
+ const target = this.quillEditorEl()?.nativeElement.querySelector('[contenteditable="true"]');
1197
+ const contentScroll = this.heightAuto() ? this.wrapperEditorEl()?.nativeElement : target;
1198
+ contentScroll.scrollTop = contentScroll.scrollHeight;
1199
+ if (this.focusBottom()) {
1200
+ this.quill()?.setSelection(contentXssFilter.length, 0, Quill.sources.SILENT);
1201
+ return;
1202
+ }
1203
+ this.quill()?.setSelection(0, 0, Quill.sources.SILENT);
1204
+ }
1205
+ handlerAddEmoji(emoji) {
1206
+ this.insertText(emoji, undefined, true);
1207
+ }
1208
+ handleShowUploadLink(event) {
1209
+ this.addEditLinkComponentRef.set(this.dynamicComponentService.resolveComponentFactory(LibsUiComponentsInputsQuillLinkComponent));
1210
+ const instance = this.addEditLinkComponentRef()?.instance;
1211
+ const selection = this.quill()?.getSelection();
1212
+ const textSelected = selection?.length ? this.quill()?.getText(selection?.index, selection?.length) : '';
1213
+ const data = event;
1214
+ this.addEditLinkComponentRef()?.setInput('zIndex', this.zIndex());
1215
+ this.addEditLinkComponentRef()?.setInput('title', data?.title || textSelected);
1216
+ this.addEditLinkComponentRef()?.setInput('link', data?.url || '');
1217
+ this.addEditLinkComponentRef()?.setInput('ignoreCommunicateMicroEvent', this.ignoreCommunicateMicroEventPopup());
1218
+ instance?.outSaveLink.subscribe((item) => {
1219
+ if (data instanceof Object && data.url) {
1220
+ this.quill()?.deleteText(data.range.index, data.range.length, Quill.sources.USER);
1221
+ this.quill()?.insertText(data.range.index, item.title, 'link', item.link);
1222
+ return;
1223
+ }
1224
+ this.insertLink(item.title, item.link);
1225
+ });
1226
+ instance?.outClose.subscribe(() => this.dynamicComponentService.remove(this.addEditLinkComponentRef()));
1227
+ this.dynamicComponentService.addToBody(this.addEditLinkComponentRef());
1228
+ }
1229
+ handlerUnInsertLink() {
1230
+ const selection = this.quill()?.getSelection(true);
1231
+ if (!isNil(selection)) {
1232
+ this.quill()?.formatText(selection, 'link', false);
1233
+ }
1234
+ }
1235
+ async validate() {
1236
+ this.messageError.set('');
1237
+ const validRequired = this.validRequired();
1238
+ const item = this.item();
1239
+ const fieldNameBind = this.fieldNameBind();
1240
+ const value = get(item, fieldNameBind);
1241
+ if (validRequired && validRequired.isRequired && (!fieldNameBind || !value || !value.trim())) {
1242
+ this.messageError.set(validRequired.message || ERROR_MESSAGE_EMPTY_VALID);
1243
+ this.outMessageError.emit(this.messageError());
1244
+ return false;
1245
+ }
1246
+ const validMinLength = this.validMinLength();
1247
+ if (validMinLength && fieldNameBind && value && value.trim().length < validMinLength.length) {
1248
+ this.messageError.set(validMinLength.message || ERROR_MESSAGE_MIN_LENGTH);
1249
+ this.outMessageError.emit(this.messageError());
1250
+ return false;
1251
+ }
1252
+ const validMaxLength = this.validMaxLength();
1253
+ if (validMaxLength && fieldNameBind && value && value.trim().length > this.validMaxLength.length) {
1254
+ this.messageError.set(validMaxLength.message || ERROR_MESSAGE_MAX_LENGTH);
1255
+ this.outMessageError.emit(this.messageError());
1256
+ return false;
1257
+ }
1258
+ this.outMessageError.emit(this.messageError());
1259
+ return true;
1260
+ }
1261
+ async insertImage(content, index) {
1262
+ const selection = index ?? this.quill()?.getSelection(true).index;
1263
+ if (!isNil(selection)) {
1264
+ this.quill()?.insertEmbed(selection, 'image', content);
1265
+ this.quill()?.setSelection(selection + 1, 0);
1266
+ }
1267
+ }
1268
+ async insertText(content, index, focusLast) {
1269
+ index = index ?? this.quill()?.getSelection(true).index;
1270
+ if (!isNil(index)) {
1271
+ this.quill()?.insertText(index, content, Quill.sources.USER);
1272
+ if (focusLast) {
1273
+ await lastValueFrom(timer(0));
1274
+ this.quill()?.setSelection(index + content.length, 0, Quill.sources.USER);
1275
+ }
1276
+ }
1277
+ }
1278
+ async insertLink(text, url, index) {
1279
+ const selection = this.quill()?.getSelection(true);
1280
+ const indexInsert = index ?? selection?.index;
1281
+ if (selection?.length) {
1282
+ this.quill()?.deleteText(selection.index, selection.length, Quill.sources.USER);
1283
+ }
1284
+ if (!isNil(indexInsert)) {
1285
+ this.quill()?.insertText(indexInsert, text, 'link', url);
1286
+ }
1287
+ }
1288
+ convertInnerHTMLAndSetValue(html) {
1289
+ this.setStyleForContent();
1290
+ set(this.item(), this.fieldNameBind(), html);
1291
+ }
1292
+ setStyleForContent() {
1293
+ const styleArray = [
1294
+ { selector: '.ql-font-Arial', style: 'font-family: Arial' },
1295
+ { selector: '.ql-font-sans-serif', style: 'font-family: sans-serif' },
1296
+ { selector: '.ql-font-serif', style: 'font-family: serif' },
1297
+ { selector: '.ql-font-monospace', style: 'font-family: monospace' },
1298
+ { selector: '.ql-font-Helvetica', style: 'font-family: Helvetica' },
1299
+ { selector: '.libs-ui-quill-format-image', style: 'max-width: 100%; height: auto', overrideStyle: true },
1300
+ { selector: '.libs-ui-quill-mention', style: 'font-weight: 600;-moz-osx-font-smoothing: grayscale;-webkit-font-smoothing: antialiased; color: #7239EA', overrideStyle: true },
1301
+ { selector: 'blockquote', style: 'border-left: 4px solid #ccc; margin-bottom: 5px; margin-top: 5px; padding-left: 16px', overrideStyle: true },
1302
+ ];
1303
+ this.blotsRegister()?.forEach((item) => {
1304
+ styleArray.push({ selector: `.${item.className}`, style: item.style, overrideStyle: true });
1305
+ });
1306
+ styleArray.forEach((item) => {
1307
+ const elements = this.quill()?.root.querySelectorAll(item.selector);
1308
+ elements?.forEach((element) => {
1309
+ if (item.overrideStyle) {
1310
+ element.setAttribute('style', item.style);
1311
+ return;
1312
+ }
1313
+ // Fixbug issue: https://admin-cv.mobio.vn/issues/49092
1314
+ let styleExist = element.getAttribute('style');
1315
+ if (styleExist) {
1316
+ ['font-family: Arial', 'font-family: sans-serif', 'font-family: serif', 'font-family: monospace', 'font-family: Helvetica'].forEach((font) => {
1317
+ styleExist = (styleExist?.includes(`${font};`) ? styleExist?.replace(`${font};`, '') : styleExist?.replace(font, ''));
1318
+ });
1319
+ }
1320
+ element.setAttribute('style', styleExist ? `${styleExist}${styleExist[styleExist.length - 1] === ';' ? '' : ';'}${item.style};` : item.style);
1321
+ });
1322
+ });
1323
+ }
1324
+ handleToggleMention(show) {
1325
+ this.showMention.set(show);
1326
+ }
1327
+ handlerShowUploadImage() {
1328
+ if (this.uploadImageConfig().modeCustom) {
1329
+ this.uploadImageComponentRef.set(this.dynamicComponentService.resolveComponentFactory(LibsUiComponentsInputsQuillUploadImageComponent));
1330
+ const instance = this.uploadImageComponentRef()?.instance;
1331
+ this.uploadImageComponentRef()?.setInput('uploadImageConfig', this.uploadImageConfig());
1332
+ this.uploadImageComponentRef()?.setInput('ignoreCommunicateMicroEvent', this.ignoreCommunicateMicroEventPopup());
1333
+ instance?.outClose.subscribe((link) => {
1334
+ this.dynamicComponentService.remove(this.uploadImageComponentRef());
1335
+ if (link) {
1336
+ this.insertImage(link);
1337
+ }
1338
+ });
1339
+ this.dynamicComponentService.addToBody(this.uploadImageComponentRef());
1340
+ return;
1341
+ }
1342
+ const input = document.createElement('input');
1343
+ input.setAttribute('type', 'file');
1344
+ input.setAttribute('accept', 'image/*');
1345
+ input.click();
1346
+ input.onchange = async () => {
1347
+ await this.uploadImagesAndInsert(input?.files);
1348
+ };
1349
+ }
1350
+ async uploadImagesAndInsert(files) {
1351
+ if (!files) {
1352
+ return;
1353
+ }
1354
+ const maxImageSize = this.uploadImageConfig()?.maxImageSize || 5 * 1024 * 1024;
1355
+ const images = Array.from(files).filter((file) => file && file.type.split('/')[0] === 'image' && (!this.uploadImageConfig().maxImageSize || file.size <= maxImageSize));
1356
+ if (!images.length) {
1357
+ this.notificationService.showCompTypeTextWarning(this.translate.instant('i18n_note_upload_image_volume', { value: getLabelBySizeFile(maxImageSize) }), { timeRemove: 8000 });
1358
+ return;
1359
+ }
1360
+ try {
1361
+ this.loadingUploadImage.set(true);
1362
+ const indexInsert = this.quill()?.getSelection(true).index;
1363
+ const links = await this.uploadImageConfig().functionUploadImage?.(images);
1364
+ if (!links?.length) {
1365
+ return;
1366
+ }
1367
+ links.forEach((link) => {
1368
+ if (!link || (this.uploadImageConfig().onlyAcceptImageHttpsLink && (link instanceof ArrayBuffer || !new RegExp(patternUrl()).test(link)))) {
1369
+ return;
1370
+ }
1371
+ this.insertImage(link, indexInsert);
1372
+ });
1373
+ this.scrollToSelectionWithElementScrollHeightAuto(indexInsert);
1374
+ }
1375
+ catch (error) {
1376
+ console.error(error);
1377
+ }
1378
+ finally {
1379
+ this.loadingUploadImage.set(false);
1380
+ }
1381
+ return;
1382
+ }
1383
+ insertMention(event) {
1384
+ const range = this.quill()?.getSelection();
1385
+ if (!range) {
1386
+ return;
1387
+ }
1388
+ const cursorPos = range.index;
1389
+ const startPosInsert = cursorPos - event.lengthKey;
1390
+ this.quill()?.deleteText(startPosInsert, event.lengthKey, Quill.sources.USER);
1391
+ this.quill()?.insertEmbed(startPosInsert, 'mention', event.data, Quill.sources.USER);
1392
+ this.insertText(' ', startPosInsert + 1);
1393
+ this.quill()?.setSelection(startPosInsert + 2, 0, Quill.sources.USER);
1394
+ }
1395
+ handlerInsertMention(data) {
1396
+ this.insertMention(data);
1397
+ }
1398
+ async setFontSize(size) {
1399
+ this.quill()?.format('size', `${size}px`);
1400
+ }
1401
+ handlerValueChange(value) {
1402
+ this.size.update((item) => ({ ...item, value: value }));
1403
+ this.quill()?.format('size', `${this.size().value}px`);
1404
+ }
1405
+ handleSelectAlign(event) {
1406
+ this.alignSelected.set(event);
1407
+ this.setStyle('align', event?.key);
1408
+ }
1409
+ handleSelectFont(event) {
1410
+ this.fontSelected.set(event);
1411
+ this.setStyle('font', event?.key);
1412
+ }
1413
+ handlerChangeShowMoreAction(event) {
1414
+ event.stopPropagation();
1415
+ this.showMoreAction.update((item) => !item);
1416
+ }
1417
+ scrollToSelectionWithElementScrollHeightAuto(index) {
1418
+ clearTimeout(this.timeoutScrollToSelectionWithElementScrollHeightAuto());
1419
+ if (!this.elementScrollHeightAuto()) {
1420
+ return;
1421
+ }
1422
+ this.timeoutScrollToSelectionWithElementScrollHeightAuto.set(setTimeout(() => {
1423
+ index = index ?? this.quill()?.getSelection(true).index;
1424
+ if (!isNil(index)) {
1425
+ const bounds = this.quill()?.getBounds(index, 1);
1426
+ this.elementScrollHeightAuto()?.scrollTo(0, (bounds?.top || 0) - 20);
1427
+ }
1428
+ }, 100));
1429
+ }
1430
+ getQlEditorElement() {
1431
+ if (this.qlEditorElement()) {
1432
+ return this.qlEditorElement();
1433
+ }
1434
+ for (const elementNode of this.quillEditorEl().nativeElement.getElementsByClassName('ql-editor')) {
1435
+ this.qlEditorElement.set(elementNode);
1436
+ break;
1437
+ }
1438
+ return this.qlEditorElement();
1439
+ }
1440
+ removeUnwantedPart(html) {
1441
+ if (!html) {
1442
+ return '';
1443
+ }
1444
+ return html
1445
+ .replace(/^\s*<p><br><\/p>/, '')
1446
+ .replace(/<div><br><\/div>/gi, '')
1447
+ .replace(/[\u200B-\u200D\uFEFF]/g, '');
1448
+ }
1449
+ handleSelectHeader(event) {
1450
+ this.headerSelected.set(event);
1451
+ this.setStyle('header', event?.key);
1452
+ }
1453
+ ngOnDestroy() {
1454
+ this.onDestroy.next();
1455
+ this.onDestroy.complete();
1456
+ this.dynamicComponentService.remove(this.addEditLinkComponentRef());
1457
+ this.dynamicComponentService.remove(this.uploadImageComponentRef());
1458
+ clearTimeout(this.timeCalculationToolBar());
1459
+ clearTimeout(this.timeoutScrollToSelectionWithElementScrollHeightAuto());
1460
+ }
1461
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1462
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: LibsUiComponentsInputsQuillComponent, isStandalone: true, selector: "libs_ui-components-inputs-quill", inputs: { isShowToolBar: { classPropertyName: "isShowToolBar", publicName: "isShowToolBar", isSignal: true, isRequired: false, transformFunction: null }, isToolbarPositionFixed: { classPropertyName: "isToolbarPositionFixed", publicName: "isToolbarPositionFixed", isSignal: true, isRequired: false, transformFunction: null }, classIncludeToolbar: { classPropertyName: "classIncludeToolbar", publicName: "classIncludeToolbar", isSignal: true, isRequired: false, transformFunction: null }, stylesIncludeToolbar: { classPropertyName: "stylesIncludeToolbar", publicName: "stylesIncludeToolbar", isSignal: true, isRequired: false, transformFunction: null }, toolbarConfig: { classPropertyName: "toolbarConfig", publicName: "toolbarConfig", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", 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: false, transformFunction: null }, autoUpdateValueWhenTextChange: { classPropertyName: "autoUpdateValueWhenTextChange", publicName: "autoUpdateValueWhenTextChange", isSignal: true, isRequired: false, transformFunction: null }, fieldNameBind: { classPropertyName: "fieldNameBind", publicName: "fieldNameBind", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", 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 }, onlyShowErrorBorderInContent: { classPropertyName: "onlyShowErrorBorderInContent", publicName: "onlyShowErrorBorderInContent", isSignal: true, isRequired: false, transformFunction: null }, classIncludeTemplate: { classPropertyName: "classIncludeTemplate", publicName: "classIncludeTemplate", isSignal: true, isRequired: false, transformFunction: null }, classInclude: { classPropertyName: "classInclude", publicName: "classInclude", isSignal: true, isRequired: false, transformFunction: null }, handlersExpand: { classPropertyName: "handlersExpand", publicName: "handlersExpand", 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 }, zIndex: { classPropertyName: "zIndex", publicName: "zIndex", isSignal: true, isRequired: false, transformFunction: null }, dataConfigMention: { classPropertyName: "dataConfigMention", publicName: "dataConfigMention", isSignal: true, isRequired: false, transformFunction: null }, blotsRegister: { classPropertyName: "blotsRegister", publicName: "blotsRegister", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, focusBottom: { classPropertyName: "focusBottom", publicName: "focusBottom", isSignal: true, isRequired: false, transformFunction: null }, blockUndoRedoKeyboard: { classPropertyName: "blockUndoRedoKeyboard", publicName: "blockUndoRedoKeyboard", isSignal: true, isRequired: false, transformFunction: null }, templateToolBarPersonalize: { classPropertyName: "templateToolBarPersonalize", publicName: "templateToolBarPersonalize", isSignal: true, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: true, isRequired: false, transformFunction: null }, uploadImageConfig: { classPropertyName: "uploadImageConfig", publicName: "uploadImageConfig", isSignal: true, isRequired: false, transformFunction: null }, heightAuto: { classPropertyName: "heightAuto", publicName: "heightAuto", isSignal: true, isRequired: false, transformFunction: null }, elementScrollHeightAuto: { classPropertyName: "elementScrollHeightAuto", publicName: "elementScrollHeightAuto", 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 } }, outputs: { item: "itemChange", outShowPopupEditLink: "outShowPopupEditLink", outMessageError: "outMessageError", outBlur: "outBlur", outFocus: "outFocus", outChange: "outChange", outFunctionsControl: "outFunctionsControl", outSelectionChange: "outSelectionChange", outTextChange: "outTextChange", outContextMenu: "outContextMenu" }, viewQueries: [{ propertyName: "wrapperEditorEl", first: true, predicate: ["wrapperEditor"], descendants: true, isSignal: true }, { propertyName: "editorEl", first: true, predicate: ["editor"], descendants: true, isSignal: true }, { propertyName: "quillOptionEl", first: true, predicate: ["quillOption"], descendants: true, isSignal: true }, { propertyName: "quillEditorEl", first: true, predicate: ["quillEditor"], descendants: true, isSignal: true }, { propertyName: "toolbarItemsEl", first: true, predicate: ["toolbarItems"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"flex flex-col w-full h-full\">\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 #wrapperEditor\n [attr.heightAuto]=\"heightAuto()\"\n [attr.isToolbarPositionFixed]=\"isToolbarPositionFixed()\"\n class=\"libs-ui-quill-wrapper w-full h-full relative flex flex-col\"\n [class.libs-ui-border-error-general]=\"messageError() && showErrorBorder() && !onlyShowErrorBorderInContent()\"\n [class.rounded-[4px]]=\"messageError() && showErrorBorder() && !onlyShowErrorBorderInContent()\">\n <div\n #editor\n class=\"libs-ui-quill\"\n [attr.showError]=\"messageError() && showErrorBorder() && onlyShowErrorBorderInContent()\"\n [class.!hidden]=\"!display()\">\n <div\n #quillOption\n [class.!top-[-9999px]]=\"!isShowToolBar()\"\n [class.!left-[-9999px]]=\"!isShowToolBar()\"\n [style]=\"stylesIncludeToolbar()\"\n [class.pointer-events-none]=\"readonly() || loadingUploadImage()\"\n class=\"{{ isToolbarPositionFixed() ? 'fixed !z-10 w-[510px] shadow-[0px_4px_16px_0px_rgba(0,20,51,0.10)] rounded-[4px] bg-[#ffffff] libs-ui-quill-toolbar-animation' : '' }} {{ classIncludeToolbar() }} h-[44px] shrink-0\"\n [class.bg-[#f8f9fa]]=\"!readonly() && !loadingUploadImage()\"\n [class.libs-ui-readonly-background]=\"readonly() || loadingUploadImage()\">\n <ng-container *ngTemplateOutlet=\"toolbar\"></ng-container>\n </div>\n <div\n #quillEditor\n LibsUiComponentsInputsMentionDirective\n [timeDelayInit]=\"timeDelayInit() + 50\"\n [mentionConfig]=\"dataConfigMention()\"\n [class.libs-ui-readonly-background]=\"readonly() || loadingUploadImage()\"\n [class.pointer-events-none]=\"loadingUploadImage()\"\n (outToggle)=\"handleToggleMention($event)\"\n (outInsertMention)=\"handlerInsertMention($event)\"></div>\n <ng-container\n #qlEditorScroll\n LibsUiComponentsScrollOverlayDirective\n [ignoreInit]=\"!qlEditorElement()\"\n [elementScroll]=\"qlEditorElement()\" />\n </div>\n @if (template(); as template) {\n <div [class]=\"classIncludeTemplate()\">\n <ng-container *ngTemplateOutlet=\"template\"></ng-container>\n </div>\n }\n @if (loadingUploadImage()) {\n <libs_ui-components-spinner [size]=\"'medium'\" />\n }\n </div>\n @if (messageError() && showErrorLabel()) {\n <div class=\"flex items-center leading-normal mt-[8px]\">\n <span class=\"text-[#ff5454] libs-ui-font-h7r\">{{ messageError() | translate }}</span>\n </div>\n }\n</div>\n\n<ng-template #toolbar>\n <div\n class=\"toolbar h-full w-full shrink-0\"\n #toolbarItems>\n @if (displaySkeleton()) {\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]\">\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=\"itemTemplate; context: { option, $index, $last }\"></ng-container>\n </div>\n }\n <div class=\"relative\">\n @if (displayShowMoreToolbar()) {\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=\"itemTemplate; context: { option, $index, $last }\"></ng-container>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</ng-template>\n\n<ng-template\n #itemTemplate\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 [listMaxItemShow]=\"8\"\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 [listConfig]=\"listConfigHeader()\"\n [listHasButtonUnSelectOption]=\"false\"\n [listHiddenInputSearch]=\"true\"\n [listMaxItemShow]=\"8\"\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=\"ql-picker {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-inputs-valid\n [dataType]=\"'int'\"\n [(item)]=\"size\"\n [fieldNameBind]=\"'value'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"72\"\n [minValueNumber]=\"8\"\n [readonly]=\"readonly() || loadingUploadImage()\"\n [classContainerInput]=\"'w-[72px] h-[32px]'\"\n (outValueChange)=\"handlerValueChange($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 (outColorChange)=\"setStyle('color', $event)\">\n <div class=\"libs-ui-icon-editor-color-text libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"></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 (outColorChange)=\"setStyle('background', $event)\">\n <div class=\"libs-ui-icon-editor-color-background libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"></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\"></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\"></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\"></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\"></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 [listMaxItemShow]=\"8\"\n [zIndex]=\"1250\"\n (outSelectKey)=\"handleSelectAlign($event)\">\n @if (alignSelected(); as alignSelected) {\n <div class=\"flex items-center cursor-pointer\">\n <i [class]=\"iconAlignSelectedComputed()\"></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 === '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\"></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 === 'emoji') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <libs_ui-components-emoji\n [zIndex]=\"zIndex()\"\n [isNgContent]=\"true\"\n (outEventEmoji)=\"handlerAddEmoji($event)\">\n <button class=\"ql-emoji\"></button>\n </libs_ui-components-emoji>\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 [mode]=\"'click'\"\n [ignoreHiddenPopoverContentWhenMouseLeave]=\"true\"\n [config]=\"{ template: viewLink, whiteTheme: true, ignoreArrow: true, zIndex: zIndex() }\"\n (outEvent)=\"handlerPopoverEvent($event)\"\n (outFunctionsControl)=\"handlerPopoverFunctionControl($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)=\"handlerEdit($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: ["@-webkit-keyframes animation-move{0%{transform:translateY(-12px)}to{transform:translateY(0)}}@keyframes animation-move{0%{transform:translateY(-12px)}to{transform:translateY(0)}}.libs-ui-quill{height:100%;width:100%;display:flex;flex-direction:column}.libs-ui-quill-toolbar-animation{animation:animation-move .4s ease}.libs-ui-quill-wrapper[heightAuto=true] .libs-ui-quill{height:auto}.libs-ui-quill-wrapper[heightAuto=true] .libs-ui-quill ::ng-deep .ql-container{height:auto}.libs-ui-quill-wrapper[heightAuto=true] .libs-ui-quill ::ng-deep .ql-container .ql-editor{position:relative!important}.libs-ui-quill-wrapper[isToolbarPositionFixed=true] .libs-ui-quill ::ng-deep .ql-container.ql-snow{border:0px!important}.libs-ui-quill-wrapper[isToolbarPositionFixed=false] .libs-ui-quill ::ng-deep .ql-container.ql-snow{border:solid 1px #e6e8ed!important;border-radius:0 0 4px 4px}.libs-ui-quill-wrapper[isToolbarPositionFixed=false] .libs-ui-quill ::ng-deep .ql-toolbar.ql-snow{border:solid 1px #e6e8ed!important;border-radius:4px 4px 0 0;border-bottom:unset!important}.libs-ui-quill-wrapper[isToolbarPositionFixed=false] .libs-ui-quill[showError=true] ::ng-deep .ql-container.ql-snow{border:solid 1px #ee2d41!important}:host ::ng-deep .toolbar,:host ::ng-deep .ql-formats{position:relative;width:100%}:host ::ng-deep .ql-toolbar.ql-snow .ql-formats{display:flex}:host ::ng-deep .ql-container.ql-snow{width:100%;height:100%;position:relative}:host ::ng-deep .ql-editor{font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;font-weight:400}:host ::ng-deep .ql-editor b{font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;font-weight:600}:host ::ng-deep .ql-toolbar.ql-snow{border:none;border-radius:8px;padding:0}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker-label{color:#071631;border:none!important;margin-top:5px}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker-label:hover{color:var(--libs-ui-color-light-1)!important}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker-label:hover .ql-stroke{stroke:var(--libs-ui-color-light-1)!important}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-item:hover{color:var(--libs-ui-color-light-1)!important}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-label.ql-active{color:#071631}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke{stroke:#071631}:host ::ng-deep .ql-editor{height:100%;width:100%;position:absolute;line-height:1.42!important}:host ::ng-deep .ql-editor:before{font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;color:#9ca2ad!important;font-size:12px}:host ::ng-deep .ql-editor img{height:auto;max-width:100%}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label{border:none!important}:host ::ng-deep .ql-snow .ql-picker.ql-bold{width:36px!important}:host ::ng-deep .ql-editor.ql-blank{font-size:11px!important;letter-spacing:.05px;font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;line-height:16px}:host ::ng-deep .ql-editor.ql-blank:before{font-style:normal!important;color:#9ca2ad!important}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-label :hover{color:red!important}:host ::ng-deep .ql-formats{margin-right:12px!important}:host ::ng-deep .ql-formats .ql-size.ql-picker,:host ::ng-deep .ql-formats .ql-header.ql-picker,:host ::ng-deep .ql-formats .ql-font.ql-picker{border:solid 1px #e6e8ed!important;border-radius:4px}:host ::ng-deep .ql-formats .ql-align.ql-picker .ql-picker-label{width:24px}:host ::ng-deep .ql-formats .ql-align.ql-picker .ql-picker-label:hover{color:red!important}:host ::ng-deep .ql-formats .ql-header.ql-picker .ql-picker-label:before{font-size:12px}:host ::ng-deep .ql-picker-item:before{font-size:12px}:host ::ng-deep .ql-picker-label:before{font-size:12px}:host ::ng-deep .ql-snow.ql-toolbar button,:host ::ng-deep .ql-snow .ql-toolbar button{background:none;border:none;cursor:pointer;display:flex;align-items:center;float:none;padding:0;width:inherit}:host ::ng-deep ul{padding-left:1rem!important}:host ::ng-deep li{padding-left:0!important}:host ::ng-deep .ql-tooltip{display:none!important}.ql-picker-options{max-height:130px;overflow-y:auto}.ql-snow .ql-picker.ql-size{border:1px solid #e6e7ea;box-shadow:#00000005 0 1px 3px,#1b1f2326 0 0 0 1px;border-radius:2px}.ql-snow .ql-picker.ql-font{border:1px solid #e6e7ea!important;margin-right:8px;border-radius:4px}.ql-snow .ql-picker.ql-font .ql-picker-options:before{font-size:12px}.ql-snow .ql-picker.ql-font .ql-picker-label:before{font-size:12px}.ql-font-Arial{font-family:Arial}.ql-font-sans-serif{font-family:\"sans-serif\"}.ql-font-Helvetica{font-family:Helvetica}.libs-ui-quill-mention{font-weight:500!important;color:#7239ea;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;font-weight:600}.ql-snow .ql-picker.ql-expanded .ql-picker-options{overflow:hidden}.ql-formats{margin-right:0!important}.ql-formats .ql-header{position:relative!important}.ql-formats .ql-header .ql-picker-options{position:absolute!important;top:-151px!important}.ql-formats .ql-font{position:relative!important}.ql-formats .ql-font .ql-picker-options{position:absolute!important;top:-93px!important}.ql-formats .ql-color{position:relative!important}.ql-formats .ql-color .ql-picker-options{position:absolute!important;top:-108px}.ql-formats .ql-background{position:relative!important}.ql-formats .ql-background .ql-picker-options{position:absolute!important;top:-108px}.ql-formats .ql-align{position:relative!important}.ql-formats .ql-align .ql-picker-label{padding:3px 4px!important}.ql-formats .ql-align .ql-picker-options{position:absolute!important;top:-106px}.ql-snow .ql-picker{font-size:12px!important;height:32px;float:none;width:auto}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1.TranslatePipe, name: "translate" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: LibsUiComponentsInputsMentionDirective, selector: "[LibsUiComponentsInputsMentionDirective]", inputs: ["timeDelayInit", "mentionConfig", "mentionListTemplate"], outputs: ["outSearchTerm", "outItemSelected", "outToggle", "outInsertMention", "outFunctionControl"] }, { kind: "directive", type: LibsUiComponentsScrollOverlayDirective, selector: "[LibsUiComponentsScrollOverlayDirective]", inputs: ["debugMode", "ignoreInit", "classContainer", "options", "elementCheckScrollX", "elementCheckScrollY", "elementScroll"], outputs: ["outScroll", "outScrollX", "outScrollY", "outScrollTop", "outScrollBottom"] }, { 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: "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: 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: LibsUiComponentsButtonsSelectColorComponent, selector: "libs_ui-components-buttons-select_color", inputs: ["zIndex", "customOptions", "externalContent", "direction", "button", "applyNow"], outputs: ["zIndexChange", "outColorChange", "outColorChangeMultipleType"] }, { 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: LibsUiComponentsEmojiComponent, selector: "libs_ui-components-emoji", inputs: ["configPopover", "isNgContent", "zIndex", "modePopoverPosition", "classPopup", "classInclude", "classIconInclude"], outputs: ["outEventEmoji", "outFunctionsControl"] }, { 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: LibsUiComponentsSpinnerComponent, selector: "libs_ui-components-spinner", inputs: ["type", "size"] }, { kind: "component", type: LibsUiComponentsSkeletonComponent, selector: "libs_ui-components-skeleton", inputs: ["config"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1463
+ }
1464
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuillComponent, decorators: [{
1465
+ type: Component,
1466
+ args: [{ selector: 'libs_ui-components-inputs-quill', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
1467
+ TranslateModule,
1468
+ NgTemplateOutlet,
1469
+ LibsUiComponentsInputsMentionDirective,
1470
+ LibsUiComponentsScrollOverlayDirective,
1471
+ LibsUiComponentsLabelComponent,
1472
+ LibsUiComponentsDropdownComponent,
1473
+ LibsUiComponentsPopoverComponent,
1474
+ LibsUiComponentsButtonsSelectColorComponent,
1475
+ LibsUiComponentsInputsValidComponent,
1476
+ LibsUiComponentsEmojiComponent,
1477
+ LibsUiComponentsButtonsButtonComponent,
1478
+ LibsUiComponentsSpinnerComponent,
1479
+ LibsUiComponentsSkeletonComponent,
1480
+ ], template: "<div class=\"flex flex-col w-full h-full\">\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 #wrapperEditor\n [attr.heightAuto]=\"heightAuto()\"\n [attr.isToolbarPositionFixed]=\"isToolbarPositionFixed()\"\n class=\"libs-ui-quill-wrapper w-full h-full relative flex flex-col\"\n [class.libs-ui-border-error-general]=\"messageError() && showErrorBorder() && !onlyShowErrorBorderInContent()\"\n [class.rounded-[4px]]=\"messageError() && showErrorBorder() && !onlyShowErrorBorderInContent()\">\n <div\n #editor\n class=\"libs-ui-quill\"\n [attr.showError]=\"messageError() && showErrorBorder() && onlyShowErrorBorderInContent()\"\n [class.!hidden]=\"!display()\">\n <div\n #quillOption\n [class.!top-[-9999px]]=\"!isShowToolBar()\"\n [class.!left-[-9999px]]=\"!isShowToolBar()\"\n [style]=\"stylesIncludeToolbar()\"\n [class.pointer-events-none]=\"readonly() || loadingUploadImage()\"\n class=\"{{ isToolbarPositionFixed() ? 'fixed !z-10 w-[510px] shadow-[0px_4px_16px_0px_rgba(0,20,51,0.10)] rounded-[4px] bg-[#ffffff] libs-ui-quill-toolbar-animation' : '' }} {{ classIncludeToolbar() }} h-[44px] shrink-0\"\n [class.bg-[#f8f9fa]]=\"!readonly() && !loadingUploadImage()\"\n [class.libs-ui-readonly-background]=\"readonly() || loadingUploadImage()\">\n <ng-container *ngTemplateOutlet=\"toolbar\"></ng-container>\n </div>\n <div\n #quillEditor\n LibsUiComponentsInputsMentionDirective\n [timeDelayInit]=\"timeDelayInit() + 50\"\n [mentionConfig]=\"dataConfigMention()\"\n [class.libs-ui-readonly-background]=\"readonly() || loadingUploadImage()\"\n [class.pointer-events-none]=\"loadingUploadImage()\"\n (outToggle)=\"handleToggleMention($event)\"\n (outInsertMention)=\"handlerInsertMention($event)\"></div>\n <ng-container\n #qlEditorScroll\n LibsUiComponentsScrollOverlayDirective\n [ignoreInit]=\"!qlEditorElement()\"\n [elementScroll]=\"qlEditorElement()\" />\n </div>\n @if (template(); as template) {\n <div [class]=\"classIncludeTemplate()\">\n <ng-container *ngTemplateOutlet=\"template\"></ng-container>\n </div>\n }\n @if (loadingUploadImage()) {\n <libs_ui-components-spinner [size]=\"'medium'\" />\n }\n </div>\n @if (messageError() && showErrorLabel()) {\n <div class=\"flex items-center leading-normal mt-[8px]\">\n <span class=\"text-[#ff5454] libs-ui-font-h7r\">{{ messageError() | translate }}</span>\n </div>\n }\n</div>\n\n<ng-template #toolbar>\n <div\n class=\"toolbar h-full w-full shrink-0\"\n #toolbarItems>\n @if (displaySkeleton()) {\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]\">\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=\"itemTemplate; context: { option, $index, $last }\"></ng-container>\n </div>\n }\n <div class=\"relative\">\n @if (displayShowMoreToolbar()) {\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=\"itemTemplate; context: { option, $index, $last }\"></ng-container>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</ng-template>\n\n<ng-template\n #itemTemplate\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 [listMaxItemShow]=\"8\"\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 [listConfig]=\"listConfigHeader()\"\n [listHasButtonUnSelectOption]=\"false\"\n [listHiddenInputSearch]=\"true\"\n [listMaxItemShow]=\"8\"\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=\"ql-picker {{ option.classInclude || 'mx-[8px]' }}\">\n <libs_ui-components-inputs-valid\n [dataType]=\"'int'\"\n [(item)]=\"size\"\n [fieldNameBind]=\"'value'\"\n [valueUpDownNumber]=\"1\"\n [maxValueNumber]=\"72\"\n [minValueNumber]=\"8\"\n [readonly]=\"readonly() || loadingUploadImage()\"\n [classContainerInput]=\"'w-[72px] h-[32px]'\"\n (outValueChange)=\"handlerValueChange($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 (outColorChange)=\"setStyle('color', $event)\">\n <div class=\"libs-ui-icon-editor-color-text libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"></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 (outColorChange)=\"setStyle('background', $event)\">\n <div class=\"libs-ui-icon-editor-color-background libs-ui-buttons-select-color text-[#6a7383] text-[16px] hover:text-[var(--libs-ui-color-light-1)]\"></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\"></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\"></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\"></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\"></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 [listMaxItemShow]=\"8\"\n [zIndex]=\"1250\"\n (outSelectKey)=\"handleSelectAlign($event)\">\n @if (alignSelected(); as alignSelected) {\n <div class=\"flex items-center cursor-pointer\">\n <i [class]=\"iconAlignSelectedComputed()\"></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 === '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\"></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 === 'emoji') {\n <div [class]=\"option.classInclude || 'mx-[8px]'\">\n <libs_ui-components-emoji\n [zIndex]=\"zIndex()\"\n [isNgContent]=\"true\"\n (outEventEmoji)=\"handlerAddEmoji($event)\">\n <button class=\"ql-emoji\"></button>\n </libs_ui-components-emoji>\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 [mode]=\"'click'\"\n [ignoreHiddenPopoverContentWhenMouseLeave]=\"true\"\n [config]=\"{ template: viewLink, whiteTheme: true, ignoreArrow: true, zIndex: zIndex() }\"\n (outEvent)=\"handlerPopoverEvent($event)\"\n (outFunctionsControl)=\"handlerPopoverFunctionControl($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)=\"handlerEdit($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: ["@-webkit-keyframes animation-move{0%{transform:translateY(-12px)}to{transform:translateY(0)}}@keyframes animation-move{0%{transform:translateY(-12px)}to{transform:translateY(0)}}.libs-ui-quill{height:100%;width:100%;display:flex;flex-direction:column}.libs-ui-quill-toolbar-animation{animation:animation-move .4s ease}.libs-ui-quill-wrapper[heightAuto=true] .libs-ui-quill{height:auto}.libs-ui-quill-wrapper[heightAuto=true] .libs-ui-quill ::ng-deep .ql-container{height:auto}.libs-ui-quill-wrapper[heightAuto=true] .libs-ui-quill ::ng-deep .ql-container .ql-editor{position:relative!important}.libs-ui-quill-wrapper[isToolbarPositionFixed=true] .libs-ui-quill ::ng-deep .ql-container.ql-snow{border:0px!important}.libs-ui-quill-wrapper[isToolbarPositionFixed=false] .libs-ui-quill ::ng-deep .ql-container.ql-snow{border:solid 1px #e6e8ed!important;border-radius:0 0 4px 4px}.libs-ui-quill-wrapper[isToolbarPositionFixed=false] .libs-ui-quill ::ng-deep .ql-toolbar.ql-snow{border:solid 1px #e6e8ed!important;border-radius:4px 4px 0 0;border-bottom:unset!important}.libs-ui-quill-wrapper[isToolbarPositionFixed=false] .libs-ui-quill[showError=true] ::ng-deep .ql-container.ql-snow{border:solid 1px #ee2d41!important}:host ::ng-deep .toolbar,:host ::ng-deep .ql-formats{position:relative;width:100%}:host ::ng-deep .ql-toolbar.ql-snow .ql-formats{display:flex}:host ::ng-deep .ql-container.ql-snow{width:100%;height:100%;position:relative}:host ::ng-deep .ql-editor{font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;font-weight:400}:host ::ng-deep .ql-editor b{font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;font-weight:600}:host ::ng-deep .ql-toolbar.ql-snow{border:none;border-radius:8px;padding:0}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker-label{color:#071631;border:none!important;margin-top:5px}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker-label:hover{color:var(--libs-ui-color-light-1)!important}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker-label:hover .ql-stroke{stroke:var(--libs-ui-color-light-1)!important}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-item:hover{color:var(--libs-ui-color-light-1)!important}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-label.ql-active{color:#071631}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke{stroke:#071631}:host ::ng-deep .ql-editor{height:100%;width:100%;position:absolute;line-height:1.42!important}:host ::ng-deep .ql-editor:before{font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;color:#9ca2ad!important;font-size:12px}:host ::ng-deep .ql-editor img{height:auto;max-width:100%}:host ::ng-deep .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label{border:none!important}:host ::ng-deep .ql-snow .ql-picker.ql-bold{width:36px!important}:host ::ng-deep .ql-editor.ql-blank{font-size:11px!important;letter-spacing:.05px;font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;line-height:16px}:host ::ng-deep .ql-editor.ql-blank:before{font-style:normal!important;color:#9ca2ad!important}:host ::ng-deep .ql-snow.ql-toolbar .ql-picker-label :hover{color:red!important}:host ::ng-deep .ql-formats{margin-right:12px!important}:host ::ng-deep .ql-formats .ql-size.ql-picker,:host ::ng-deep .ql-formats .ql-header.ql-picker,:host ::ng-deep .ql-formats .ql-font.ql-picker{border:solid 1px #e6e8ed!important;border-radius:4px}:host ::ng-deep .ql-formats .ql-align.ql-picker .ql-picker-label{width:24px}:host ::ng-deep .ql-formats .ql-align.ql-picker .ql-picker-label:hover{color:red!important}:host ::ng-deep .ql-formats .ql-header.ql-picker .ql-picker-label:before{font-size:12px}:host ::ng-deep .ql-picker-item:before{font-size:12px}:host ::ng-deep .ql-picker-label:before{font-size:12px}:host ::ng-deep .ql-snow.ql-toolbar button,:host ::ng-deep .ql-snow .ql-toolbar button{background:none;border:none;cursor:pointer;display:flex;align-items:center;float:none;padding:0;width:inherit}:host ::ng-deep ul{padding-left:1rem!important}:host ::ng-deep li{padding-left:0!important}:host ::ng-deep .ql-tooltip{display:none!important}.ql-picker-options{max-height:130px;overflow-y:auto}.ql-snow .ql-picker.ql-size{border:1px solid #e6e7ea;box-shadow:#00000005 0 1px 3px,#1b1f2326 0 0 0 1px;border-radius:2px}.ql-snow .ql-picker.ql-font{border:1px solid #e6e7ea!important;margin-right:8px;border-radius:4px}.ql-snow .ql-picker.ql-font .ql-picker-options:before{font-size:12px}.ql-snow .ql-picker.ql-font .ql-picker-label:before{font-size:12px}.ql-font-Arial{font-family:Arial}.ql-font-sans-serif{font-family:\"sans-serif\"}.ql-font-Helvetica{font-family:Helvetica}.libs-ui-quill-mention{font-weight:500!important;color:#7239ea;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-family:var(--libs-ui-font-family-name),\"Arial, Helvetica, sans-serif\"!important;font-weight:600}.ql-snow .ql-picker.ql-expanded .ql-picker-options{overflow:hidden}.ql-formats{margin-right:0!important}.ql-formats .ql-header{position:relative!important}.ql-formats .ql-header .ql-picker-options{position:absolute!important;top:-151px!important}.ql-formats .ql-font{position:relative!important}.ql-formats .ql-font .ql-picker-options{position:absolute!important;top:-93px!important}.ql-formats .ql-color{position:relative!important}.ql-formats .ql-color .ql-picker-options{position:absolute!important;top:-108px}.ql-formats .ql-background{position:relative!important}.ql-formats .ql-background .ql-picker-options{position:absolute!important;top:-108px}.ql-formats .ql-align{position:relative!important}.ql-formats .ql-align .ql-picker-label{padding:3px 4px!important}.ql-formats .ql-align .ql-picker-options{position:absolute!important;top:-106px}.ql-snow .ql-picker{font-size:12px!important;height:32px;float:none;width:auto}\n"] }]
1481
+ }], ctorParameters: () => [] });
1482
+
1483
+ //region Function Table
1484
+ const createTableSelector = (quill, anchorEl) => {
1485
+ // Đóng popup cũ nếu còn tồn tại
1486
+ document.querySelectorAll('.manual-table-creator').forEach((el) => el.parentElement?.removeChild(el));
1487
+ const popup = document.createElement('div');
1488
+ popup.className = 'manual-table-creator';
1489
+ // Inline style để đảm bảo hiển thị ngay cả khi CSS component không áp dụng
1490
+ popup.style.position = 'absolute';
1491
+ popup.style.background = '#fff';
1492
+ popup.style.border = '1px solid #ddd';
1493
+ popup.style.borderRadius = '6px';
1494
+ popup.style.boxShadow = '0 6px 16px rgba(0,0,0,0.12)';
1495
+ popup.style.padding = '12px';
1496
+ popup.style.zIndex = '10000';
1497
+ popup.style.minWidth = '200px';
1498
+ setStylesElement(popup, {
1499
+ position: 'absolute',
1500
+ background: '#fff',
1501
+ border: '1px solid #ddd',
1502
+ borderRadius: '6px',
1503
+ boxShadow: '0 6px 16px rgba(0,0,0,0.12)',
1504
+ padding: '12px',
1505
+ zIndex: '10000',
1506
+ minWidth: '200px',
1507
+ });
1508
+ const info = document.createElement('div');
1509
+ info.textContent = 'Chọn kích thước (hàng × cột)';
1510
+ setStylesElement(info, {
1511
+ fontSize: '12px',
1512
+ color: '#333',
1513
+ marginBottom: '10px',
1514
+ textAlign: 'center',
1515
+ fontWeight: '600',
1516
+ });
1517
+ popup.appendChild(info);
1518
+ const grid = document.createElement('div');
1519
+ setStylesElement(grid, {
1520
+ display: 'grid',
1521
+ gridTemplateColumns: 'repeat(8, 18px)',
1522
+ gap: '4px',
1523
+ });
1524
+ const max = 8;
1525
+ for (let r = 1; r <= max; r++) {
1526
+ for (let c = 1; c <= max; c++) {
1527
+ const cell = document.createElement('div');
1528
+ cell.style.width = '18px';
1529
+ cell.style.height = '18px';
1530
+ cell.style.border = '1px solid #e5e7eb';
1531
+ cell.style.background = '#fff';
1532
+ cell.style.cursor = 'pointer';
1533
+ cell.addEventListener('mouseenter', () => {
1534
+ info.textContent = `Chọn kích thước: ${r} × ${c}`;
1535
+ Array.from(grid.children).forEach((el, idx) => {
1536
+ const rr = Math.floor(idx / max) + 1;
1537
+ const cc = (idx % max) + 1;
1538
+ el.style.background = rr <= r && cc <= c ? '#dbeafe' : '#fff';
1539
+ el.style.borderColor = rr <= r && cc <= c ? '#93c5fd' : '#e5e7eb';
1540
+ });
1541
+ });
1542
+ cell.addEventListener('click', () => {
1543
+ try {
1544
+ quill.focus();
1545
+ const tableModule = quill.getModule('table');
1546
+ if (tableModule && typeof tableModule.insertTable === 'function') {
1547
+ tableModule.insertTable(r, c);
1548
+ }
1549
+ }
1550
+ finally {
1551
+ if (document.body.contains(popup)) {
1552
+ document.body.removeChild(popup);
1553
+ }
1554
+ document.removeEventListener('click', onOutsideClick, true);
1555
+ }
1556
+ });
1557
+ grid.appendChild(cell);
1558
+ }
1559
+ }
1560
+ popup.appendChild(grid);
1561
+ const placePopup = () => {
1562
+ const rect = anchorEl?.getBoundingClientRect();
1563
+ const top = rect ? rect.bottom + window.scrollY + 6 : 100;
1564
+ const left = rect ? rect.left + window.scrollX : 100;
1565
+ popup.style.top = `${top}px`;
1566
+ popup.style.left = `${left}px`;
1567
+ };
1568
+ const onOutsideClick = (ev) => {
1569
+ if (!popup.contains(ev.target)) {
1570
+ if (document.body.contains(popup)) {
1571
+ document.body.removeChild(popup);
1572
+ }
1573
+ document.removeEventListener('click', onOutsideClick, true);
1574
+ }
1575
+ };
1576
+ document.body.appendChild(popup);
1577
+ placePopup();
1578
+ setTimeout(() => document.addEventListener('click', onOutsideClick, true));
1579
+ };
1580
+ const createContextMenuOptionTable = (quill) => {
1581
+ let lastContextIndex = null;
1582
+ const closeAnyMenu = () => {
1583
+ document.querySelectorAll('.manual-table-menu').forEach((el) => el.parentElement?.removeChild(el));
1584
+ };
1585
+ const runWithSelection = (fn) => {
1586
+ try {
1587
+ quill.focus();
1588
+ if (typeof lastContextIndex === 'number') {
1589
+ quill.setSelection(lastContextIndex, 0, 'silent');
1590
+ }
1591
+ }
1592
+ catch {
1593
+ /* no-op: giữ yên nếu không thể set selection */
1594
+ }
1595
+ fn();
1596
+ };
1597
+ const createMenuItem = (label, onClick) => {
1598
+ const item = document.createElement('div');
1599
+ item.textContent = label;
1600
+ item.style.padding = '6px 12px';
1601
+ item.style.cursor = 'pointer';
1602
+ item.addEventListener('click', () => {
1603
+ runWithSelection(onClick);
1604
+ closeAnyMenu();
1605
+ });
1606
+ item.addEventListener('mouseenter', () => {
1607
+ item.style.background = '#f5f5f5';
1608
+ });
1609
+ item.addEventListener('mouseleave', () => {
1610
+ item.style.background = 'transparent';
1611
+ });
1612
+ return item;
1613
+ };
1614
+ const openContextMenu = (pageX, pageY) => {
1615
+ closeAnyMenu();
1616
+ const menu = document.createElement('div');
1617
+ menu.className = 'manual-table-menu';
1618
+ menu.style.position = 'absolute';
1619
+ menu.style.top = `${pageY}px`;
1620
+ menu.style.left = `${pageX}px`;
1621
+ menu.style.background = '#fff';
1622
+ menu.style.border = '1px solid #ccc';
1623
+ menu.style.borderRadius = '4px';
1624
+ menu.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
1625
+ menu.style.zIndex = '10001';
1626
+ menu.style.padding = '4px';
1627
+ menu.style.minWidth = '180px';
1628
+ const tableModule = quill.getModule('table');
1629
+ const safeCall = (method, ...args) => {
1630
+ if (tableModule && typeof tableModule[method] === 'function') {
1631
+ tableModule[method](...args);
1632
+ }
1633
+ };
1634
+ menu.appendChild(createMenuItem('Thêm hàng bên trên', () => safeCall('insertRowAbove')));
1635
+ menu.appendChild(createMenuItem('Thêm hàng bên dưới', () => safeCall('insertRowBelow')));
1636
+ menu.appendChild(createMenuItem('Thêm cột bên trái', () => safeCall('insertColumnLeft')));
1637
+ menu.appendChild(createMenuItem('Thêm cột bên phải', () => safeCall('insertColumnRight')));
1638
+ const divider = document.createElement('div');
1639
+ divider.style.height = '1px';
1640
+ divider.style.background = '#eee';
1641
+ divider.style.margin = '4px 0';
1642
+ menu.appendChild(divider);
1643
+ menu.appendChild(createMenuItem('Xóa hàng', () => safeCall('deleteRow')));
1644
+ menu.appendChild(createMenuItem('Xóa cột', () => safeCall('deleteColumn')));
1645
+ menu.appendChild(createMenuItem('Xóa bảng', () => safeCall('deleteTable')));
1646
+ document.body.appendChild(menu);
1647
+ const onOutside = (ev) => {
1648
+ if (!menu.contains(ev.target)) {
1649
+ closeAnyMenu();
1650
+ document.removeEventListener('click', onOutside, true);
1651
+ }
1652
+ };
1653
+ setTimeout(() => document.addEventListener('click', onOutside, true));
1654
+ const onScroll = () => {
1655
+ closeAnyMenu();
1656
+ window.removeEventListener('scroll', onScroll, true);
1657
+ window.removeEventListener('resize', onScroll, true);
1658
+ };
1659
+ window.addEventListener('scroll', onScroll, true);
1660
+ window.addEventListener('resize', onScroll, true);
1661
+ };
1662
+ fromEvent(quill.root, 'contextmenu').subscribe((ev) => {
1663
+ const target = ev.target;
1664
+ const isCell = !!target.closest('td,th,table,.ql-table');
1665
+ if (!isCell)
1666
+ return;
1667
+ ev.preventDefault();
1668
+ try {
1669
+ // Ưu tiên lấy index từ blot tương ứng node
1670
+ const QuillAny = Quill2x;
1671
+ const blot = QuillAny?.find?.(target);
1672
+ if (blot && typeof blot.offset === 'function') {
1673
+ lastContextIndex = blot.offset(quill.scroll);
1674
+ }
1675
+ else {
1676
+ // Fallback: đặt selection hiện tại nếu không xác định được
1677
+ const sel = quill.getSelection(true);
1678
+ lastContextIndex = sel ? sel.index : 0;
1679
+ }
1680
+ }
1681
+ catch {
1682
+ const sel = quill.getSelection(true);
1683
+ lastContextIndex = sel ? sel.index : 0;
1684
+ }
1685
+ quill.focus();
1686
+ openContextMenu(ev.pageX, ev.pageY);
1687
+ });
1688
+ };
1689
+
1690
+ class LibsUiComponentsInputsQuill2xComponent {
1691
+ quill;
1692
+ blotsRegister = input();
1693
+ outContextMenu = output();
1694
+ outFunctionsControl = output();
1695
+ htmlContainer = viewChild.required('htmlContainer');
1696
+ quillBodyContainer = viewChild.required('snowContainer');
1697
+ destroyRef = inject(DestroyRef);
1698
+ ngOnInit() {
1699
+ this.blotsRegister()?.forEach((item) => {
1700
+ Quill2x.register(item.component, true);
1701
+ });
1702
+ const toolbarOptions = [
1703
+ ['bold', 'italic', 'underline', 'strike'],
1704
+ ['blockquote', 'code-block', 'code'],
1705
+ ['link', 'image', 'video', 'formula'],
1706
+ [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
1707
+ [{ script: 'sub' }, { script: 'super' }],
1708
+ [{ indent: '-1' }, { indent: '+1' }],
1709
+ [{ direction: 'rtl' }],
1710
+ [{ size: ['small', false, 'large', 'huge'] }],
1711
+ [{ header: [1, 2, 3, 4, 5, 6, false] }],
1712
+ [{ color: [] }, { background: [] }],
1713
+ [{ font: [] }],
1714
+ [{ align: [] }],
1715
+ ['table'],
1716
+ ];
1717
+ const options = {
1718
+ theme: 'snow',
1719
+ modules: {
1720
+ toolbar: {
1721
+ container: toolbarOptions,
1722
+ handlers: {
1723
+ table: () => {
1724
+ const button = this.htmlContainer().nativeElement.querySelector?.('button.ql-table');
1725
+ console.log('button', button, this.htmlContainer().nativeElement);
1726
+ createTableSelector(this.quill, button);
1727
+ },
1728
+ },
1729
+ },
1730
+ table: true,
1731
+ keyboard: {
1732
+ bindings: {
1733
+ backspace: {
1734
+ key: UtilsKeyCodeConstant.BACKSPACE,
1735
+ handler: (range) => this.noPreventEmbedDeletion(range),
1736
+ },
1737
+ delete: {
1738
+ key: UtilsKeyCodeConstant.DELETE,
1739
+ handler: (range) => this.noPreventEmbedDeletion(range),
1740
+ },
1741
+ },
1742
+ },
1743
+ },
1744
+ };
1745
+ this.quill = new Quill2x(this.quillBodyContainer().nativeElement, options);
1746
+ fromEvent(this.quillBodyContainer()?.nativeElement, 'contextmenu')
1747
+ .pipe(takeUntilDestroyed(this.destroyRef))
1748
+ .subscribe((e) => this.outContextMenu.emit(e));
1749
+ // Tự động tải nội dung đã lưu (nếu có)
1750
+ try {
1751
+ const saved = localStorage.getItem('quill2x_saved_html');
1752
+ if (saved) {
1753
+ this.quill.clipboard.dangerouslyPasteHTML(saved, 'silent');
1754
+ }
1755
+ }
1756
+ catch {
1757
+ /* no-op: bỏ qua nếu không thể tải từ localStorage */
1758
+ }
1759
+ // Gán handler lưu/tải/xóa nội dung
1760
+ const saveBtn = document.getElementById('save-content');
1761
+ saveBtn?.addEventListener('click', () => {
1762
+ const html = (this.quill?.root).innerHTML;
1763
+ try {
1764
+ localStorage.setItem('quill2x_saved_html', html);
1765
+ this.setStyleForContent();
1766
+ // eslint-disable-next-line no-console
1767
+ console.log('Đã lưu nội dung');
1768
+ }
1769
+ catch {
1770
+ /* no-op: bỏ qua nếu lưu thất bại */
1771
+ }
1772
+ });
1773
+ this.outFunctionsControl.emit(this.FunctionsControl);
1774
+ this.setStyleForContent();
1775
+ createContextMenuOptionTable(this.quill);
1776
+ }
1777
+ get FunctionsControl() {
1778
+ return {
1779
+ quill: () => this.quill,
1780
+ };
1781
+ }
1782
+ noPreventEmbedDeletion(range) {
1783
+ if (!this.quill)
1784
+ return false;
1785
+ const delta = this.quill?.getContents(range.index, 1);
1786
+ if (delta?.ops.some((option) => this.blotsRegister()?.find((item) => get(option.insert, item.component['blotName']) && item.ignoreDelete))) {
1787
+ return false; // Ngăn không cho Quill xử lý xóa
1788
+ }
1789
+ return true;
1790
+ }
1791
+ setStyleForContent() {
1792
+ const styleArray = [
1793
+ { selector: '.ql-font-Arial', style: 'font-family: Arial' },
1794
+ { selector: '.ql-font-sans-serif', style: 'font-family: sans-serif' },
1795
+ { selector: '.ql-font-serif', style: 'font-family: serif' },
1796
+ { selector: '.ql-font-monospace', style: 'font-family: monospace' },
1797
+ { selector: '.ql-font-Helvetica', style: 'font-family: Helvetica' },
1798
+ { selector: '.libs-ui-quill-format-image', style: 'max-width: 100%; height: auto', overrideStyle: true },
1799
+ { selector: '.libs-ui-quill-mention', style: 'font-weight: 600;-moz-osx-font-smoothing: grayscale;-webkit-font-smoothing: antialiased; color: #7239EA', overrideStyle: true },
1800
+ { selector: 'blockquote', style: 'border-left: 4px solid #ccc; margin-bottom: 5px; margin-top: 5px; padding-left: 16px', overrideStyle: true },
1801
+ ];
1802
+ this.blotsRegister()?.forEach((item) => {
1803
+ styleArray.push({ selector: `.${item.className}`, style: item.style, overrideStyle: true });
1804
+ });
1805
+ styleArray.forEach((item) => {
1806
+ const elements = this.quill?.root.querySelectorAll(item.selector);
1807
+ elements?.forEach((element) => {
1808
+ if (item.overrideStyle) {
1809
+ element.setAttribute('style', item.style);
1810
+ return;
1811
+ }
1812
+ // Fixbug issue: https://admin-cv.mobio.vn/issues/49092
1813
+ let styleExist = element.getAttribute('style');
1814
+ if (styleExist) {
1815
+ ['font-family: Arial', 'font-family: sans-serif', 'font-family: serif', 'font-family: monospace', 'font-family: Helvetica'].forEach((font) => {
1816
+ styleExist = (styleExist?.includes(`${font};`) ? styleExist?.replace(`${font};`, '') : styleExist?.replace(font, ''));
1817
+ });
1818
+ }
1819
+ element.setAttribute('style', styleExist ? `${styleExist}${styleExist[styleExist.length - 1] === ';' ? '' : ';'}${item.style};` : item.style);
1820
+ });
1821
+ });
1822
+ }
1823
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuill2xComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1824
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "18.2.14", type: LibsUiComponentsInputsQuill2xComponent, isStandalone: true, selector: "libs_ui-components-inputs-quill2x", inputs: { blotsRegister: { classPropertyName: "blotsRegister", publicName: "blotsRegister", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { 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 }], ngImport: i0, template: "<div\n #htmlContainer\n class=\"container\">\n <div class=\"panel\">\n <div #snowContainer></div>\n <div class=\"action-buttons\">\n <button\n id=\"save-content\"\n class=\"action-btn save-btn\">\n \uD83D\uDCBE L\u01B0u n\u1ED9i dung\n </button>\n </div>\n </div>\n</div>\n", styles: ["body{padding:25px}.container{display:flex;flex-direction:column}.action-buttons{display:flex;gap:10px;margin-top:15px;padding:10px;background-color:#f8f9fa;border-radius:4px;border:1px solid #e9ecef}.action-btn{padding:8px 16px;border:none;border-radius:4px;cursor:pointer;font-size:14px;font-weight:500;transition:all .2s ease;display:flex;align-items:center;gap:5px}.action-btn:hover{transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.action-btn:active{transform:translateY(0)}.action-btn.save-btn{background-color:#28a745;color:#fff}.action-btn.save-btn:hover{background-color:#218838}.action-btn.clear-btn{background-color:#dc3545;color:#fff}.action-btn.clear-btn:hover{background-color:#c82333}.action-btn.load-btn{background-color:#007bff;color:#fff}.action-btn.load-btn:hover{background-color:#0056b3}.panel{margin:10px 0}.ql-container{font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;border:1px solid #ccc;border-radius:4px}.ql-toolbar{border:1px solid #ccc;border-bottom:none;border-radius:4px 4px 0 0;padding:8px;background-color:#f8f9fa}.ql-toolbar .ql-formats{margin-right:15px}.ql-toolbar .ql-formats:last-child{margin-right:0}.ql-toolbar button{border:none;background:none;cursor:pointer;padding:5px;margin:0 1px;border-radius:3px;color:#444}.ql-toolbar button:hover{background-color:#e9ecef;color:#000}.ql-toolbar button.ql-active{background-color:#007bff;color:#fff}.ql-toolbar .ql-picker{color:#444}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up{width:28px}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up .ql-picker-label{padding:2px 4px}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up .ql-picker-label svg{position:static;margin-top:0;width:18px;height:18px}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up .ql-picker-options .ql-picker-item:hover{background-color:#f0f0f0}.ql-toolbar .ql-table-up svg{width:18px;height:18px}.ql-toolbar .custom-table-icon{display:inline-block;width:18px;height:18px;vertical-align:middle;color:currentColor}.ql-editor{border:1px solid #ccc;border-top:none;border-radius:0 0 4px 4px;padding:12px;min-height:300px;line-height:1.42}.ql-editor:focus{outline:none;box-shadow:0 0 0 2px #007bff40}.ql-editor p{margin-bottom:10px}.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6{margin-top:10px;margin-bottom:10px;font-weight:700}.ql-editor blockquote{border-left:4px solid #ccc;margin:16px 0;padding-left:16px;color:#666}.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor .ql-table,.ql-editor table{border-collapse:separate!important;border-spacing:0!important;table-layout:fixed!important;width:100%!important;margin:10px 0!important}.ql-editor .ql-table colgroup col,.ql-editor table colgroup col{border:none!important}.ql-editor .ql-table td,.ql-editor .ql-table th,.ql-editor table td,.ql-editor table th{border:1px solid #ccc!important;padding:8px!important;position:relative!important;vertical-align:top!important;min-width:50px!important;min-height:30px!important;word-wrap:break-word!important;box-sizing:border-box!important}.ql-editor .ql-table td:focus,.ql-editor .ql-table th:focus,.ql-editor table td:focus,.ql-editor table th:focus{outline:2px solid #007bff!important;outline-offset:-2px!important}.ql-editor .ql-table th,.ql-editor table th{background-color:#f5f5f5!important;font-weight:700!important;text-align:center!important}.ql-editor .ql-table tr:hover td,.ql-editor .ql-table tr:hover th,.ql-editor table tr:hover td,.ql-editor table tr:hover th{background-color:#007bff0d!important}.ql-editor .table-up-wrapper{position:relative!important;margin:10px 0!important}.ql-editor .table-up-wrapper .ql-table,.ql-editor .table-up-wrapper table{border-collapse:separate!important;border-spacing:0!important;table-layout:fixed!important;width:100%!important;margin:0!important}.ql-editor .table-up-cell{position:relative!important}.ql-editor .table-up-cell .table-up-cell-inner{min-height:1em!important;outline:none!important;width:100%!important;height:100%!important}.ql-editor .table-up-cell .table-up-cell-inner:focus{outline:none!important}.ql-editor .table-up-selected{background-color:#007bff1a!important}.ql-editor .table-up-selected:after{content:\"\"!important;position:absolute!important;inset:0!important;background-color:#007bff1a!important;pointer-events:none!important}.ql-editor .table-up-resize-line{position:absolute!important;background-color:#007bff!important;z-index:100!important}.ql-editor .table-up-resize-line.table-up-resize-line-row{height:2px!important;cursor:row-resize!important;left:0!important;right:0!important}.ql-editor .table-up-resize-line.table-up-resize-line-col{width:2px!important;cursor:col-resize!important;top:0!important;bottom:0!important}.ql-editor .table-up-resize-scale{position:absolute!important;width:8px!important;height:8px!important;background-color:#007bff!important;border:1px solid #fff!important;z-index:101!important;cursor:nw-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-tr{top:-4px!important;right:-4px!important;cursor:ne-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-br{bottom:-4px!important;right:-4px!important;cursor:se-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-bl{bottom:-4px!important;left:-4px!important;cursor:sw-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-tl{top:-4px!important;left:-4px!important;cursor:nw-resize!important}.ql-editor .ql-editor table td.table-up-cell-selected,.ql-editor .ql-editor table th.table-up-cell-selected,.ql-editor .ql-editor .table-up-cell-selected,.ql-editor table td.table-up-cell-selected,.ql-editor table th.table-up-cell-selected,.ql-editor td.table-up-cell-selected,.ql-editor th.table-up-cell-selected{background-color:#007bff66!important;border:3px solid #007bff!important;box-shadow:0 0 0 1px #007bff99!important;position:relative!important}.ql-editor .table-up-merged-cell{background-color:#007bff0d!important;border:2px solid #007bff!important;position:relative!important}.ql-editor .table-up-merged-cell:after{content:\"\"!important;position:absolute!important;top:2px!important;right:2px!important;width:0!important;height:0!important;border-left:8px solid transparent!important;border-right:8px solid transparent!important;border-top:8px solid #007bff!important}.ql-editor .ql-editor table td,.ql-editor .ql-editor table th{transition:all .2s ease!important}.ql-editor .ql-editor table td:hover,.ql-editor .ql-editor table th:hover{background-color:#007bff1a!important;cursor:pointer!important}.ql-editor .table-selection-guide{position:fixed!important;top:10px!important;right:10px!important;background:#007bffe6!important;color:#fff!important;padding:8px 12px!important;border-radius:4px!important;font-size:12px!important;font-weight:700!important;z-index:1000!important;pointer-events:none!important}.ql-editor .table-selection-guide.hidden{display:none!important}.ql-editor .table-up-resize-indicator{position:absolute!important;background-color:#007bff4d!important;pointer-events:none!important;z-index:99!important}.ql-editor .table-up-resize-indicator.table-up-resize-indicator-col{width:2px!important;top:0!important;bottom:0!important}.ql-editor .table-up-resize-indicator.table-up-resize-indicator-row{height:2px!important;left:0!important;right:0!important}.ql-editor .table-up-cell:after{content:\"\"!important;position:absolute!important;right:-1px!important;top:0!important;bottom:0!important;width:4px!important;cursor:col-resize!important;background:transparent!important;z-index:10!important}.ql-editor .table-up-cell:hover:after{background:#007bff4d!important}.ql-editor .manual-resize-handle{position:absolute!important;right:-2px!important;top:0!important;bottom:0!important;width:4px!important;cursor:col-resize!important;background:transparent!important;z-index:15!important;transition:background-color .2s ease!important}.ql-editor .manual-resize-handle:hover{background:#007bff4d!important}.ql-editor .manual-resize-handle:active{background:#007bff80!important}.ql-editor .ql-editor .ql-table td,.ql-editor .ql-editor .ql-table th,.ql-editor .ql-editor table td,.ql-editor .ql-editor table th{position:relative!important}.ql-editor .table-up-menu{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 2px 8px #00000026!important;z-index:1000!important;padding:4px!important}.ql-editor .table-up-menu .table-up-menu-item{padding:8px 12px!important;cursor:pointer!important;border-radius:3px!important;font-size:13px!important}.ql-editor .table-up-menu .table-up-menu-item:hover{background-color:#f5f5f5!important}.ql-editor .table-up-menu .table-up-menu-item.table-up-menu-item-break{height:1px!important;background-color:#e9ecef!important;margin:4px 0!important;padding:0!important}button{background-color:#007bff;color:#fff;border:none;padding:8px 12px;margin:4px;border-radius:4px;cursor:pointer;font-size:14px}button:hover{background-color:#0056b3}button:active{background-color:#004085}button:disabled{background-color:#6c757d;cursor:not-allowed}.table-up-creator,.ql-table-up-creator,[class*=table-creator],[class*=creator]{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 2px 8px #00000026!important;z-index:9999!important;padding:12px!important;display:block!important;visibility:visible!important;opacity:1!important}.table-up-creator .table-up-creator-grid,.ql-table-up-creator .table-up-creator-grid,[class*=table-creator] .table-up-creator-grid,[class*=creator] .table-up-creator-grid{display:grid!important;gap:2px!important;margin-bottom:10px!important}.table-up-creator .table-up-creator-grid .table-up-creator-cell,.ql-table-up-creator .table-up-creator-grid .table-up-creator-cell,[class*=table-creator] .table-up-creator-grid .table-up-creator-cell,[class*=creator] .table-up-creator-grid .table-up-creator-cell{width:16px!important;height:16px!important;border:1px solid #ddd!important;cursor:pointer!important}.table-up-creator .table-up-creator-grid .table-up-creator-cell:hover,.table-up-creator .table-up-creator-grid .table-up-creator-cell.active,.ql-table-up-creator .table-up-creator-grid .table-up-creator-cell:hover,.ql-table-up-creator .table-up-creator-grid .table-up-creator-cell.active,[class*=table-creator] .table-up-creator-grid .table-up-creator-cell:hover,[class*=table-creator] .table-up-creator-grid .table-up-creator-cell.active,[class*=creator] .table-up-creator-grid .table-up-creator-cell:hover,[class*=creator] .table-up-creator-grid .table-up-creator-cell.active{background-color:#007bff!important;border-color:#007bff!important}.table-up-creator .table-up-creator-info,.ql-table-up-creator .table-up-creator-info,[class*=table-creator] .table-up-creator-info,[class*=creator] .table-up-creator-info{text-align:center!important;font-size:12px!important;color:#666!important;margin-bottom:10px!important}.table-up-creator .table-up-creator-buttons,.ql-table-up-creator .table-up-creator-buttons,[class*=table-creator] .table-up-creator-buttons,[class*=creator] .table-up-creator-buttons{display:flex!important;gap:8px!important}.table-up-creator .table-up-creator-buttons button,.ql-table-up-creator .table-up-creator-buttons button,[class*=table-creator] .table-up-creator-buttons button,[class*=creator] .table-up-creator-buttons button{flex:1!important;padding:6px 12px!important;font-size:12px!important}[class*=creator]{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;z-index:9999!important;padding:10px!important;min-width:200px!important;min-height:100px!important}.ql-picker-options{z-index:9999!important}[class*=creator],[class*=table-creator]{outline:2px solid red!important;background:#ff0!important}.manual-table-creator{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 2px 8px #00000026!important;z-index:10000!important;padding:12px!important;display:block!important;visibility:visible!important;opacity:1!important;min-width:200px!important}.manual-table-creator div:first-child{text-align:center!important;font-size:12px!important;color:#333!important;margin-bottom:10px!important;font-weight:700!important;background:#f8f9fa!important;padding:4px 8px!important;border-radius:3px!important}.manual-table-creator div:last-child{display:grid!important;grid-template-columns:repeat(8,20px)!important;gap:2px!important;-webkit-user-select:none!important;user-select:none!important}.manual-table-creator div:last-child div{width:18px!important;height:18px!important;border:1px solid #ddd!important;cursor:pointer!important;background:#fff!important;transition:all .1s ease!important}.manual-table-creator div:last-child div:hover{background-color:#007bff!important;border-color:#0056b3!important;transform:scale(1.05)!important}.manual-table-menu{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 4px 12px #0003!important;z-index:10001!important;padding:4px!important;min-width:150px!important;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif!important}.manual-table-menu div{padding:6px 12px!important;cursor:pointer!important;border-radius:3px!important;font-size:13px!important;color:#333!important;transition:background-color .1s ease!important}.manual-table-menu div:hover{background-color:#f5f5f5!important}.manual-table-menu div:active{background-color:#e9ecef!important}.manual-table-menu div[style*=\"height: 1px\"]{height:1px!important;background:#eee!important;margin:4px 0!important;padding:0!important;cursor:default!important}.manual-table-menu div[style*=\"height: 1px\"]:hover{background:#eee!important}.manual-table-creator *,.manual-table-menu *{pointer-events:auto!important;-webkit-user-select:none!important;user-select:none!important}.status-message{position:fixed!important;top:20px!important;right:20px!important;padding:12px 20px!important;border-radius:4px!important;box-shadow:0 2px 8px #0003!important;z-index:10001!important;font-size:14px!important;font-weight:500!important;color:#fff!important}@keyframes slideIn{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes slideOut{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}.table-up-color-picker .table-up-color-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:2px;margin:8px 0}.table-up-color-picker .table-up-color-grid .table-up-color-item{width:20px;height:20px;cursor:pointer;border:1px solid #ddd;border-radius:2px}.table-up-color-picker .table-up-color-grid .table-up-color-item:hover{transform:scale(1.1);border-color:#007bff}@keyframes selection-glow{0%{opacity:.3}to{opacity:.7}}.ql-editor table td,.ql-editor table th{transition:all .2s ease!important;cursor:pointer!important}.ql-editor table td:hover,.ql-editor table th:hover{background-color:#007bff1a!important;transform:scale(1.02)!important;box-shadow:0 2px 4px #007bff33!important}.selection-area-indicator{position:absolute!important;border:2px dashed #007bff!important;background-color:#007bff1a!important;pointer-events:none!important;z-index:1000!important;border-radius:4px!important}.manual-table-creator{position:absolute;top:0;left:0;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 16px #0000001f;padding:12px;z-index:10000;min-width:200px}.manual-table-creator>div:first-child{font-size:12px;color:#333;margin-bottom:10px;text-align:center;font-weight:600}.manual-table-creator>div:last-child{display:grid;grid-template-columns:repeat(8,18px);gap:4px}.manual-table-creator>div:last-child>div{width:18px;height:18px;border:1px solid #e5e7eb;background:#fff;cursor:pointer;transition:background-color .12s ease}.manual-table-creator>div:last-child>div:hover{background-color:#dbeafe;border-color:#93c5fd}\n"], dependencies: [{ kind: "ngmodule", type: TranslateModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1825
+ }
1826
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LibsUiComponentsInputsQuill2xComponent, decorators: [{
1827
+ type: Component,
1828
+ args: [{ selector: 'libs_ui-components-inputs-quill2x', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [TranslateModule], template: "<div\n #htmlContainer\n class=\"container\">\n <div class=\"panel\">\n <div #snowContainer></div>\n <div class=\"action-buttons\">\n <button\n id=\"save-content\"\n class=\"action-btn save-btn\">\n \uD83D\uDCBE L\u01B0u n\u1ED9i dung\n </button>\n </div>\n </div>\n</div>\n", styles: ["body{padding:25px}.container{display:flex;flex-direction:column}.action-buttons{display:flex;gap:10px;margin-top:15px;padding:10px;background-color:#f8f9fa;border-radius:4px;border:1px solid #e9ecef}.action-btn{padding:8px 16px;border:none;border-radius:4px;cursor:pointer;font-size:14px;font-weight:500;transition:all .2s ease;display:flex;align-items:center;gap:5px}.action-btn:hover{transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.action-btn:active{transform:translateY(0)}.action-btn.save-btn{background-color:#28a745;color:#fff}.action-btn.save-btn:hover{background-color:#218838}.action-btn.clear-btn{background-color:#dc3545;color:#fff}.action-btn.clear-btn:hover{background-color:#c82333}.action-btn.load-btn{background-color:#007bff;color:#fff}.action-btn.load-btn:hover{background-color:#0056b3}.panel{margin:10px 0}.ql-container{font-size:14px;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;border:1px solid #ccc;border-radius:4px}.ql-toolbar{border:1px solid #ccc;border-bottom:none;border-radius:4px 4px 0 0;padding:8px;background-color:#f8f9fa}.ql-toolbar .ql-formats{margin-right:15px}.ql-toolbar .ql-formats:last-child{margin-right:0}.ql-toolbar button{border:none;background:none;cursor:pointer;padding:5px;margin:0 1px;border-radius:3px;color:#444}.ql-toolbar button:hover{background-color:#e9ecef;color:#000}.ql-toolbar button.ql-active{background-color:#007bff;color:#fff}.ql-toolbar .ql-picker{color:#444}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up{width:28px}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up .ql-picker-label{padding:2px 4px}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up .ql-picker-label svg{position:static;margin-top:0;width:18px;height:18px}.ql-toolbar .ql-picker:not(.ql-color-picker):not(.ql-icon-picker).ql-table-up .ql-picker-options .ql-picker-item:hover{background-color:#f0f0f0}.ql-toolbar .ql-table-up svg{width:18px;height:18px}.ql-toolbar .custom-table-icon{display:inline-block;width:18px;height:18px;vertical-align:middle;color:currentColor}.ql-editor{border:1px solid #ccc;border-top:none;border-radius:0 0 4px 4px;padding:12px;min-height:300px;line-height:1.42}.ql-editor:focus{outline:none;box-shadow:0 0 0 2px #007bff40}.ql-editor p{margin-bottom:10px}.ql-editor h1,.ql-editor h2,.ql-editor h3,.ql-editor h4,.ql-editor h5,.ql-editor h6{margin-top:10px;margin-bottom:10px;font-weight:700}.ql-editor blockquote{border-left:4px solid #ccc;margin:16px 0;padding-left:16px;color:#666}.ql-editor ul,.ql-editor ol{padding-left:1.5em}.ql-editor .ql-table,.ql-editor table{border-collapse:separate!important;border-spacing:0!important;table-layout:fixed!important;width:100%!important;margin:10px 0!important}.ql-editor .ql-table colgroup col,.ql-editor table colgroup col{border:none!important}.ql-editor .ql-table td,.ql-editor .ql-table th,.ql-editor table td,.ql-editor table th{border:1px solid #ccc!important;padding:8px!important;position:relative!important;vertical-align:top!important;min-width:50px!important;min-height:30px!important;word-wrap:break-word!important;box-sizing:border-box!important}.ql-editor .ql-table td:focus,.ql-editor .ql-table th:focus,.ql-editor table td:focus,.ql-editor table th:focus{outline:2px solid #007bff!important;outline-offset:-2px!important}.ql-editor .ql-table th,.ql-editor table th{background-color:#f5f5f5!important;font-weight:700!important;text-align:center!important}.ql-editor .ql-table tr:hover td,.ql-editor .ql-table tr:hover th,.ql-editor table tr:hover td,.ql-editor table tr:hover th{background-color:#007bff0d!important}.ql-editor .table-up-wrapper{position:relative!important;margin:10px 0!important}.ql-editor .table-up-wrapper .ql-table,.ql-editor .table-up-wrapper table{border-collapse:separate!important;border-spacing:0!important;table-layout:fixed!important;width:100%!important;margin:0!important}.ql-editor .table-up-cell{position:relative!important}.ql-editor .table-up-cell .table-up-cell-inner{min-height:1em!important;outline:none!important;width:100%!important;height:100%!important}.ql-editor .table-up-cell .table-up-cell-inner:focus{outline:none!important}.ql-editor .table-up-selected{background-color:#007bff1a!important}.ql-editor .table-up-selected:after{content:\"\"!important;position:absolute!important;inset:0!important;background-color:#007bff1a!important;pointer-events:none!important}.ql-editor .table-up-resize-line{position:absolute!important;background-color:#007bff!important;z-index:100!important}.ql-editor .table-up-resize-line.table-up-resize-line-row{height:2px!important;cursor:row-resize!important;left:0!important;right:0!important}.ql-editor .table-up-resize-line.table-up-resize-line-col{width:2px!important;cursor:col-resize!important;top:0!important;bottom:0!important}.ql-editor .table-up-resize-scale{position:absolute!important;width:8px!important;height:8px!important;background-color:#007bff!important;border:1px solid #fff!important;z-index:101!important;cursor:nw-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-tr{top:-4px!important;right:-4px!important;cursor:ne-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-br{bottom:-4px!important;right:-4px!important;cursor:se-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-bl{bottom:-4px!important;left:-4px!important;cursor:sw-resize!important}.ql-editor .table-up-resize-scale.table-up-resize-scale-tl{top:-4px!important;left:-4px!important;cursor:nw-resize!important}.ql-editor .ql-editor table td.table-up-cell-selected,.ql-editor .ql-editor table th.table-up-cell-selected,.ql-editor .ql-editor .table-up-cell-selected,.ql-editor table td.table-up-cell-selected,.ql-editor table th.table-up-cell-selected,.ql-editor td.table-up-cell-selected,.ql-editor th.table-up-cell-selected{background-color:#007bff66!important;border:3px solid #007bff!important;box-shadow:0 0 0 1px #007bff99!important;position:relative!important}.ql-editor .table-up-merged-cell{background-color:#007bff0d!important;border:2px solid #007bff!important;position:relative!important}.ql-editor .table-up-merged-cell:after{content:\"\"!important;position:absolute!important;top:2px!important;right:2px!important;width:0!important;height:0!important;border-left:8px solid transparent!important;border-right:8px solid transparent!important;border-top:8px solid #007bff!important}.ql-editor .ql-editor table td,.ql-editor .ql-editor table th{transition:all .2s ease!important}.ql-editor .ql-editor table td:hover,.ql-editor .ql-editor table th:hover{background-color:#007bff1a!important;cursor:pointer!important}.ql-editor .table-selection-guide{position:fixed!important;top:10px!important;right:10px!important;background:#007bffe6!important;color:#fff!important;padding:8px 12px!important;border-radius:4px!important;font-size:12px!important;font-weight:700!important;z-index:1000!important;pointer-events:none!important}.ql-editor .table-selection-guide.hidden{display:none!important}.ql-editor .table-up-resize-indicator{position:absolute!important;background-color:#007bff4d!important;pointer-events:none!important;z-index:99!important}.ql-editor .table-up-resize-indicator.table-up-resize-indicator-col{width:2px!important;top:0!important;bottom:0!important}.ql-editor .table-up-resize-indicator.table-up-resize-indicator-row{height:2px!important;left:0!important;right:0!important}.ql-editor .table-up-cell:after{content:\"\"!important;position:absolute!important;right:-1px!important;top:0!important;bottom:0!important;width:4px!important;cursor:col-resize!important;background:transparent!important;z-index:10!important}.ql-editor .table-up-cell:hover:after{background:#007bff4d!important}.ql-editor .manual-resize-handle{position:absolute!important;right:-2px!important;top:0!important;bottom:0!important;width:4px!important;cursor:col-resize!important;background:transparent!important;z-index:15!important;transition:background-color .2s ease!important}.ql-editor .manual-resize-handle:hover{background:#007bff4d!important}.ql-editor .manual-resize-handle:active{background:#007bff80!important}.ql-editor .ql-editor .ql-table td,.ql-editor .ql-editor .ql-table th,.ql-editor .ql-editor table td,.ql-editor .ql-editor table th{position:relative!important}.ql-editor .table-up-menu{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 2px 8px #00000026!important;z-index:1000!important;padding:4px!important}.ql-editor .table-up-menu .table-up-menu-item{padding:8px 12px!important;cursor:pointer!important;border-radius:3px!important;font-size:13px!important}.ql-editor .table-up-menu .table-up-menu-item:hover{background-color:#f5f5f5!important}.ql-editor .table-up-menu .table-up-menu-item.table-up-menu-item-break{height:1px!important;background-color:#e9ecef!important;margin:4px 0!important;padding:0!important}button{background-color:#007bff;color:#fff;border:none;padding:8px 12px;margin:4px;border-radius:4px;cursor:pointer;font-size:14px}button:hover{background-color:#0056b3}button:active{background-color:#004085}button:disabled{background-color:#6c757d;cursor:not-allowed}.table-up-creator,.ql-table-up-creator,[class*=table-creator],[class*=creator]{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 2px 8px #00000026!important;z-index:9999!important;padding:12px!important;display:block!important;visibility:visible!important;opacity:1!important}.table-up-creator .table-up-creator-grid,.ql-table-up-creator .table-up-creator-grid,[class*=table-creator] .table-up-creator-grid,[class*=creator] .table-up-creator-grid{display:grid!important;gap:2px!important;margin-bottom:10px!important}.table-up-creator .table-up-creator-grid .table-up-creator-cell,.ql-table-up-creator .table-up-creator-grid .table-up-creator-cell,[class*=table-creator] .table-up-creator-grid .table-up-creator-cell,[class*=creator] .table-up-creator-grid .table-up-creator-cell{width:16px!important;height:16px!important;border:1px solid #ddd!important;cursor:pointer!important}.table-up-creator .table-up-creator-grid .table-up-creator-cell:hover,.table-up-creator .table-up-creator-grid .table-up-creator-cell.active,.ql-table-up-creator .table-up-creator-grid .table-up-creator-cell:hover,.ql-table-up-creator .table-up-creator-grid .table-up-creator-cell.active,[class*=table-creator] .table-up-creator-grid .table-up-creator-cell:hover,[class*=table-creator] .table-up-creator-grid .table-up-creator-cell.active,[class*=creator] .table-up-creator-grid .table-up-creator-cell:hover,[class*=creator] .table-up-creator-grid .table-up-creator-cell.active{background-color:#007bff!important;border-color:#007bff!important}.table-up-creator .table-up-creator-info,.ql-table-up-creator .table-up-creator-info,[class*=table-creator] .table-up-creator-info,[class*=creator] .table-up-creator-info{text-align:center!important;font-size:12px!important;color:#666!important;margin-bottom:10px!important}.table-up-creator .table-up-creator-buttons,.ql-table-up-creator .table-up-creator-buttons,[class*=table-creator] .table-up-creator-buttons,[class*=creator] .table-up-creator-buttons{display:flex!important;gap:8px!important}.table-up-creator .table-up-creator-buttons button,.ql-table-up-creator .table-up-creator-buttons button,[class*=table-creator] .table-up-creator-buttons button,[class*=creator] .table-up-creator-buttons button{flex:1!important;padding:6px 12px!important;font-size:12px!important}[class*=creator]{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;z-index:9999!important;padding:10px!important;min-width:200px!important;min-height:100px!important}.ql-picker-options{z-index:9999!important}[class*=creator],[class*=table-creator]{outline:2px solid red!important;background:#ff0!important}.manual-table-creator{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 2px 8px #00000026!important;z-index:10000!important;padding:12px!important;display:block!important;visibility:visible!important;opacity:1!important;min-width:200px!important}.manual-table-creator div:first-child{text-align:center!important;font-size:12px!important;color:#333!important;margin-bottom:10px!important;font-weight:700!important;background:#f8f9fa!important;padding:4px 8px!important;border-radius:3px!important}.manual-table-creator div:last-child{display:grid!important;grid-template-columns:repeat(8,20px)!important;gap:2px!important;-webkit-user-select:none!important;user-select:none!important}.manual-table-creator div:last-child div{width:18px!important;height:18px!important;border:1px solid #ddd!important;cursor:pointer!important;background:#fff!important;transition:all .1s ease!important}.manual-table-creator div:last-child div:hover{background-color:#007bff!important;border-color:#0056b3!important;transform:scale(1.05)!important}.manual-table-menu{position:absolute!important;background:#fff!important;border:1px solid #ccc!important;border-radius:4px!important;box-shadow:0 4px 12px #0003!important;z-index:10001!important;padding:4px!important;min-width:150px!important;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif!important}.manual-table-menu div{padding:6px 12px!important;cursor:pointer!important;border-radius:3px!important;font-size:13px!important;color:#333!important;transition:background-color .1s ease!important}.manual-table-menu div:hover{background-color:#f5f5f5!important}.manual-table-menu div:active{background-color:#e9ecef!important}.manual-table-menu div[style*=\"height: 1px\"]{height:1px!important;background:#eee!important;margin:4px 0!important;padding:0!important;cursor:default!important}.manual-table-menu div[style*=\"height: 1px\"]:hover{background:#eee!important}.manual-table-creator *,.manual-table-menu *{pointer-events:auto!important;-webkit-user-select:none!important;user-select:none!important}.status-message{position:fixed!important;top:20px!important;right:20px!important;padding:12px 20px!important;border-radius:4px!important;box-shadow:0 2px 8px #0003!important;z-index:10001!important;font-size:14px!important;font-weight:500!important;color:#fff!important}@keyframes slideIn{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes slideOut{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}.table-up-color-picker .table-up-color-grid{display:grid;grid-template-columns:repeat(10,1fr);gap:2px;margin:8px 0}.table-up-color-picker .table-up-color-grid .table-up-color-item{width:20px;height:20px;cursor:pointer;border:1px solid #ddd;border-radius:2px}.table-up-color-picker .table-up-color-grid .table-up-color-item:hover{transform:scale(1.1);border-color:#007bff}@keyframes selection-glow{0%{opacity:.3}to{opacity:.7}}.ql-editor table td,.ql-editor table th{transition:all .2s ease!important;cursor:pointer!important}.ql-editor table td:hover,.ql-editor table th:hover{background-color:#007bff1a!important;transform:scale(1.02)!important;box-shadow:0 2px 4px #007bff33!important}.selection-area-indicator{position:absolute!important;border:2px dashed #007bff!important;background-color:#007bff1a!important;pointer-events:none!important;z-index:1000!important;border-radius:4px!important}.manual-table-creator{position:absolute;top:0;left:0;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 6px 16px #0000001f;padding:12px;z-index:10000;min-width:200px}.manual-table-creator>div:first-child{font-size:12px;color:#333;margin-bottom:10px;text-align:center;font-weight:600}.manual-table-creator>div:last-child{display:grid;grid-template-columns:repeat(8,18px);gap:4px}.manual-table-creator>div:last-child>div{width:18px;height:18px;border:1px solid #e5e7eb;background:#fff;cursor:pointer;transition:background-color .12s ease}.manual-table-creator>div:last-child>div:hover{background-color:#dbeafe;border-color:#93c5fd}\n"] }]
1829
+ }] });
1830
+
1831
+ /**
1832
+ * Generated bundle index. Do not edit.
1833
+ */
1834
+
1835
+ export { LibsUiComponentsInputsQuill2xComponent, LibsUiComponentsInputsQuillComponent };
1836
+ //# sourceMappingURL=libs-ui-components-inputs-quill.mjs.map