@libs-ui/components-inputs-quill2x 0.2.356-8 → 0.2.357-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +553 -68
- package/blots-custom/mention2x.component.d.ts +3 -3
- package/esm2022/blots-custom/mention2x.component.mjs +1 -1
- package/esm2022/link/link.component.mjs +2 -2
- package/esm2022/quill2x.component.mjs +76 -55
- package/esm2022/upload-image/upload-image.component.mjs +4 -38
- package/esm2022/utils/functions.define.mjs +2 -2
- package/fesm2022/libs-ui-components-inputs-quill2x.mjs +82 -95
- package/fesm2022/libs-ui-components-inputs-quill2x.mjs.map +1 -1
- package/link/link.component.d.ts +1 -1
- package/package.json +24 -2
- package/quill2x.component.d.ts +17 -13
package/README.md
CHANGED
|
@@ -1,114 +1,599 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @libs-ui/components-inputs-quill2x
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Rich Text Editor mạnh mẽ dựa trên Quill 2.x, tích hợp đầy đủ toolbar, mention, emoji, bảng, upload ảnh và validation cho Angular.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
- 🛠️ **Custom Toolbar**: Hỗ trợ nhiều chế độ hiển thị toolbar (`default`, `basic`, `all`, `custom`) và tùy chỉnh vị trí cố định.
|
|
9
|
-
- 🖼️ **Media Support**: Tích hợp trình tải lên hình ảnh, video và hỗ trợ resize hình ảnh.
|
|
10
|
-
- 🏷️ **Mention Integration**: Hỗ trợ tính năng mention (@người dùng, #thẻ tag) mượt mà.
|
|
11
|
-
- 📊 **Table Support**: Hỗ trợ tạo và quản lý bảng chuyên nghiệp với các thao tác dòng/cột.
|
|
12
|
-
- 😀 **Emoji Picker**: Tích hợp sẵn bộ chọn emoji.
|
|
13
|
-
- ✅ **Validation**: Hỗ trợ các quy tắc kiểm tra bắt buộc, độ dài tối thiểu/tối đa.
|
|
14
|
-
- 📱 **Responsive**: Giao diện co giãn tốt và toolbar thông minh (nút xem thêm khi không đủ chỗ).
|
|
15
|
-
- 🎨 **Styling**: Tùy chỉnh màu sắc văn bản, màu nền và phông chữ linh hoạt.
|
|
7
|
+
`@libs-ui/components-inputs-quill2x` cung cấp một trình soạn thảo văn bản giàu tính năng (Rich Text Editor) được xây dựng trên nền tảng Quill 2.x. Component hỗ trợ binding hai chiều với object model, toolbar thông minh tự co giãn theo chiều rộng container, cùng nhiều tính năng nâng cao như mention, emoji, bảng, upload ảnh và validation tích hợp. Nội dung luôn được lọc qua bộ lọc XSS nội bộ trước khi ghi vào model.
|
|
16
8
|
|
|
17
|
-
##
|
|
9
|
+
## Tính năng
|
|
10
|
+
|
|
11
|
+
- Toolbar thông minh tự tính toán độ rộng, tự động ẩn nút vào menu "Xem thêm" khi không đủ chỗ
|
|
12
|
+
- Hỗ trợ 4 chế độ toolbar: `default`, `basic`, `all`, `custom`
|
|
13
|
+
- Toolbar floating (position fixed) hiển thị theo sự kiện click/mouseenter trên element bất kỳ
|
|
14
|
+
- Tích hợp mention (@user, #tag) với danh sách gợi ý có thể lọc động
|
|
15
|
+
- Bộ chọn emoji tích hợp sẵn
|
|
16
|
+
- Hỗ trợ tạo và quản lý bảng (table) với context menu dòng/cột
|
|
17
|
+
- Upload ảnh từ file hoặc paste từ clipboard, hỗ trợ custom upload function
|
|
18
|
+
- Plugin resize ảnh (tùy chọn)
|
|
19
|
+
- Validation: bắt buộc nhập, độ dài tối thiểu/tối đa
|
|
20
|
+
- Lọc màu gần trắng (near white) khi paste từ Word/nguồn bên ngoài
|
|
21
|
+
- Hỗ trợ nhập tiếng Việt Telex/VNI qua composition event
|
|
22
|
+
- Auto-detect và format URL thành hyperlink khi gõ
|
|
23
|
+
- Lịch sử undo/redo với debounce 2 giây
|
|
24
|
+
- Expose `FunctionsControl` để component cha tương tác trực tiếp với editor
|
|
25
|
+
- XSS filter tự động khi set content
|
|
26
|
+
|
|
27
|
+
## Khi nào sử dụng
|
|
18
28
|
|
|
19
|
-
|
|
29
|
+
- Khi cần trình soạn thảo văn bản hỗ trợ định dạng (Bold, Italic, danh sách, tiêu đề)
|
|
30
|
+
- Khi xây dựng hệ thống chat hoặc comment cần tính năng mention người dùng
|
|
31
|
+
- Khi cần tích hợp bảng, hình ảnh vào nội dung (CMS, báo cáo, ghi chú)
|
|
32
|
+
- Khi cần trải nghiệm soạn thảo chuyên nghiệp tương đương Slack, Notion
|
|
33
|
+
- Khi cần toolbar floating gắn với một element bên ngoài editor
|
|
34
|
+
|
|
35
|
+
## Cài đặt
|
|
20
36
|
|
|
21
37
|
```bash
|
|
22
38
|
npm install @libs-ui/components-inputs-quill2x
|
|
23
39
|
```
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
> Các package phụ thuộc (`quill2x` = `npm:quill@2.0.3`, `@ssumo/quill-resize-module`, `quill-delta`...) đã khai báo `peerDependencies` trong `package.json` của lib → trình quản lý gói tự cài kèm, không cần cài thủ công.
|
|
42
|
+
|
|
43
|
+
### ⚠️ Nạp CSS của Quill (BẮT BUỘC — nếu thiếu, editor/toolbar sẽ vỡ giao diện)
|
|
44
|
+
|
|
45
|
+
Quill ship style riêng, **KHÔNG** đi kèm trong bundle của component. Consumer PHẢI tự nạp CSS theme từ package `quill2x`. Khai báo trong `angular.json` / `project.json`:
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
// architect.build.options.styles (Angular CLI) HOẶC targets.build.options.styles (Nx)
|
|
49
|
+
"styles": [
|
|
50
|
+
"src/styles.scss",
|
|
51
|
+
"./node_modules/quill2x/dist/quill.snow.css",
|
|
52
|
+
"./node_modules/quill2x/dist/quill.bubble.css"
|
|
53
|
+
]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
> 🔴 `quill.snow.css` là theme mặc định (BẮT BUỘC). Chỉ thêm `quill.bubble.css` nếu dùng theme bubble.
|
|
57
|
+
> Lấy CSS từ package `quill2x` (KHÔNG dùng `quill`) để khớp đúng version Quill 2.x.
|
|
58
|
+
> Nếu gặp cảnh báo CommonJS khi build, thêm `allowedCommonJsDependencies: ["quill2x"]` vào cấu hình build.
|
|
26
59
|
|
|
27
|
-
|
|
60
|
+
## Import
|
|
28
61
|
|
|
29
62
|
```typescript
|
|
30
63
|
import { LibsUiComponentsInputsQuill2xComponent } from '@libs-ui/components-inputs-quill2x';
|
|
31
64
|
|
|
65
|
+
// Interfaces và types
|
|
66
|
+
import {
|
|
67
|
+
IQuill2xCustomConfig,
|
|
68
|
+
IQuill2xFunctionControlEvent,
|
|
69
|
+
IQuill2xUploadImageConfig,
|
|
70
|
+
IQuill2xBlotRegister,
|
|
71
|
+
IQuill2xSelectionChange,
|
|
72
|
+
IQuill2xTextChange,
|
|
73
|
+
IQuill2xLink,
|
|
74
|
+
IQuill2xToolbarConfig,
|
|
75
|
+
QUILL2X_TYPE_MODE_BAR_CONFIG_OPTION,
|
|
76
|
+
} from '@libs-ui/components-inputs-quill2x';
|
|
77
|
+
|
|
78
|
+
// Utility functions
|
|
79
|
+
import {
|
|
80
|
+
getHTMLFromDeltaOfQuill2x,
|
|
81
|
+
getDeltaOfQuill2xFromHTML,
|
|
82
|
+
isEmptyQuill2x,
|
|
83
|
+
convertStandardList,
|
|
84
|
+
convertStandardListToQuill2x,
|
|
85
|
+
} from '@libs-ui/components-inputs-quill2x';
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Ví dụ sử dụng
|
|
89
|
+
|
|
90
|
+
### 1. Cơ bản — binding hai chiều với object model
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// component.ts
|
|
94
|
+
import { Component, signal } from '@angular/core';
|
|
95
|
+
import { LibsUiComponentsInputsQuill2xComponent } from '@libs-ui/components-inputs-quill2x';
|
|
96
|
+
|
|
32
97
|
@Component({
|
|
33
98
|
standalone: true,
|
|
99
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
34
100
|
imports: [LibsUiComponentsInputsQuill2xComponent],
|
|
35
|
-
|
|
101
|
+
templateUrl: './my.component.html',
|
|
36
102
|
})
|
|
37
|
-
export class
|
|
38
|
-
|
|
103
|
+
export class MyComponent {
|
|
104
|
+
protected formData = signal<Record<string, string>>({
|
|
105
|
+
content: '<p>Xin chào! Đây là nội dung ban đầu.</p>',
|
|
106
|
+
});
|
|
39
107
|
|
|
40
|
-
|
|
108
|
+
protected handlerChange(html: string): void {
|
|
109
|
+
// html là nội dung HTML đã qua XSS filter
|
|
110
|
+
console.log('Nội dung mới:', html);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
41
114
|
|
|
42
115
|
```html
|
|
116
|
+
<!-- my.component.html -->
|
|
43
117
|
<libs_ui-components-inputs-quill2x
|
|
44
118
|
[(item)]="formData"
|
|
45
119
|
[fieldBind]="'content'"
|
|
46
|
-
[placeholder]="'Nhập nội dung
|
|
120
|
+
[placeholder]="'Nhập nội dung...'"
|
|
121
|
+
(outChange)="handlerChange($event)">
|
|
122
|
+
</libs_ui-components-inputs-quill2x>
|
|
47
123
|
```
|
|
48
124
|
|
|
49
|
-
###
|
|
125
|
+
### 2. Toàn bộ tính năng — toolbar `all`, resize editor, giới hạn chiều cao
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// component.ts
|
|
129
|
+
import { Component, signal } from '@angular/core';
|
|
130
|
+
import { LibsUiComponentsInputsQuill2xComponent, IQuill2xCustomConfig } from '@libs-ui/components-inputs-quill2x';
|
|
131
|
+
|
|
132
|
+
@Component({
|
|
133
|
+
standalone: true,
|
|
134
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
135
|
+
imports: [LibsUiComponentsInputsQuill2xComponent],
|
|
136
|
+
templateUrl: './my.component.html',
|
|
137
|
+
})
|
|
138
|
+
export class MyComponent {
|
|
139
|
+
protected articleData = signal<Record<string, string>>({
|
|
140
|
+
body: '<h1>Tiêu đề bài viết</h1><p>Nội dung bài viết tại đây.</p>',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
protected fullConfig: IQuill2xCustomConfig = {
|
|
144
|
+
toolbar: signal({
|
|
145
|
+
type: signal<'all'>('all'),
|
|
146
|
+
}),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
protected handlerFunctionsControl(ctrl: IQuill2xFunctionControlEvent): void {
|
|
150
|
+
// Lưu lại để sử dụng về sau
|
|
151
|
+
this.editorControl = ctrl;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private editorControl?: IQuill2xFunctionControlEvent;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
50
157
|
|
|
51
158
|
```html
|
|
159
|
+
<!-- my.component.html -->
|
|
52
160
|
<libs_ui-components-inputs-quill2x
|
|
53
|
-
[(item)]="
|
|
54
|
-
[fieldBind]="'
|
|
55
|
-
[quillCustomConfig]="
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| Thuộc tính | Kiểu dữ liệu | Mặc định | Mô tả |
|
|
65
|
-
| :------------------ | :--------------------- | :---------------------- | :-------------------------------------------------- |
|
|
66
|
-
| `item` | `TYPE_OBJECT` | **(Required)** | Object chứa dữ liệu cần bind. |
|
|
67
|
-
| `fieldBind` | `string` | **(Required)** | Tên trường trong `item` để bind giá trị HTML. |
|
|
68
|
-
| `placeholder` | `string` | `'i18n_import_content'` | Văn bản gợi ý khi editor trống. |
|
|
69
|
-
| `readonly` | `boolean` | `false` | Chế độ chỉ đọc. |
|
|
70
|
-
| `displayToolbar` | `boolean` | `true` | Hiển thị hoặc ẩn thanh công cụ. |
|
|
71
|
-
| `quillCustomConfig` | `IQuill2xCustomConfig` | `undefined` | Cấu hình nâng cao cho toolbar và editor. |
|
|
72
|
-
| `dataConfigMention` | `IMentionConfig` | `undefined` | Cấu hình cho tính năng mention. |
|
|
73
|
-
| `validRequired` | `IValidRequired` | `undefined` | Quy tắc kiểm tra bắt buộc nhập. |
|
|
74
|
-
| `validMinLength` | `IValidLength` | `undefined` | Quy tắc kiểm tra độ dài tối thiểu. |
|
|
75
|
-
| `validMaxLength` | `IValidLength` | `undefined` | Quy tắc kiểm tra độ dài tối đa. |
|
|
76
|
-
| `resize` | `'vertical' \| 'none'` | `'none'` | Cho phép thay đổi kích thước editor theo chiều dọc. |
|
|
77
|
-
|
|
78
|
-
### Outputs
|
|
79
|
-
|
|
80
|
-
| Sự kiện | Kiểu dữ liệu | Mô tả |
|
|
81
|
-
| :-------------------- | :----------------------------- | :--------------------------------------------------------------------------------------- |
|
|
82
|
-
| `outChange` | `string` | Phát ra nội dung HTML mới khi có thay đổi. |
|
|
83
|
-
| `outFocus` | `void` | Phát ra khi editor được focus. |
|
|
84
|
-
| `outBlur` | `void` | Phát ra khi editor mất focus. |
|
|
85
|
-
| `outFunctionsControl` | `IQuill2xFunctionControlEvent` | Cung cấp các phương thức điều khiển editor (`setContent`, `insertText`, `quill()`, ...). |
|
|
161
|
+
[(item)]="articleData"
|
|
162
|
+
[fieldBind]="'body'"
|
|
163
|
+
[quillCustomConfig]="fullConfig"
|
|
164
|
+
[resize]="'vertical'"
|
|
165
|
+
[minHeightEditorContentDefault]="150"
|
|
166
|
+
[maxHeightEditorContentDefault]="600"
|
|
167
|
+
[resizeImagePlugin]="true"
|
|
168
|
+
[validRequired]="{ isRequired: true, message: 'Vui lòng nhập nội dung' }"
|
|
169
|
+
(outFunctionsControl)="handlerFunctionsControl($event)">
|
|
170
|
+
</libs_ui-components-inputs-quill2x>
|
|
171
|
+
```
|
|
86
172
|
|
|
87
|
-
|
|
173
|
+
### 3. Mention — gợi ý @user khi gõ ký tự trigger
|
|
88
174
|
|
|
89
|
-
|
|
175
|
+
```typescript
|
|
176
|
+
// component.ts
|
|
177
|
+
import { Component, signal } from '@angular/core';
|
|
178
|
+
import { LibsUiComponentsInputsQuill2xComponent } from '@libs-ui/components-inputs-quill2x';
|
|
179
|
+
import { IMentionConfig } from '@libs-ui/components-inputs-mention';
|
|
180
|
+
|
|
181
|
+
@Component({
|
|
182
|
+
standalone: true,
|
|
183
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
184
|
+
imports: [LibsUiComponentsInputsQuill2xComponent],
|
|
185
|
+
templateUrl: './my.component.html',
|
|
186
|
+
})
|
|
187
|
+
export class MyComponent {
|
|
188
|
+
protected commentData = signal<Record<string, string>>({ comment: '' });
|
|
189
|
+
|
|
190
|
+
protected mentionConfig: IMentionConfig = {
|
|
191
|
+
items: [
|
|
192
|
+
{ id: '1', name: 'Anh Tuấn', username: 'tuan@company.com' },
|
|
193
|
+
{ id: '2', name: 'Bảo Ngọc', username: 'ngoc@company.com' },
|
|
194
|
+
{ id: '3', name: 'Công Thành', username: 'thanh@company.com' },
|
|
195
|
+
],
|
|
196
|
+
triggerChar: '@',
|
|
197
|
+
labelKey: 'name',
|
|
198
|
+
mentionFilter: (search: string, items?: Array<Record<string, string>>) => {
|
|
199
|
+
const keyword = search.toLowerCase();
|
|
200
|
+
return items?.filter((item) => item['name'].toLowerCase().includes(keyword));
|
|
201
|
+
},
|
|
202
|
+
mentionSelect: (item: Record<string, string>, triggerChar?: string) => {
|
|
203
|
+
return (triggerChar || '@') + item['name'];
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<!-- my.component.html -->
|
|
211
|
+
<libs_ui-components-inputs-quill2x
|
|
212
|
+
[(item)]="commentData"
|
|
213
|
+
[fieldBind]="'comment'"
|
|
214
|
+
[dataConfigMention]="mentionConfig"
|
|
215
|
+
[placeholder]="'Nhập bình luận, gõ @ để mention...'">
|
|
216
|
+
</libs_ui-components-inputs-quill2x>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### 4. Upload ảnh — custom upload function
|
|
90
220
|
|
|
91
221
|
```typescript
|
|
92
|
-
|
|
222
|
+
// component.ts
|
|
223
|
+
import { Component, signal } from '@angular/core';
|
|
224
|
+
import { LibsUiComponentsInputsQuill2xComponent, IQuill2xUploadImageConfig } from '@libs-ui/components-inputs-quill2x';
|
|
225
|
+
|
|
226
|
+
@Component({
|
|
227
|
+
standalone: true,
|
|
228
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
229
|
+
imports: [LibsUiComponentsInputsQuill2xComponent],
|
|
230
|
+
templateUrl: './my.component.html',
|
|
231
|
+
})
|
|
232
|
+
export class MyComponent {
|
|
233
|
+
protected postData = signal<Record<string, string>>({ content: '' });
|
|
234
|
+
|
|
235
|
+
protected uploadConfig: IQuill2xUploadImageConfig = {
|
|
236
|
+
modeCustom: true,
|
|
237
|
+
showIcon: true,
|
|
238
|
+
maxImageSize: 5 * 1024 * 1024, // 5MB
|
|
239
|
+
onlyAcceptImageHttpsLink: true,
|
|
240
|
+
functionUploadImage: async (files: Array<File>): Promise<Array<string | ArrayBuffer>> => {
|
|
241
|
+
// Gọi API upload ảnh thực tế
|
|
242
|
+
const uploadedUrls: string[] = [];
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
const formData = new FormData();
|
|
245
|
+
formData.append('file', file);
|
|
246
|
+
const response = await fetch('/api/upload', { method: 'POST', body: formData });
|
|
247
|
+
const result = await response.json();
|
|
248
|
+
uploadedUrls.push(result.url);
|
|
249
|
+
}
|
|
250
|
+
return uploadedUrls;
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```html
|
|
257
|
+
<!-- my.component.html -->
|
|
258
|
+
<libs_ui-components-inputs-quill2x
|
|
259
|
+
[(item)]="postData"
|
|
260
|
+
[fieldBind]="'content'"
|
|
261
|
+
[uploadImageConfig]="uploadConfig">
|
|
262
|
+
</libs_ui-components-inputs-quill2x>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 5. Toolbar floating (position fixed) — toolbar hiển thị theo element trigger
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// component.ts
|
|
269
|
+
import { AfterViewInit, Component, ElementRef, signal, ViewChild } from '@angular/core';
|
|
270
|
+
import { LibsUiComponentsInputsQuill2xComponent, IQuill2xCustomConfig } from '@libs-ui/components-inputs-quill2x';
|
|
271
|
+
|
|
272
|
+
@Component({
|
|
273
|
+
standalone: true,
|
|
274
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
275
|
+
imports: [LibsUiComponentsInputsQuill2xComponent],
|
|
276
|
+
templateUrl: './my.component.html',
|
|
277
|
+
})
|
|
278
|
+
export class MyComponent implements AfterViewInit {
|
|
279
|
+
@ViewChild('triggerBtn') triggerBtn?: ElementRef<HTMLButtonElement>;
|
|
280
|
+
|
|
281
|
+
protected floatingData = signal<Record<string, string>>({ content: '' });
|
|
282
|
+
protected floatingConfig = signal<IQuill2xCustomConfig | undefined>(undefined);
|
|
283
|
+
|
|
284
|
+
ngAfterViewInit(): void {
|
|
285
|
+
if (!this.triggerBtn) return;
|
|
286
|
+
this.floatingConfig.set({
|
|
287
|
+
toolbar: signal({
|
|
288
|
+
type: signal<'default'>('default'),
|
|
289
|
+
positionFixed: signal({
|
|
290
|
+
event: signal<'click'>('click'),
|
|
291
|
+
elementShow: signal(this.triggerBtn.nativeElement),
|
|
292
|
+
position: signal<'bottom'>('bottom'),
|
|
293
|
+
align: signal<'left'>('left'),
|
|
294
|
+
zIndex: signal(9999),
|
|
295
|
+
width: signal(600),
|
|
296
|
+
gapVertical: signal(5),
|
|
297
|
+
gapHorizontal: signal(0),
|
|
298
|
+
}),
|
|
299
|
+
}),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```html
|
|
306
|
+
<!-- my.component.html -->
|
|
307
|
+
<button #triggerBtn class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
|
|
308
|
+
Mở toolbar
|
|
309
|
+
</button>
|
|
310
|
+
<libs_ui-components-inputs-quill2x
|
|
311
|
+
[(item)]="floatingData"
|
|
312
|
+
[fieldBind]="'content'"
|
|
313
|
+
[quillCustomConfig]="floatingConfig()"
|
|
314
|
+
[minHeightEditorContentDefault]="150">
|
|
315
|
+
</libs_ui-components-inputs-quill2x>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 6. Lập trình điều khiển editor qua FunctionsControl
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// component.ts
|
|
322
|
+
import { Component, signal } from '@angular/core';
|
|
323
|
+
import { LibsUiComponentsInputsQuill2xComponent, IQuill2xFunctionControlEvent } from '@libs-ui/components-inputs-quill2x';
|
|
324
|
+
|
|
325
|
+
@Component({
|
|
326
|
+
standalone: true,
|
|
327
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
328
|
+
imports: [LibsUiComponentsInputsQuill2xComponent],
|
|
329
|
+
templateUrl: './my.component.html',
|
|
330
|
+
})
|
|
331
|
+
export class MyComponent {
|
|
332
|
+
protected editorData = signal<Record<string, string>>({ html: '' });
|
|
333
|
+
private editorCtrl?: IQuill2xFunctionControlEvent;
|
|
334
|
+
|
|
335
|
+
protected handlerFunctionsControl(ctrl: IQuill2xFunctionControlEvent): void {
|
|
336
|
+
this.editorCtrl = ctrl;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
protected async handlerInsertTemplate(): Promise<void> {
|
|
340
|
+
await this.editorCtrl?.setContent('<p>Nội dung template mới được set từ bên ngoài.</p>');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
protected async handlerValidate(): Promise<void> {
|
|
344
|
+
const isValid = await this.editorCtrl?.checkIsValid();
|
|
345
|
+
console.log('Valid:', isValid);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
```html
|
|
351
|
+
<!-- my.component.html -->
|
|
352
|
+
<libs_ui-components-inputs-quill2x
|
|
353
|
+
[(item)]="editorData"
|
|
354
|
+
[fieldBind]="'html'"
|
|
355
|
+
[validRequired]="{ isRequired: true, message: 'Vui lòng nhập nội dung' }"
|
|
356
|
+
(outFunctionsControl)="handlerFunctionsControl($event)">
|
|
357
|
+
</libs_ui-components-inputs-quill2x>
|
|
358
|
+
<button (click)="handlerInsertTemplate()">Chèn template</button>
|
|
359
|
+
<button (click)="handlerValidate()">Kiểm tra hợp lệ</button>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## @Input()
|
|
363
|
+
|
|
364
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
365
|
+
|---|---|---|---|---|
|
|
366
|
+
| `item` (model, required) | `TYPE_OBJECT` | — | Object chứa dữ liệu binding. Binding hai chiều qua `[(item)]`. | `[(item)]="formData"` |
|
|
367
|
+
| `fieldBind` (required) | `string` | — | Tên trường trong `item` để đọc/ghi nội dung HTML. | `[fieldBind]="'content'"` |
|
|
368
|
+
| `placeholder` | `string` | `'i18n_import_content'` | Văn bản placeholder khi editor trống. | `[placeholder]="'Nhập nội dung...'"` |
|
|
369
|
+
| `readonly` | `boolean` | `false` | Bật chế độ chỉ đọc, vô hiệu hóa toolbar. | `[readonly]="true"` |
|
|
370
|
+
| `displayToolbar` | `boolean` | `true` | Ẩn/hiện toàn bộ thanh công cụ. | `[displayToolbar]="false"` |
|
|
371
|
+
| `quillCustomConfig` | `IQuill2xCustomConfig` | `undefined` | Cấu hình nâng cao: loại toolbar, position fixed, class tùy chỉnh. | `[quillCustomConfig]="myConfig"` |
|
|
372
|
+
| `dataConfigMention` | `IMentionConfig` | `undefined` | Cấu hình tính năng mention (@user, #tag). | `[dataConfigMention]="mentionConfig"` |
|
|
373
|
+
| `uploadImageConfig` | `IQuill2xUploadImageConfig` | mặc định | Cấu hình upload ảnh: custom mode, kích thước tối đa, function upload. | `[uploadImageConfig]="uploadCfg"` |
|
|
374
|
+
| `label` | `ILabel` | `undefined` | Label hiển thị phía trên editor. | `[label]="{ text: 'Mô tả' }"` |
|
|
375
|
+
| `resize` | `'vertical' \| 'none'` | `'none'` | Cho phép kéo giãn editor theo chiều dọc. | `[resize]="'vertical'"` |
|
|
376
|
+
| `zIndex` | `number` | `1250` | z-index áp dụng cho các popup/modal của editor. | `[zIndex]="2000"` |
|
|
377
|
+
| `autoFocus` | `boolean` | `false` | Tự động focus vào editor khi khởi tạo. | `[autoFocus]="true"` |
|
|
378
|
+
| `focusTimerOnInit` | `number` | `750` | Delay (ms) trước khi thực hiện auto focus. | `[focusTimerOnInit]="300"` |
|
|
379
|
+
| `focusBottom` | `boolean` | `false` | Khi auto focus, đặt con trỏ ở cuối nội dung thay vì đầu. | `[focusBottom]="true"` |
|
|
380
|
+
| `fontSizeDefault` | `number` | `14` | Cỡ chữ mặc định (px). | `[fontSizeDefault]="16"` |
|
|
381
|
+
| `heightEditorContentDefault` | `number` | `undefined` | Chiều cao cố định của vùng soạn thảo (px). | `[heightEditorContentDefault]="300"` |
|
|
382
|
+
| `minHeightEditorContentDefault` | `number` | `undefined` | Chiều cao tối thiểu của vùng soạn thảo (px). | `[minHeightEditorContentDefault]="150"` |
|
|
383
|
+
| `maxHeightEditorContentDefault` | `number` | `undefined` | Chiều cao tối đa của vùng soạn thảo (px). | `[maxHeightEditorContentDefault]="500"` |
|
|
384
|
+
| `validRequired` | `IValidRequired` | `undefined` | Quy tắc bắt buộc nhập. `{ isRequired: true, message: '...' }` | `[validRequired]="{ isRequired: true }"` |
|
|
385
|
+
| `validMinLength` | `IValidLength` | `undefined` | Quy tắc độ dài tối thiểu. `{ length: 10, message: '...' }` | `[validMinLength]="{ length: 20 }"` |
|
|
386
|
+
| `validMaxLength` | `IValidLength` | `undefined` | Quy tắc độ dài tối đa. `{ length: 1000, message: '...' }` | `[validMaxLength]="{ length: 500 }"` |
|
|
387
|
+
| `showErrorLabel` | `boolean` | `true` | Hiển thị label thông báo lỗi validation bên dưới editor. | `[showErrorLabel]="false"` |
|
|
388
|
+
| `showErrorBorder` | `boolean` | `true` | Hiển thị viền đỏ khi validation thất bại. | `[showErrorBorder]="false"` |
|
|
389
|
+
| `ignoreShowBorderErrorToolbar` | `boolean` | `false` | Không áp dụng viền đỏ lên toolbar khi có lỗi. | `[ignoreShowBorderErrorToolbar]="true"` |
|
|
390
|
+
| `blotsRegister` | `Array<IQuill2xBlotRegister>` | `undefined` | Đăng ký custom Quill blots/formats. | `[blotsRegister]="customBlots"` |
|
|
391
|
+
| `templateToolBarPersonalize` | `TemplateRef` | `undefined` | TemplateRef để thêm nút tùy chỉnh vào toolbar. | `[templateToolBarPersonalize]="myTmpl"` |
|
|
392
|
+
| `handlersExpand` | `Array<{title: string; action: () => void}>` | `undefined` | Mở rộng handlers toolbar. Key `title` khớp với tên nút trong toolbar. | `[handlersExpand]="[{title:'myBtn', action: fn}]"` |
|
|
393
|
+
| `resizeImagePlugin` | `boolean` | `false` | Bật plugin cho phép resize ảnh trực tiếp trong editor. | `[resizeImagePlugin]="true"` |
|
|
394
|
+
| `removeNearWhiteColorsOnPaste` | `boolean` | `true` | Tự động xóa màu gần trắng khi paste từ nguồn bên ngoài. | `[removeNearWhiteColorsOnPaste]="false"` |
|
|
395
|
+
| `ignoreShowPopupEditLink` | `boolean` | `false` | Khi `true`, emit `outShowPopupEditLink` thay vì dùng popup mặc định để edit link. | `[ignoreShowPopupEditLink]="true"` |
|
|
396
|
+
| `ignoreCommunicateMicroEventPopup` | `boolean` | `false` | Bỏ qua giao tiếp micro-frontend event cho các popup nội bộ. | `[ignoreCommunicateMicroEventPopup]="true"` |
|
|
397
|
+
|
|
398
|
+
## @Output()
|
|
399
|
+
|
|
400
|
+
| Output | Type | Mô tả | Handler TS | Binding HTML |
|
|
401
|
+
|---|---|---|---|---|
|
|
402
|
+
| `(outChange)` | `string` | Emit nội dung HTML (đã qua XSS filter) mỗi khi nội dung thay đổi. | `handlerChange(html: string): void { event.stopPropagation?.(); this.savedHtml = html; }` | `(outChange)="handlerChange($event)"` |
|
|
403
|
+
| `(outFocus)` | `void` | Emit khi editor nhận focus. | `handlerFocus(): void { this.isEditing.set(true); }` | `(outFocus)="handlerFocus()"` |
|
|
404
|
+
| `(outBlur)` | `void` | Emit khi editor mất focus. | `handlerBlur(): void { this.isEditing.set(false); }` | `(outBlur)="handlerBlur()"` |
|
|
405
|
+
| `(outFunctionsControl)` | `IQuill2xFunctionControlEvent` | Emit ngay khi editor khởi tạo xong, trả về object chứa các hàm điều khiển editor. | `handlerFunctionsControl(ctrl: IQuill2xFunctionControlEvent): void { this.editorCtrl = ctrl; }` | `(outFunctionsControl)="handlerFunctionsControl($event)"` |
|
|
406
|
+
| `(outSelectionChange)` | `IQuill2xSelectionChange` | Emit khi vị trí con trỏ hoặc vùng chọn thay đổi. | `handlerSelectionChange(e: IQuill2xSelectionChange): void { console.log(e.range); }` | `(outSelectionChange)="handlerSelectionChange($event)"` |
|
|
407
|
+
| `(outTextChange)` | `IQuill2xTextChange` | Emit khi nội dung thay đổi, bao gồm Quill Delta. | `handlerTextChange(e: IQuill2xTextChange): void { console.log(e.delta); }` | `(outTextChange)="handlerTextChange($event)"` |
|
|
408
|
+
| `(outMessageError)` | `string` | Emit thông báo lỗi validation. Chuỗi rỗng khi không có lỗi. | `handlerMessageError(msg: string): void { this.errorMsg.set(msg); }` | `(outMessageError)="handlerMessageError($event)"` |
|
|
409
|
+
| `(outContextMenu)` | `MouseEvent` | Emit khi người dùng click chuột phải vào vùng soạn thảo. | `handlerContextMenu(event: MouseEvent): void { event.stopPropagation(); event.preventDefault(); }` | `(outContextMenu)="handlerContextMenu($event)"` |
|
|
410
|
+
| `(outShowPopupEditLink)` | `{ dataLink: IQuill2xLink; callback: (linkEdit: { title: string; link: string }) => Promise<void> }` | Emit khi cần hiển thị popup edit link tùy chỉnh. Chỉ hoạt động khi `ignoreShowPopupEditLink="true"`. | `handlerShowPopupEditLink(e: { dataLink: IQuill2xLink; callback: Function }): void { this.openCustomLinkDialog(e); }` | `(outShowPopupEditLink)="handlerShowPopupEditLink($event)"` |
|
|
411
|
+
|
|
412
|
+
## FunctionsControl — Điều khiển editor từ component cha
|
|
413
|
+
|
|
414
|
+
`FunctionsControl` được emit qua `(outFunctionsControl)`. Lưu lại để gọi các hàm bên dưới:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
private editorCtrl?: IQuill2xFunctionControlEvent;
|
|
418
|
+
|
|
419
|
+
protected handlerFunctionsControl(ctrl: IQuill2xFunctionControlEvent): void {
|
|
420
|
+
this.editorCtrl = ctrl;
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
| Method | Signature | Mô tả |
|
|
425
|
+
|---|---|---|
|
|
426
|
+
| `setContent` | `(content: string) => Promise<void>` | Set toàn bộ nội dung HTML của editor (qua XSS filter và convert list). |
|
|
427
|
+
| `insertText` | `(value: string, index?: number, focusLt?: boolean) => Promise<void>` | Chèn plain text tại vị trí con trỏ hoặc index chỉ định. |
|
|
428
|
+
| `insertLink` | `(value: string, url: string, index?: number) => Promise<void>` | Chèn hyperlink vào editor. |
|
|
429
|
+
| `insertImage` | `(content: string, index?: number) => Promise<void>` | Chèn ảnh theo URL vào editor. |
|
|
430
|
+
| `setFontSize` | `(size: number) => Promise<Delta \| undefined>` | Đặt cỡ chữ (px) tại vị trí con trỏ hiện tại. |
|
|
431
|
+
| `setColor` | `(color: string) => Promise<Delta \| undefined>` | Đặt màu chữ tại vị trí con trỏ. |
|
|
432
|
+
| `setBackground` | `(color: string) => Promise<Delta \| undefined>` | Đặt màu nền tại vị trí con trỏ. |
|
|
433
|
+
| `checkIsValid` | `() => Promise<boolean>` | Chạy validation theo các input `valid*` đã cấu hình. Trả về `true` nếu hợp lệ. |
|
|
434
|
+
| `refreshItemValue` | `() => void` | Buộc đọc lại nội dung HTML từ DOM và cập nhật vào `item[fieldBind]`. |
|
|
435
|
+
| `setMessageError` | `(message: string) => Promise<void>` | Hiển thị thông báo lỗi tùy chỉnh bên dưới editor. |
|
|
436
|
+
| `reCalculatorToolbar` | `() => Promise<void>` | Tính toán lại kích thước toolbar (dùng khi container thay đổi kích thước). |
|
|
437
|
+
| `updatePositionToolbar` | `() => Promise<void>` | Cập nhật lại vị trí toolbar floating (dùng khi trigger element di chuyển). |
|
|
438
|
+
| `quill` | `() => Quill2x \| undefined` | Trả về instance Quill gốc để truy cập API Quill trực tiếp. |
|
|
439
|
+
|
|
440
|
+
## Types & Interfaces
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import {
|
|
444
|
+
IQuill2xCustomConfig,
|
|
445
|
+
IQuill2xToolbarConfig,
|
|
446
|
+
IQuill2xUploadImageConfig,
|
|
447
|
+
IQuill2xFunctionControlEvent,
|
|
448
|
+
IQuill2xLink,
|
|
449
|
+
IQuill2xBlotRegister,
|
|
450
|
+
IQuill2xSelectionChange,
|
|
451
|
+
IQuill2xTextChange,
|
|
452
|
+
QUILL2X_TYPE_MODE_BAR_CONFIG_OPTION,
|
|
453
|
+
} from '@libs-ui/components-inputs-quill2x';
|
|
454
|
+
import { WritableSignal } from '@angular/core';
|
|
455
|
+
|
|
456
|
+
// Loại toolbar
|
|
457
|
+
type QUILL2X_TYPE_MODE_BAR_CONFIG_OPTION = 'all' | 'default' | 'basic' | 'custom';
|
|
458
|
+
|
|
459
|
+
// Cấu hình tổng thể editor
|
|
460
|
+
interface IQuill2xCustomConfig {
|
|
461
|
+
classContainer?: WritableSignal<string>;
|
|
93
462
|
toolbar?: WritableSignal<{
|
|
94
|
-
type: WritableSignal<
|
|
463
|
+
type: WritableSignal<QUILL2X_TYPE_MODE_BAR_CONFIG_OPTION>;
|
|
95
464
|
positionFixed?: WritableSignal<{
|
|
96
465
|
event: WritableSignal<'click' | 'mouseenter'>;
|
|
97
466
|
elementShow: WritableSignal<HTMLElement>;
|
|
98
|
-
|
|
467
|
+
position?: WritableSignal<'top' | 'bottom'>;
|
|
468
|
+
align?: WritableSignal<'left' | 'center' | 'right'>;
|
|
469
|
+
zIndex: WritableSignal<number>;
|
|
470
|
+
width: WritableSignal<number>;
|
|
471
|
+
gapVertical?: WritableSignal<number>;
|
|
472
|
+
gapHorizontal?: WritableSignal<number>;
|
|
99
473
|
}>;
|
|
100
|
-
|
|
474
|
+
styles?: WritableSignal<Record<string, unknown>>;
|
|
475
|
+
options?: WritableSignal<Array<IQuill2xToolbarConfig>>;
|
|
476
|
+
classCustomContainerToolbar?: WritableSignal<string>;
|
|
477
|
+
lessWidthToolbarRecallCalculator?: WritableSignal<number>;
|
|
478
|
+
}>;
|
|
479
|
+
editor?: WritableSignal<{
|
|
480
|
+
classCustomContainerEditor?: WritableSignal<string>;
|
|
101
481
|
}>;
|
|
102
|
-
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Cấu hình từng nút trên toolbar
|
|
485
|
+
interface IQuill2xToolbarConfig {
|
|
486
|
+
type: string;
|
|
487
|
+
mode?: Array<QUILL2X_TYPE_MODE_BAR_CONFIG_OPTION>;
|
|
488
|
+
width: number;
|
|
489
|
+
display?: boolean;
|
|
490
|
+
classInclude?: string;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Cấu hình upload ảnh
|
|
494
|
+
interface IQuill2xUploadImageConfig {
|
|
495
|
+
modeCustom: boolean; // true = mở popup tùy chỉnh thay vì input file
|
|
496
|
+
showIcon?: boolean; // Hiển thị nút ảnh trên toolbar
|
|
497
|
+
zIndex?: number;
|
|
498
|
+
maxImageSize?: number; // Giới hạn kích thước file (bytes)
|
|
499
|
+
onlyAcceptImageHttpsLink?: boolean; // Chỉ chấp nhận link https
|
|
500
|
+
functionUploadImage?: (files: Array<File>) => Promise<Array<string | ArrayBuffer>>;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// API điều khiển editor từ bên ngoài
|
|
504
|
+
interface IQuill2xFunctionControlEvent {
|
|
505
|
+
checkIsValid: () => Promise<boolean>;
|
|
506
|
+
refreshItemValue: () => void;
|
|
507
|
+
setContent: (content: string) => Promise<void>;
|
|
508
|
+
insertText: (value: string, index?: number, focusLt?: boolean) => Promise<void>;
|
|
509
|
+
insertLink: (value: string, url: string, index?: number) => Promise<void>;
|
|
510
|
+
insertImage: (content: string, index?: number) => Promise<void>;
|
|
511
|
+
setFontSize: (size: number) => Promise<Delta | undefined>;
|
|
512
|
+
setColor: (color: string) => Promise<Delta | undefined>;
|
|
513
|
+
setBackground: (color: string) => Promise<Delta | undefined>;
|
|
514
|
+
setMessageError: (message: string) => Promise<void>;
|
|
515
|
+
quill: () => Quill2x | undefined;
|
|
516
|
+
reCalculatorToolbar: () => Promise<void>;
|
|
517
|
+
updatePositionToolbar: () => Promise<void>;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Thông tin link khi click vào hyperlink trong editor
|
|
521
|
+
interface IQuill2xLink {
|
|
522
|
+
title: string;
|
|
523
|
+
url: string;
|
|
524
|
+
range: { index: number; length: number };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Đăng ký custom Quill blot
|
|
528
|
+
interface IQuill2xBlotRegister {
|
|
529
|
+
component: any; // Class Quill blot
|
|
530
|
+
className: string; // CSS class của blot
|
|
531
|
+
style: string; // Inline styles dạng 'key:value;key2:value2'
|
|
532
|
+
ignoreDelete?: boolean; // true = không xóa blot khi nhấn Backspace/Delete
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Dữ liệu emit từ outSelectionChange
|
|
536
|
+
interface IQuill2xSelectionChange {
|
|
537
|
+
quill: Quill2x;
|
|
538
|
+
range: Range;
|
|
539
|
+
oldRange: Range;
|
|
540
|
+
source: EmitterSource;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Dữ liệu emit từ outTextChange
|
|
544
|
+
interface IQuill2xTextChange {
|
|
545
|
+
quill: Quill2x;
|
|
546
|
+
delta: Delta;
|
|
103
547
|
}
|
|
104
548
|
```
|
|
105
549
|
|
|
106
|
-
##
|
|
550
|
+
## Utility Functions
|
|
107
551
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
552
|
+
```typescript
|
|
553
|
+
import {
|
|
554
|
+
isEmptyQuill2x,
|
|
555
|
+
convertStandardList,
|
|
556
|
+
convertStandardListToQuill2x,
|
|
557
|
+
getHTMLFromDeltaOfQuill2x,
|
|
558
|
+
getDeltaOfQuill2xFromHTML,
|
|
559
|
+
} from '@libs-ui/components-inputs-quill2x';
|
|
560
|
+
|
|
561
|
+
// Kiểm tra editor có nội dung hay không
|
|
562
|
+
const isEmpty = isEmptyQuill2x(quillInstance);
|
|
563
|
+
|
|
564
|
+
// Chuẩn hóa HTML list từ định dạng nội bộ sang HTML chuẩn (để lưu)
|
|
565
|
+
const standardHtml = convertStandardList(rawHtml);
|
|
566
|
+
|
|
567
|
+
// Chuẩn hóa HTML list từ HTML chuẩn sang định dạng Quill (khi load vào editor)
|
|
568
|
+
const quillHtml = convertStandardListToQuill2x(savedHtml);
|
|
569
|
+
|
|
570
|
+
// Lấy HTML từ Quill Delta
|
|
571
|
+
const html = getHTMLFromDeltaOfQuill2x(delta);
|
|
572
|
+
|
|
573
|
+
// Lấy Quill Delta từ HTML string
|
|
574
|
+
const delta = getDeltaOfQuill2xFromHTML(html);
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
## Lưu ý quan trọng
|
|
578
|
+
|
|
579
|
+
⚠️ **item là model bắt buộc**: Phải dùng `[(item)]` (two-way binding). Không truyền object thuần bất biến vì component sẽ ghi trực tiếp vào `item[fieldBind]` khi nội dung thay đổi.
|
|
111
580
|
|
|
112
|
-
|
|
581
|
+
⚠️ **IQuill2xCustomConfig dùng WritableSignal**: Toàn bộ thuộc tính trong `IQuill2xCustomConfig` là `WritableSignal`. Phải khởi tạo với `signal(...)` thay vì truyền giá trị thường. Xem ví dụ mục 2 và 5 ở trên.
|
|
582
|
+
|
|
583
|
+
⚠️ **position fixed cần ngAfterViewInit**: Khi dùng `positionFixed.elementShow`, element DOM phải tồn tại trước. Khởi tạo config trong `ngAfterViewInit` sau khi `@ViewChild` đã sẵn sàng.
|
|
584
|
+
|
|
585
|
+
⚠️ **XSS Security**: Nội dung được tự động lọc qua `xssFilter` khi set content qua `setContent()`. Không set content trực tiếp qua Quill API (`quill().clipboard.dangerouslyPasteHTML`) mà không sanitize.
|
|
586
|
+
|
|
587
|
+
⚠️ **Peer dependency quill2x**: Package `quill2x` phải được cài đặt. Component không hoạt động với `quill` phiên bản khác.
|
|
588
|
+
|
|
589
|
+
⚠️ **Tính toán toolbar**: Toolbar tự tính toán độ rộng sau khi render. Nếu component cha có animation hoặc thay đổi kích thước, gọi `editorCtrl?.reCalculatorToolbar()` sau khi animation kết thúc để toolbar hiển thị đúng.
|
|
590
|
+
|
|
591
|
+
⚠️ **convertStandardList khi lưu**: HTML được lưu vào `item[fieldBind]` đã qua `convertStandardList()` để chuẩn hóa định dạng danh sách. Khi hiển thị lại nội dung, dùng component `@libs-ui/components-inputs-quill2x-preview` hoặc apply CSS Quill Snow theme.
|
|
592
|
+
|
|
593
|
+
## Demo
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
npx nx serve core-ui
|
|
597
|
+
```
|
|
113
598
|
|
|
114
|
-
|
|
599
|
+
Truy cập: http://localhost:4500/inputs/quill2x
|