@sd-angular/core 19.0.0-beta.3 → 19.0.0-beta.5
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/components/document-builder/src/document-builder.component.d.ts +7 -6
- package/components/document-builder/src/document-builder.config.d.ts +21 -0
- package/components/document-builder/src/document-builder.model.d.ts +1 -0
- package/components/document-builder/src/document-builder.utils.d.ts +10 -0
- package/components/document-builder/src/plugins/heading/heading.plugin.d.ts +4 -0
- package/components/document-builder/src/plugins/{image-upload.plugin.d.ts → image-upload/image-upload.plugin.d.ts} +0 -4
- package/components/document-builder/src/plugins/index.d.ts +6 -5
- package/components/table/src/models/table-item.model.d.ts +2 -1
- package/fesm2022/sd-angular-core-components-document-builder.mjs +541 -422
- package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-table.mjs +138 -72
- package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-workflow.mjs +23 -23
- package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs +24 -2
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date.mjs +15 -3
- package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-datetime.mjs +17 -3
- package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input-number.mjs +18 -3
- package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input.mjs +20 -6
- package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-radio.mjs +17 -2
- package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-select.mjs +15 -2
- package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-textarea.mjs +21 -2
- package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-auth.mjs +5 -5
- package/fesm2022/sd-angular-core-modules-auth.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
- package/fesm2022/sd-angular-core-pipes.mjs +21 -1
- package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-confirm.mjs +1 -1
- package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
- package/fesm2022/sd-angular-core-utilities-extensions.mjs +66 -1
- package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
- package/fesm2022/sd-angular-core-utilities-models.mjs +2 -2
- package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
- package/forms/autocomplete/src/autocomplete.component.d.ts +5 -1
- package/forms/date/src/date.component.d.ts +4 -1
- package/forms/datetime/src/datetime.component.d.ts +4 -1
- package/forms/input/src/input.component.d.ts +6 -4
- package/forms/input-number/src/input-number.component.d.ts +4 -1
- package/forms/radio/src/radio.component.d.ts +5 -1
- package/forms/select/src/select.component.d.ts +5 -1
- package/forms/textarea/src/textarea.component.d.ts +3 -1
- package/modules/auth/guards/portal.guard.d.ts +3 -3
- package/package.json +56 -56
- package/pipes/index.d.ts +1 -0
- package/pipes/src/empty.pipe.d.ts +7 -0
- package/utilities/extensions/index.d.ts +1 -0
- package/utilities/extensions/src/color.extension.d.ts +20 -0
- package/utilities/models/src/pattern.model.d.ts +2 -2
- /package/components/document-builder/src/plugins/{comment.plugin.d.ts → comment/comment.plugin.d.ts} +0 -0
- /package/components/document-builder/src/plugins/{page-orientation.plugin.d.ts → page-orientation/page-orientation.plugin.d.ts} +0 -0
- /package/components/document-builder/src/plugins/{table-fit.plugin.d.ts → table-fit/table-fit.plugin.d.ts} +0 -0
- /package/components/document-builder/src/plugins/{variable.plugin.d.ts → variable/variable.plugin.d.ts} +0 -0
|
@@ -4,9 +4,9 @@ import { CommonModule } from '@angular/common';
|
|
|
4
4
|
import * as i1 from '@ckeditor/ckeditor5-angular';
|
|
5
5
|
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
|
|
6
6
|
import { Plugin, ButtonView, ClassicEditor, Essentials, Paragraph, Bold, Italic, Underline, FontSize, FontColor, FontBackgroundColor, Alignment, Widget, toWidget, GeneralHtmlSupport, FontFamily, Heading, List, Table, TableToolbar, TableProperties, TableCellProperties, TableColumnResize, PasteFromOffice, PageBreak, Undo, Subscript, Superscript, Image, ImageUpload, ImageToolbar, ImageCaption, ImageResize, ImageStyle } from 'ckeditor5';
|
|
7
|
-
import { SdResolveMaybeAsync } from '@sd-angular/core/utilities';
|
|
8
|
-
import { SdUtilities } from '@sd-angular/core/utilities/extensions';
|
|
9
7
|
import { Subscription, Subject, throttleTime } from 'rxjs';
|
|
8
|
+
import { SdResolveMaybeAsync, hslToHex, rgbToHex, SdUtilities } from '@sd-angular/core/utilities';
|
|
9
|
+
import { v4 } from 'uuid';
|
|
10
10
|
|
|
11
11
|
class PageNumberPlugin extends Plugin {
|
|
12
12
|
init() {
|
|
@@ -130,100 +130,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImpo
|
|
|
130
130
|
type: Output
|
|
131
131
|
}] } });
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
const ICON_PORTRAIT = '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M14 2H6c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6V4h8v12z"/></svg>';
|
|
135
|
-
// Icon khổ ngang (Mới)
|
|
136
|
-
const ICON_LANDSCAPE = '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M18 4H2C.9 4 0 4.9 0 6v8c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 10H2V6h16v8z"/></svg>';
|
|
137
|
-
class PageOrientationPlugin extends Plugin {
|
|
138
|
-
static pluginName = 'PageOrientationPlugin';
|
|
139
|
-
_currentOrientation = 'PORTRAIT';
|
|
140
|
-
orientationChangeEmitter;
|
|
141
|
-
buttonView;
|
|
133
|
+
class HeadingPlugin extends Plugin {
|
|
142
134
|
init() {
|
|
143
135
|
const editor = this.editor;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
view.set({
|
|
150
|
-
// label: 'Xoay giấy (A4)',
|
|
151
|
-
icon: ICON_PORTRAIT,
|
|
152
|
-
// tooltip: true,
|
|
153
|
-
// withText: true,
|
|
154
|
-
class: 'btn-orientation', // Class để style nếu cần
|
|
155
|
-
});
|
|
156
|
-
// Xử lý khi bấm nút
|
|
157
|
-
view.on('execute', () => {
|
|
158
|
-
this.toggleOrientation();
|
|
159
|
-
});
|
|
160
|
-
return view;
|
|
136
|
+
editor.conversion.for('editingDowncast').markerToHighlight({
|
|
137
|
+
model: 'highlightMarker',
|
|
138
|
+
view: {
|
|
139
|
+
classes: 'ck-heading-highlight',
|
|
140
|
+
},
|
|
161
141
|
});
|
|
162
142
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Toggle between portrait and landscape orientation
|
|
165
|
-
*/
|
|
166
|
-
toggleOrientation() {
|
|
167
|
-
const newOrientation = this._currentOrientation === 'PORTRAIT' ? 'LANDSCAPE' : 'PORTRAIT';
|
|
168
|
-
this.setOrientation(newOrientation);
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Set orientation programmatically
|
|
172
|
-
*/
|
|
173
|
-
setOrientation(orientation) {
|
|
174
|
-
const editor = this.editor;
|
|
175
|
-
const editingView = editor.editing.view;
|
|
176
|
-
const rootElement = editingView.document.getRoot();
|
|
177
|
-
editor.editing.view.change(writer => {
|
|
178
|
-
if (orientation === 'LANDSCAPE') {
|
|
179
|
-
writer.addClass('landscape', rootElement);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
writer.removeClass('landscape', rootElement);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
// Update button icon
|
|
186
|
-
if (this.buttonView) {
|
|
187
|
-
this.buttonView.icon = orientation === 'LANDSCAPE' ? ICON_LANDSCAPE : ICON_PORTRAIT;
|
|
188
|
-
}
|
|
189
|
-
this._currentOrientation = orientation;
|
|
190
|
-
this.orientationChangeEmitter?.(orientation);
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Get current orientation
|
|
194
|
-
*/
|
|
195
|
-
getOrientation() {
|
|
196
|
-
return this._currentOrientation;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Register callback for orientation changes
|
|
200
|
-
*/
|
|
201
|
-
onOrientationChange(callback) {
|
|
202
|
-
this.orientationChangeEmitter = callback;
|
|
203
|
-
}
|
|
204
143
|
}
|
|
205
144
|
|
|
206
145
|
class CommentPlugin extends Plugin {
|
|
207
146
|
init() {
|
|
208
147
|
const editor = this.editor;
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
148
|
+
// 1. MODEL MARKER -> VIEW CSS
|
|
149
|
+
editor.conversion.for('downcast').markerToHighlight({
|
|
150
|
+
model: 'comment',
|
|
151
|
+
view: data => {
|
|
152
|
+
return {
|
|
153
|
+
classes: 'ck-comment-marker',
|
|
154
|
+
attributes: {
|
|
155
|
+
'data-comment-id': data.markerName,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
215
158
|
},
|
|
216
159
|
});
|
|
217
|
-
//
|
|
160
|
+
// 2. ĐĂNG KÝ UI COMPONENT: 'addCommentBtn'
|
|
218
161
|
editor.ui.componentFactory.add('addCommentBtn', locale => {
|
|
219
162
|
const view = new ButtonView(locale);
|
|
220
163
|
// Lấy config từ Angular
|
|
221
164
|
const config = editor.config;
|
|
222
165
|
const getOption = config.get('getOption');
|
|
223
166
|
const option = getOption?.();
|
|
224
|
-
//
|
|
167
|
+
// Ẩn button nếu không có onAddComment
|
|
225
168
|
if (!option?.onAddComment) {
|
|
226
|
-
// Trả về button rỗng hoặc null để không hiển thị
|
|
227
169
|
view.set({
|
|
228
170
|
label: '',
|
|
229
171
|
isVisible: false,
|
|
@@ -232,19 +174,18 @@ class CommentPlugin extends Plugin {
|
|
|
232
174
|
}
|
|
233
175
|
view.set({
|
|
234
176
|
label: 'Thêm bình luận',
|
|
235
|
-
// Icon SVG comment
|
|
236
177
|
icon: '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M18 13v6l-4-4H4a2 2 0 01-2-2V4a2 2 0 012-2h14a2 2 0 012 2v9zM5 7h10v2H5V7zm0 4h10v2H5v-2z"/></svg>',
|
|
237
178
|
tooltip: true,
|
|
238
|
-
isEnabled: false,
|
|
179
|
+
isEnabled: false,
|
|
239
180
|
});
|
|
240
|
-
//
|
|
181
|
+
// Logic Enable/Disable: Dựa theo Selection
|
|
241
182
|
const selection = editor.model.document.selection;
|
|
242
183
|
// Lắng nghe sự kiện change selection để bật/tắt nút
|
|
243
184
|
this.listenTo(selection, 'change', () => {
|
|
244
185
|
// Enable khi có bôi đen text (không phải collapsed)
|
|
245
186
|
view.isEnabled = !selection.isCollapsed;
|
|
246
187
|
});
|
|
247
|
-
//
|
|
188
|
+
// Logic Execute: Khi bấm nút
|
|
248
189
|
this.listenTo(view, 'execute', () => {
|
|
249
190
|
const range = selection.getFirstRange();
|
|
250
191
|
if (!range || !option?.onAddComment)
|
|
@@ -256,13 +197,36 @@ class CommentPlugin extends Plugin {
|
|
|
256
197
|
selectedText += item.data;
|
|
257
198
|
}
|
|
258
199
|
}
|
|
259
|
-
//
|
|
200
|
+
// BẮN EVENT RA NGOÀI - KHÔNG TỰ ADD MARKER
|
|
260
201
|
// Angular component sẽ xử lý logic (mở modal, validation, etc.)
|
|
261
202
|
// và gọi lại hàm addComment() nếu cần
|
|
262
203
|
option.onAddComment(range);
|
|
263
204
|
});
|
|
264
205
|
return view;
|
|
265
206
|
});
|
|
207
|
+
// 3. Xử lý sự kiện Copy (Clipboard Output)
|
|
208
|
+
this.listenTo(editor.editing.view.document, 'clipboardOutput', (evt, data) => {
|
|
209
|
+
const content = data.content;
|
|
210
|
+
editor.editing.view.change(writer => {
|
|
211
|
+
// Tạo range bao quanh toàn bộ nội dung clipboard
|
|
212
|
+
const range = writer.createRangeIn(content);
|
|
213
|
+
// Mảng chứa các item cần xử lý
|
|
214
|
+
const itemsToClean = [];
|
|
215
|
+
// 1. Duyệt qua để tìm các thẻ có class ck-comment-marker
|
|
216
|
+
for (const item of range.getItems()) {
|
|
217
|
+
if (item.is('element') && item.hasClass('ck-comment-marker')) {
|
|
218
|
+
itemsToClean.push(item);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// 2. Thực hiện xóa Class và Attribute
|
|
222
|
+
for (const item of itemsToClean) {
|
|
223
|
+
// Xóa class 'ck-comment-marker'
|
|
224
|
+
writer.removeClass('ck-comment-marker', item);
|
|
225
|
+
// Xóa thuộc tính 'data-comment-id'
|
|
226
|
+
writer.removeAttribute('data-comment-id', item);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
266
230
|
}
|
|
267
231
|
}
|
|
268
232
|
|
|
@@ -282,13 +246,14 @@ class VariablePlugin extends Plugin {
|
|
|
282
246
|
allowWhere: '$text',
|
|
283
247
|
isInline: true,
|
|
284
248
|
isObject: true,
|
|
285
|
-
allowAttributes: ['id', 'value', 'display', 'data'],
|
|
249
|
+
allowAttributes: ['id', 'uuid', 'value', 'display', 'data'],
|
|
286
250
|
});
|
|
287
251
|
// 2. Conversion: Model -> View (Hiển thị ra HTML)
|
|
288
252
|
conversion.for('downcast').elementToElement({
|
|
289
253
|
model: 'variable',
|
|
290
254
|
view: (modelItem, { writer: viewWriter }) => {
|
|
291
255
|
const id = modelItem.getAttribute('id');
|
|
256
|
+
const uuid = modelItem.getAttribute('uuid');
|
|
292
257
|
const display = modelItem.getAttribute('display');
|
|
293
258
|
const value = modelItem.getAttribute('value');
|
|
294
259
|
// Xử lý data (Object -> String)
|
|
@@ -297,6 +262,7 @@ class VariablePlugin extends Plugin {
|
|
|
297
262
|
const widgetElement = viewWriter.createContainerElement('span', {
|
|
298
263
|
class: 'variable-widget',
|
|
299
264
|
'data-id': id,
|
|
265
|
+
'data-uuid': uuid,
|
|
300
266
|
'data-value': value,
|
|
301
267
|
'data-display': display,
|
|
302
268
|
'data-json': dataJson,
|
|
@@ -323,6 +289,7 @@ class VariablePlugin extends Plugin {
|
|
|
323
289
|
}
|
|
324
290
|
return modelWriter.createElement('variable', {
|
|
325
291
|
id: viewElement.getAttribute('data-id'),
|
|
292
|
+
uuid: viewElement.getAttribute('data-uuid'),
|
|
326
293
|
value: viewElement.getAttribute('data-value'),
|
|
327
294
|
display: viewElement.getAttribute('data-display'),
|
|
328
295
|
data: parsedData, // Lưu vào model dưới dạng Object gốc
|
|
@@ -335,7 +302,6 @@ class VariablePlugin extends Plugin {
|
|
|
335
302
|
const jsonData = dataTransfer.getData('ck-variable');
|
|
336
303
|
if (!jsonData)
|
|
337
304
|
return;
|
|
338
|
-
// Ngăn trình duyệt xử lý mặc định
|
|
339
305
|
evt.stop();
|
|
340
306
|
let variable = JSON.parse(jsonData);
|
|
341
307
|
const config = editor.config;
|
|
@@ -363,17 +329,18 @@ class VariablePlugin extends Plugin {
|
|
|
363
329
|
const viewRange = data.dropRange;
|
|
364
330
|
const modelRange = editor.editing.mapper.toModelRange(viewRange);
|
|
365
331
|
editor.model.change(writer => {
|
|
366
|
-
//
|
|
332
|
+
// 4.1. Chèn biến
|
|
367
333
|
const variableElem = writer.createElement('variable', {
|
|
368
334
|
id: variable.id,
|
|
335
|
+
uuid: v4(),
|
|
369
336
|
value: variable.value,
|
|
370
337
|
display: variable.display,
|
|
371
338
|
data: variable.data,
|
|
372
339
|
});
|
|
373
340
|
editor.model.insertContent(variableElem, modelRange);
|
|
374
|
-
//
|
|
341
|
+
// 4.2. Đặt con trỏ ra sau biến
|
|
375
342
|
writer.setSelection(variableElem, 'after');
|
|
376
|
-
//
|
|
343
|
+
// 4.3. Xóa sạch các maker drop
|
|
377
344
|
// Thay vì chỉ xóa 'drop-target', ta duyệt tìm tất cả marker có tên bắt đầu bằng 'drop-target'
|
|
378
345
|
// Vì đôi khi CKEditor tạo ra các biến thể khác nhau
|
|
379
346
|
for (const marker of editor.model.markers) {
|
|
@@ -382,11 +349,10 @@ class VariablePlugin extends Plugin {
|
|
|
382
349
|
}
|
|
383
350
|
}
|
|
384
351
|
});
|
|
385
|
-
//
|
|
352
|
+
// 4.4. Focus lại vào editor để xóa các artifact của việc kéo thả
|
|
386
353
|
editor.editing.view.focus();
|
|
387
|
-
//
|
|
388
|
-
// Đôi khi View chưa kịp render lại, ta ép xóa class thủ công trên root
|
|
389
|
-
// (Thường bước C ở trên là đủ, nhưng đây là chốt chặn cuối cùng bằng JS)
|
|
354
|
+
// 4.5. Xóa class rác trên view
|
|
355
|
+
// Đôi khi View chưa kịp render lại, ta ép xóa class thủ công trên root
|
|
390
356
|
const viewRoot = editor.editing.view.document.getRoot();
|
|
391
357
|
if (viewRoot) {
|
|
392
358
|
editor.editing.view.change(viewWriter => {
|
|
@@ -483,6 +449,35 @@ class VariablePlugin extends Plugin {
|
|
|
483
449
|
}
|
|
484
450
|
}
|
|
485
451
|
}, { priority: 'highest' });
|
|
452
|
+
// 8. Xử lý sự kiện Copy (Clipboard Output)
|
|
453
|
+
// Khi copy, thay thế variable bằng text
|
|
454
|
+
this.listenTo(editor.editing.view.document, 'clipboardOutput', (evt, data) => {
|
|
455
|
+
const content = data.content;
|
|
456
|
+
editor.editing.view.change(writer => {
|
|
457
|
+
// Tạo range bao quanh toàn bộ nội dung clipboard
|
|
458
|
+
const range = writer.createRangeIn(content);
|
|
459
|
+
const itemsToReplace = [];
|
|
460
|
+
// Duyệt qua tất cả các phần tử trong clipboard để tìm variable
|
|
461
|
+
for (const item of range.getItems()) {
|
|
462
|
+
// Kiểm tra đúng là thẻ span và có class variable-widget
|
|
463
|
+
if (item.is('element', 'span') && item.hasClass('variable-widget')) {
|
|
464
|
+
itemsToReplace.push(item);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// Thay thế variable bằng text
|
|
468
|
+
for (const item of itemsToReplace) {
|
|
469
|
+
const displayText = item.getAttribute('data-display');
|
|
470
|
+
if (displayText) {
|
|
471
|
+
// Tạo một node text thuần túy
|
|
472
|
+
const textNode = writer.createText(`{{${displayText}}}`);
|
|
473
|
+
// Chèn text node vào ngay trước widget cũ
|
|
474
|
+
writer.insert(writer.createPositionBefore(item), textNode);
|
|
475
|
+
// Xóa widget cũ đi
|
|
476
|
+
writer.remove(item);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
});
|
|
486
481
|
}
|
|
487
482
|
#isSdDocumentBuilderVariableResult = (obj) => {
|
|
488
483
|
return (obj !== null &&
|
|
@@ -545,25 +540,17 @@ class TableFitPlugin extends Plugin {
|
|
|
545
540
|
}
|
|
546
541
|
}
|
|
547
542
|
|
|
548
|
-
/**
|
|
549
|
-
* Custom base64 upload adapter plugin for CKEditor 5.
|
|
550
|
-
* Converts uploaded images to base64 data URLs instead of uploading to a server.
|
|
551
|
-
*/
|
|
552
543
|
class ImageUploadPlugin extends Plugin {
|
|
553
544
|
static get pluginName() {
|
|
554
545
|
return 'ImageUploadPlugin';
|
|
555
546
|
}
|
|
556
547
|
init() {
|
|
557
548
|
const editor = this.editor;
|
|
558
|
-
// Register the custom upload adapter
|
|
559
549
|
editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
|
|
560
550
|
return new Base64UploadAdapter(loader);
|
|
561
551
|
};
|
|
562
552
|
}
|
|
563
553
|
}
|
|
564
|
-
/**
|
|
565
|
-
* Custom upload adapter that converts images to base64 data URLs.
|
|
566
|
-
*/
|
|
567
554
|
class Base64UploadAdapter {
|
|
568
555
|
loader;
|
|
569
556
|
constructor(loader) {
|
|
@@ -597,16 +584,288 @@ class Base64UploadAdapter {
|
|
|
597
584
|
}
|
|
598
585
|
}
|
|
599
586
|
|
|
587
|
+
// Icon khổ dọc (Mặc định cũ)
|
|
588
|
+
const ICON_PORTRAIT = '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M14 2H6c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6V4h8v12z"/></svg>';
|
|
589
|
+
// Icon khổ ngang (Mới)
|
|
590
|
+
const ICON_LANDSCAPE = '<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M18 4H2C.9 4 0 4.9 0 6v8c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 10H2V6h16v8z"/></svg>';
|
|
591
|
+
class PageOrientationPlugin extends Plugin {
|
|
592
|
+
static pluginName = 'PageOrientationPlugin';
|
|
593
|
+
_currentOrientation = 'PORTRAIT';
|
|
594
|
+
orientationChangeEmitter;
|
|
595
|
+
buttonView;
|
|
596
|
+
init() {
|
|
597
|
+
const editor = this.editor;
|
|
598
|
+
const componentFactory = editor.ui.componentFactory;
|
|
599
|
+
// Đăng ký nút tên là 'pageOrientation'
|
|
600
|
+
componentFactory.add('pageOrientation', locale => {
|
|
601
|
+
const view = new ButtonView(locale);
|
|
602
|
+
this.buttonView = view;
|
|
603
|
+
view.set({
|
|
604
|
+
// label: 'Xoay giấy (A4)',
|
|
605
|
+
icon: ICON_PORTRAIT,
|
|
606
|
+
// tooltip: true,
|
|
607
|
+
// withText: true,
|
|
608
|
+
class: 'btn-orientation', // Class để style nếu cần
|
|
609
|
+
});
|
|
610
|
+
// Xử lý khi bấm nút
|
|
611
|
+
view.on('execute', () => {
|
|
612
|
+
this.toggleOrientation();
|
|
613
|
+
});
|
|
614
|
+
return view;
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Toggle between portrait and landscape orientation
|
|
619
|
+
*/
|
|
620
|
+
toggleOrientation() {
|
|
621
|
+
const newOrientation = this._currentOrientation === 'PORTRAIT' ? 'LANDSCAPE' : 'PORTRAIT';
|
|
622
|
+
this.setOrientation(newOrientation);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Set orientation programmatically
|
|
626
|
+
*/
|
|
627
|
+
setOrientation(orientation) {
|
|
628
|
+
const editor = this.editor;
|
|
629
|
+
const editingView = editor.editing.view;
|
|
630
|
+
const rootElement = editingView.document.getRoot();
|
|
631
|
+
editor.editing.view.change(writer => {
|
|
632
|
+
if (orientation === 'LANDSCAPE') {
|
|
633
|
+
writer.addClass('landscape', rootElement);
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
writer.removeClass('landscape', rootElement);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
// Update button icon
|
|
640
|
+
if (this.buttonView) {
|
|
641
|
+
this.buttonView.icon = orientation === 'LANDSCAPE' ? ICON_LANDSCAPE : ICON_PORTRAIT;
|
|
642
|
+
}
|
|
643
|
+
this._currentOrientation = orientation;
|
|
644
|
+
this.orientationChangeEmitter?.(orientation);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Get current orientation
|
|
648
|
+
*/
|
|
649
|
+
getOrientation() {
|
|
650
|
+
return this._currentOrientation;
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Register callback for orientation changes
|
|
654
|
+
*/
|
|
655
|
+
onOrientationChange(callback) {
|
|
656
|
+
this.orientationChangeEmitter = callback;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Cấu hình màu cho Document Builder
|
|
662
|
+
* Bảng màu tập trung và cấu hình cho việc lựa chọn màu nhất quán
|
|
663
|
+
*/
|
|
664
|
+
/**
|
|
665
|
+
* Trả về bảng màu chung được sử dụng trong tất cả tính năng của document builder
|
|
666
|
+
* @returns Mảng các tùy chọn màu được định sẵn với giá trị hex và label
|
|
667
|
+
*/
|
|
668
|
+
function getPresetColors() {
|
|
669
|
+
return [
|
|
670
|
+
{ color: '#000000', label: 'Black' },
|
|
671
|
+
{ color: '#4D4D4D', label: 'Dim grey' },
|
|
672
|
+
{ color: '#999999', label: 'Grey' },
|
|
673
|
+
{ color: '#E6E6E6', label: 'Light grey' },
|
|
674
|
+
{ color: '#FFFFFF', label: 'White' },
|
|
675
|
+
{ color: '#E64D4D', label: 'Red' },
|
|
676
|
+
{ color: '#E6994D', label: 'Orange' },
|
|
677
|
+
{ color: '#E6E64D', label: 'Yellow' },
|
|
678
|
+
{ color: '#99E64D', label: 'Light green' },
|
|
679
|
+
{ color: '#4DE64D', label: 'Green' },
|
|
680
|
+
{ color: '#4DE699', label: 'Aquamarine' },
|
|
681
|
+
{ color: '#4DE6E6', label: 'Turquoise' },
|
|
682
|
+
{ color: '#4D99E6', label: 'Light blue' },
|
|
683
|
+
{ color: '#4D4DE6', label: 'Blue' },
|
|
684
|
+
{ color: '#994DE6', label: 'Purple' },
|
|
685
|
+
];
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Trả về cấu hình bộ chọn màu với định dạng hex
|
|
689
|
+
* @returns Đối tượng cấu hình bộ chọn màu
|
|
690
|
+
*/
|
|
691
|
+
function getColorPickerConfig() {
|
|
692
|
+
return {
|
|
693
|
+
format: 'hex',
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Trả về cấu hình kích thước font cho document builder
|
|
698
|
+
* @returns Mảng các tùy chọn kích thước font được định sẵn
|
|
699
|
+
*/
|
|
700
|
+
function getFontSizeOptions() {
|
|
701
|
+
return [
|
|
702
|
+
{
|
|
703
|
+
title: '9',
|
|
704
|
+
model: '9pt',
|
|
705
|
+
view: {
|
|
706
|
+
name: 'span',
|
|
707
|
+
styles: { 'font-size': '9pt' },
|
|
708
|
+
priority: 7,
|
|
709
|
+
},
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
title: '10',
|
|
713
|
+
model: '10pt',
|
|
714
|
+
view: {
|
|
715
|
+
name: 'span',
|
|
716
|
+
styles: { 'font-size': '10pt' },
|
|
717
|
+
priority: 7,
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
title: '11',
|
|
722
|
+
model: '11pt',
|
|
723
|
+
view: {
|
|
724
|
+
name: 'span',
|
|
725
|
+
styles: { 'font-size': '11pt' },
|
|
726
|
+
priority: 7,
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
title: '12',
|
|
731
|
+
model: '12pt',
|
|
732
|
+
view: {
|
|
733
|
+
name: 'span',
|
|
734
|
+
styles: { 'font-size': '12pt' },
|
|
735
|
+
priority: 7,
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
title: '13',
|
|
740
|
+
model: '13pt',
|
|
741
|
+
view: {
|
|
742
|
+
name: 'span',
|
|
743
|
+
styles: { 'font-size': '13pt' },
|
|
744
|
+
priority: 7,
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
title: '14',
|
|
749
|
+
model: '14pt',
|
|
750
|
+
view: {
|
|
751
|
+
name: 'span',
|
|
752
|
+
styles: { 'font-size': '14pt' },
|
|
753
|
+
priority: 7,
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
title: '16',
|
|
758
|
+
model: '16pt',
|
|
759
|
+
view: {
|
|
760
|
+
name: 'span',
|
|
761
|
+
styles: { 'font-size': '16pt' },
|
|
762
|
+
priority: 7,
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
title: '18',
|
|
767
|
+
model: '18pt',
|
|
768
|
+
view: {
|
|
769
|
+
name: 'span',
|
|
770
|
+
styles: { 'font-size': '18pt' },
|
|
771
|
+
priority: 7,
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
title: '20',
|
|
776
|
+
model: '20pt',
|
|
777
|
+
view: {
|
|
778
|
+
name: 'span',
|
|
779
|
+
styles: { 'font-size': '20pt' },
|
|
780
|
+
priority: 7,
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
title: '24',
|
|
785
|
+
model: '24pt',
|
|
786
|
+
view: {
|
|
787
|
+
name: 'span',
|
|
788
|
+
styles: { 'font-size': '24pt' },
|
|
789
|
+
priority: 7,
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
];
|
|
793
|
+
}
|
|
794
|
+
function getHeadingOptions() {
|
|
795
|
+
return [
|
|
796
|
+
{ model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
|
|
797
|
+
{ model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
|
|
798
|
+
{ model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
|
|
799
|
+
{ model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' },
|
|
800
|
+
];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Document Builder Utilities
|
|
805
|
+
* Các hàm tiện ích cho document builder
|
|
806
|
+
*/
|
|
807
|
+
/**
|
|
808
|
+
* Chuẩn hóa nội dung bằng cách chuyển đổi tất cả màu HSL và RGB sang hex
|
|
809
|
+
* @param content - Nội dung HTML cần chuẩn hóa
|
|
810
|
+
* @returns Nội dung đã được chuẩn hóa với màu hex
|
|
811
|
+
*/
|
|
812
|
+
function normalize(content) {
|
|
813
|
+
let normalized = content;
|
|
814
|
+
// Chuyển đổi HSL sang hex
|
|
815
|
+
const hslRegex = /hsl\(\s*(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?\s*\)/gi;
|
|
816
|
+
normalized = normalized.replace(hslRegex, (match, h, s, l) => {
|
|
817
|
+
try {
|
|
818
|
+
const hue = parseInt(h, 10);
|
|
819
|
+
const saturation = parseInt(s, 10);
|
|
820
|
+
const lightness = parseInt(l, 10);
|
|
821
|
+
// Kiểm tra giá trị hợp lệ
|
|
822
|
+
if (hue >= 0 && hue <= 360 && saturation >= 0 && saturation <= 100 && lightness >= 0 && lightness <= 100) {
|
|
823
|
+
return hslToHex(hue, saturation, lightness);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
catch (error) {
|
|
827
|
+
console.warn('Failed to convert HSL to hex:', error, match);
|
|
828
|
+
}
|
|
829
|
+
return match; // Giữ nguyên nếu không thể chuyển đổi
|
|
830
|
+
});
|
|
831
|
+
// Chuyển đổi RGB sang hex
|
|
832
|
+
const rgbRegex = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/gi;
|
|
833
|
+
normalized = normalized.replace(rgbRegex, (match, r, g, b) => {
|
|
834
|
+
try {
|
|
835
|
+
const red = parseInt(r, 10);
|
|
836
|
+
const green = parseInt(g, 10);
|
|
837
|
+
const blue = parseInt(b, 10);
|
|
838
|
+
if (red >= 0 && red <= 255 && green >= 0 && green <= 255 && blue >= 0 && blue <= 255) {
|
|
839
|
+
return rgbToHex(red, green, blue);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
catch (error) {
|
|
843
|
+
console.warn('Failed to convert RGB to hex:', error, match);
|
|
844
|
+
}
|
|
845
|
+
return match;
|
|
846
|
+
});
|
|
847
|
+
return normalized;
|
|
848
|
+
}
|
|
849
|
+
|
|
600
850
|
class SdDocumentBuilder {
|
|
601
|
-
#id = '1212';
|
|
602
851
|
option;
|
|
603
852
|
disabled = false;
|
|
604
853
|
set _disabled(val) {
|
|
605
854
|
this.disabled = val === '' || !!val;
|
|
606
855
|
this.#updateState();
|
|
607
856
|
}
|
|
857
|
+
contentChange = new EventEmitter(); // Emit HTML content
|
|
608
858
|
Editor = ClassicEditor;
|
|
609
859
|
#editor;
|
|
860
|
+
#id = '55b0afb0-288d-423c-98b3-5f9db286e16d';
|
|
861
|
+
#subscription = new Subscription();
|
|
862
|
+
#sharedColors = getPresetColors();
|
|
863
|
+
#headingOptions = getHeadingOptions();
|
|
864
|
+
#fontSizeOptions = getFontSizeOptions();
|
|
865
|
+
#colorPickerConfig = getColorPickerConfig();
|
|
866
|
+
#contentChangeSubject = new Subject();
|
|
867
|
+
#idTimeOutScrollHeading = null;
|
|
868
|
+
#headingElementsMap = new Map(); // Hash lưu trữ các heading
|
|
610
869
|
// Config
|
|
611
870
|
config = {
|
|
612
871
|
getOption: () => this.option,
|
|
@@ -643,11 +902,12 @@ class SdDocumentBuilder {
|
|
|
643
902
|
ImageResize,
|
|
644
903
|
ImageStyle,
|
|
645
904
|
// Custom Plugin
|
|
646
|
-
|
|
905
|
+
HeadingPlugin,
|
|
647
906
|
CommentPlugin,
|
|
648
907
|
VariablePlugin,
|
|
649
908
|
TableFitPlugin,
|
|
650
909
|
ImageUploadPlugin,
|
|
910
|
+
PageOrientationPlugin,
|
|
651
911
|
],
|
|
652
912
|
toolbar: {
|
|
653
913
|
items: [
|
|
@@ -685,119 +945,35 @@ class SdDocumentBuilder {
|
|
|
685
945
|
toolbar: ['toggleImageCaption', '|', 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side'],
|
|
686
946
|
},
|
|
687
947
|
fontSize: {
|
|
688
|
-
options:
|
|
689
|
-
// Định nghĩa từng size một cách tường minh
|
|
690
|
-
{
|
|
691
|
-
title: '9',
|
|
692
|
-
model: '9pt',
|
|
693
|
-
view: {
|
|
694
|
-
name: 'span',
|
|
695
|
-
styles: { 'font-size': '9pt' },
|
|
696
|
-
priority: 7,
|
|
697
|
-
},
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
title: '10',
|
|
701
|
-
model: '10pt',
|
|
702
|
-
view: {
|
|
703
|
-
name: 'span',
|
|
704
|
-
styles: { 'font-size': '10pt' },
|
|
705
|
-
priority: 7,
|
|
706
|
-
},
|
|
707
|
-
},
|
|
708
|
-
{
|
|
709
|
-
title: '11',
|
|
710
|
-
model: '11pt',
|
|
711
|
-
view: {
|
|
712
|
-
name: 'span',
|
|
713
|
-
styles: { 'font-size': '11pt' },
|
|
714
|
-
priority: 7,
|
|
715
|
-
},
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
title: '12',
|
|
719
|
-
model: '12pt',
|
|
720
|
-
view: {
|
|
721
|
-
name: 'span',
|
|
722
|
-
styles: { 'font-size': '12pt' },
|
|
723
|
-
priority: 7,
|
|
724
|
-
},
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
title: '13',
|
|
728
|
-
model: '13pt',
|
|
729
|
-
view: {
|
|
730
|
-
name: 'span',
|
|
731
|
-
styles: { 'font-size': '13pt' },
|
|
732
|
-
priority: 7,
|
|
733
|
-
},
|
|
734
|
-
},
|
|
735
|
-
{
|
|
736
|
-
title: '14',
|
|
737
|
-
model: '14pt',
|
|
738
|
-
view: {
|
|
739
|
-
name: 'span',
|
|
740
|
-
styles: { 'font-size': '14pt' },
|
|
741
|
-
priority: 7,
|
|
742
|
-
},
|
|
743
|
-
},
|
|
744
|
-
{
|
|
745
|
-
title: '16',
|
|
746
|
-
model: '16pt',
|
|
747
|
-
view: {
|
|
748
|
-
name: 'span',
|
|
749
|
-
styles: { 'font-size': '16pt' },
|
|
750
|
-
priority: 7,
|
|
751
|
-
},
|
|
752
|
-
},
|
|
753
|
-
{
|
|
754
|
-
title: '18',
|
|
755
|
-
model: '18pt',
|
|
756
|
-
view: {
|
|
757
|
-
name: 'span',
|
|
758
|
-
styles: { 'font-size': '18pt' },
|
|
759
|
-
priority: 7,
|
|
760
|
-
},
|
|
761
|
-
},
|
|
762
|
-
{
|
|
763
|
-
title: '20',
|
|
764
|
-
model: '20pt',
|
|
765
|
-
view: {
|
|
766
|
-
name: 'span',
|
|
767
|
-
styles: { 'font-size': '20pt' },
|
|
768
|
-
priority: 7,
|
|
769
|
-
},
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
title: '24',
|
|
773
|
-
model: '24pt',
|
|
774
|
-
view: {
|
|
775
|
-
name: 'span',
|
|
776
|
-
styles: { 'font-size': '24pt' },
|
|
777
|
-
priority: 7,
|
|
778
|
-
},
|
|
779
|
-
},
|
|
780
|
-
],
|
|
948
|
+
options: this.#fontSizeOptions,
|
|
781
949
|
supportAllValues: false, // Khuyên dùng false để ép user chọn đúng size chuẩn
|
|
782
950
|
},
|
|
951
|
+
heading: {
|
|
952
|
+
options: this.#headingOptions,
|
|
953
|
+
},
|
|
783
954
|
// 4. Cấu hình bảng màu (Tùy chọn)
|
|
784
955
|
fontColor: {
|
|
785
|
-
columns: 5,
|
|
956
|
+
// columns: 5,
|
|
786
957
|
documentColors: 10,
|
|
958
|
+
colorPicker: this.#colorPickerConfig,
|
|
959
|
+
colors: this.#sharedColors,
|
|
787
960
|
},
|
|
788
961
|
fontBackgroundColor: {
|
|
789
|
-
columns: 5,
|
|
962
|
+
// columns: 5,
|
|
790
963
|
documentColors: 10,
|
|
964
|
+
colorPicker: this.#colorPickerConfig,
|
|
965
|
+
colors: this.#sharedColors,
|
|
791
966
|
},
|
|
792
967
|
table: {
|
|
793
|
-
contentToolbar: [
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
968
|
+
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', '|', 'tableProperties', 'tableCellProperties'],
|
|
969
|
+
tableProperties: {
|
|
970
|
+
borderColors: this.#sharedColors,
|
|
971
|
+
colorPicker: this.#colorPickerConfig,
|
|
972
|
+
},
|
|
973
|
+
tableCellProperties: {
|
|
974
|
+
borderColors: this.#sharedColors,
|
|
975
|
+
colorPicker: this.#colorPickerConfig,
|
|
976
|
+
},
|
|
801
977
|
},
|
|
802
978
|
// Quan trọng: Cho phép paste style từ Word nhưng bỏ qua margin/padding
|
|
803
979
|
htmlSupport: {
|
|
@@ -814,15 +990,10 @@ class SdDocumentBuilder {
|
|
|
814
990
|
],
|
|
815
991
|
},
|
|
816
992
|
};
|
|
817
|
-
contentChange = new EventEmitter(); // Emit HTML content
|
|
818
|
-
#subscription = new Subscription();
|
|
819
|
-
#contentChangeSubject = new Subject();
|
|
820
|
-
#editorChangeRxjs = new Subject();
|
|
821
993
|
ngOnInit() {
|
|
822
|
-
//
|
|
823
|
-
// Debounce trong rxjs không hỗ trợ leading -->
|
|
994
|
+
// Debounce trong rxjs không hỗ trợ leading --> throttleTime
|
|
824
995
|
this.#subscription.add(this.#contentChangeSubject.pipe(throttleTime(500, undefined, { leading: true, trailing: true })).subscribe(content => {
|
|
825
|
-
this.contentChange.emit(content);
|
|
996
|
+
this.contentChange.emit(normalize(content));
|
|
826
997
|
}));
|
|
827
998
|
}
|
|
828
999
|
ngOnDestroy() {
|
|
@@ -846,49 +1017,17 @@ class SdDocumentBuilder {
|
|
|
846
1017
|
catch (error) {
|
|
847
1018
|
console.warn('PageOrientationPlugin not available:', error);
|
|
848
1019
|
}
|
|
849
|
-
//
|
|
1020
|
+
// Lắng nghe selection
|
|
850
1021
|
editor.model.document.selection.on('change', $event => {
|
|
851
1022
|
this.option.onSelection?.(this.#editor.model.document.selection, $event);
|
|
852
1023
|
});
|
|
853
|
-
//
|
|
1024
|
+
// Lắng nghe sự kiện thay đổi nội dung
|
|
854
1025
|
editor.model.document.on('change:data', () => {
|
|
855
1026
|
const content = editor.getData();
|
|
856
1027
|
this.#contentChangeSubject.next(content);
|
|
857
1028
|
});
|
|
858
1029
|
this.#updateState();
|
|
859
1030
|
}
|
|
860
|
-
#updateState() {
|
|
861
|
-
if (!this.#editor)
|
|
862
|
-
return;
|
|
863
|
-
if (this.disabled) {
|
|
864
|
-
// Bật chế độ chỉ đọc với ID khóa
|
|
865
|
-
this.#editor.enableReadOnlyMode(this.#id);
|
|
866
|
-
}
|
|
867
|
-
else {
|
|
868
|
-
// Tắt chế độ chỉ đọc với ID khóa tương ứng
|
|
869
|
-
this.#editor.disableReadOnlyMode(this.#id);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
scrollToComment = (markerId) => {
|
|
873
|
-
if (!this.#editor)
|
|
874
|
-
return;
|
|
875
|
-
const editor = this.#editor;
|
|
876
|
-
const marker = editor.model.markers.get(markerId);
|
|
877
|
-
if (marker) {
|
|
878
|
-
// 1. Set Selection vào Marker đó trước
|
|
879
|
-
this.#editor.model.change(writer => {
|
|
880
|
-
writer.setSelection(marker.getRange());
|
|
881
|
-
});
|
|
882
|
-
// 2. Sau đó gọi scrollToTheSelection
|
|
883
|
-
this.#editor.editing.view.scrollToTheSelection({
|
|
884
|
-
alignToTop: true,
|
|
885
|
-
});
|
|
886
|
-
editor.editing.view.focus();
|
|
887
|
-
}
|
|
888
|
-
else {
|
|
889
|
-
console.warn(`Marker with id ${markerId} not found.`);
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
1031
|
setContent = (html) => {
|
|
893
1032
|
this.#editor?.setData?.(html);
|
|
894
1033
|
};
|
|
@@ -925,55 +1064,6 @@ class SdDocumentBuilder {
|
|
|
925
1064
|
}
|
|
926
1065
|
return 'PORTRAIT';
|
|
927
1066
|
};
|
|
928
|
-
getVariables = () => {
|
|
929
|
-
if (!this.#editor)
|
|
930
|
-
return [];
|
|
931
|
-
const model = this.#editor.model;
|
|
932
|
-
const root = model.document.getRoot();
|
|
933
|
-
if (!root)
|
|
934
|
-
return [];
|
|
935
|
-
const variables = [];
|
|
936
|
-
// Duyệt qua tất cả các phần tử trong range
|
|
937
|
-
// range.getItems() sẽ trả về từng node (text, element...)
|
|
938
|
-
try {
|
|
939
|
-
const range = model.createRangeIn(root);
|
|
940
|
-
for (const item of range.getItems()) {
|
|
941
|
-
// Sử dụng item.is('element', 'variable') là chính xác
|
|
942
|
-
if (item.is('element', 'variable')) {
|
|
943
|
-
variables.push({
|
|
944
|
-
id: item.getAttribute('id'),
|
|
945
|
-
value: item.getAttribute('value'),
|
|
946
|
-
display: item.getAttribute('display'),
|
|
947
|
-
data: item.getAttribute('data'),
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
catch (e) {
|
|
953
|
-
console.error(e);
|
|
954
|
-
return [];
|
|
955
|
-
}
|
|
956
|
-
return variables;
|
|
957
|
-
};
|
|
958
|
-
getComments() {
|
|
959
|
-
if (!this.#editor)
|
|
960
|
-
return [];
|
|
961
|
-
const markers = this.#editor.model.markers;
|
|
962
|
-
const comments = [];
|
|
963
|
-
// Duyệt qua tất cả markers trong Model
|
|
964
|
-
for (const marker of markers) {
|
|
965
|
-
// Chỉ lấy marker do plugin comment tạo ra (prefix 'comment:')
|
|
966
|
-
if (marker.name.startsWith('comment:')) {
|
|
967
|
-
// Lấy text nằm trong vùng marker đó
|
|
968
|
-
const currentText = this.#getTextFromRange(marker.getRange());
|
|
969
|
-
comments.push({
|
|
970
|
-
markerId: marker.name,
|
|
971
|
-
selectedText: currentText,
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
return comments;
|
|
976
|
-
}
|
|
977
1067
|
scrollToTop() {
|
|
978
1068
|
setTimeout(() => {
|
|
979
1069
|
if (this.#editor) {
|
|
@@ -993,13 +1083,66 @@ class SdDocumentBuilder {
|
|
|
993
1083
|
}
|
|
994
1084
|
}, 100);
|
|
995
1085
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1086
|
+
#updateState() {
|
|
1087
|
+
if (!this.#editor)
|
|
1088
|
+
return;
|
|
1089
|
+
if (this.disabled) {
|
|
1090
|
+
// Bật chế độ chỉ đọc với ID khóa
|
|
1091
|
+
this.#editor.enableReadOnlyMode(this.#id);
|
|
1092
|
+
// Disable page orientation button
|
|
1093
|
+
try {
|
|
1094
|
+
const orientationPlugin = this.#editor.plugins.get('PageOrientationPlugin');
|
|
1095
|
+
if (orientationPlugin && orientationPlugin.buttonView) {
|
|
1096
|
+
orientationPlugin.buttonView.isEnabled = false;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
catch (error) {
|
|
1100
|
+
console.warn('Failed to disable orientation button:', error);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
// Tắt chế độ chỉ đọc với ID khóa tương ứng
|
|
1105
|
+
this.#editor.disableReadOnlyMode(this.#id);
|
|
1106
|
+
// Enable page orientation button
|
|
1107
|
+
try {
|
|
1108
|
+
const orientationPlugin = this.#editor.plugins.get('PageOrientationPlugin');
|
|
1109
|
+
if (orientationPlugin && orientationPlugin.buttonView) {
|
|
1110
|
+
orientationPlugin.buttonView.isEnabled = true;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
catch (error) {
|
|
1114
|
+
console.warn('Failed to enable orientation button:', error);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
#getTextFromElement = (element) => {
|
|
1119
|
+
let text = '';
|
|
1120
|
+
// Heading trong Model chứa các text node con
|
|
1121
|
+
for (const child of element.getChildren()) {
|
|
1122
|
+
if (child.is('$text') || child.is('$textProxy')) {
|
|
1123
|
+
text += child.data;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return text;
|
|
1127
|
+
};
|
|
1128
|
+
#getTextFromRange = (range) => {
|
|
1129
|
+
let text = '';
|
|
1130
|
+
for (const item of range.getItems()) {
|
|
1131
|
+
// TextProxy là một phần của Text Node nằm trong Range
|
|
1132
|
+
if (item.is('$textProxy') || item.is('$text')) {
|
|
1133
|
+
text += item.data;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return text;
|
|
1137
|
+
};
|
|
1138
|
+
// ========================================================================
|
|
1139
|
+
// 1. QUẢN LÝ HEADING
|
|
1140
|
+
// ========================================================================
|
|
998
1141
|
heading = {
|
|
999
1142
|
// ========================================================================
|
|
1000
1143
|
// HÀM LẤY DANH SÁCH HEADING (TOC)
|
|
1001
1144
|
// ========================================================================
|
|
1002
|
-
|
|
1145
|
+
all: () => {
|
|
1003
1146
|
if (!this.#editor)
|
|
1004
1147
|
return [];
|
|
1005
1148
|
const root = this.#editor.model.document.getRoot();
|
|
@@ -1035,37 +1178,46 @@ class SdDocumentBuilder {
|
|
|
1035
1178
|
}
|
|
1036
1179
|
return headings;
|
|
1037
1180
|
},
|
|
1038
|
-
|
|
1039
|
-
// HÀM SCROLL TO HEADING
|
|
1040
|
-
// ========================================================================
|
|
1041
|
-
scrollToHeading: (id) => {
|
|
1181
|
+
scroll: (id) => {
|
|
1042
1182
|
if (!this.#editor)
|
|
1043
1183
|
return;
|
|
1044
|
-
// 1. Lấy Model Element từ kho lưu trữ
|
|
1045
1184
|
const modelElement = this.#headingElementsMap.get(id);
|
|
1046
1185
|
if (modelElement) {
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1186
|
+
this.#editor.model.change(writer => {
|
|
1187
|
+
// Xóa marker cũ
|
|
1188
|
+
if (this.#idTimeOutScrollHeading) {
|
|
1189
|
+
clearTimeout(this.#idTimeOutScrollHeading);
|
|
1190
|
+
}
|
|
1191
|
+
const currentMarker = this.#editor.model.markers.get('highlightMarker');
|
|
1192
|
+
if (currentMarker) {
|
|
1193
|
+
writer.removeMarker(currentMarker);
|
|
1194
|
+
}
|
|
1195
|
+
// Tạo Range bao trùm highlight
|
|
1196
|
+
const range = writer.createRangeOn(modelElement);
|
|
1197
|
+
// Thêm Marker mới
|
|
1198
|
+
writer.addMarker('highlightMarker', {
|
|
1199
|
+
range: range,
|
|
1200
|
+
usingOperation: false,
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
// Scroll tới vị trí đó
|
|
1204
|
+
const viewElement = this.#editor.editing.mapper.toViewElement(modelElement);
|
|
1052
1205
|
if (viewElement) {
|
|
1053
|
-
|
|
1054
|
-
const domElement = view.domConverter.mapViewToDom(viewElement);
|
|
1206
|
+
const domElement = this.#editor.editing.view.domConverter.viewToDom(viewElement);
|
|
1055
1207
|
if (domElement) {
|
|
1056
|
-
|
|
1057
|
-
domElement.scrollIntoView({
|
|
1058
|
-
behavior: 'smooth',
|
|
1059
|
-
block: 'start',
|
|
1060
|
-
inline: 'nearest',
|
|
1061
|
-
});
|
|
1062
|
-
// 5. (Tùy chọn) Focus và đặt con trỏ vào đó
|
|
1063
|
-
view.focus();
|
|
1064
|
-
editor.model.change(writer => {
|
|
1065
|
-
writer.setSelection(modelElement, 'on');
|
|
1066
|
-
});
|
|
1208
|
+
domElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1067
1209
|
}
|
|
1068
1210
|
}
|
|
1211
|
+
// Tự động tắt marker sau 10 giây
|
|
1212
|
+
this.#idTimeOutScrollHeading = setTimeout(() => {
|
|
1213
|
+
if (this.#editor) {
|
|
1214
|
+
this.#editor.model.change(writer => {
|
|
1215
|
+
const marker = this.#editor.model.markers.get('highlightMarker');
|
|
1216
|
+
if (marker)
|
|
1217
|
+
writer.removeMarker(marker);
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
}, 5000);
|
|
1069
1221
|
}
|
|
1070
1222
|
else {
|
|
1071
1223
|
console.warn(`Heading with id ${id} not found.`);
|
|
@@ -1073,30 +1225,7 @@ class SdDocumentBuilder {
|
|
|
1073
1225
|
},
|
|
1074
1226
|
};
|
|
1075
1227
|
// ========================================================================
|
|
1076
|
-
//
|
|
1077
|
-
// ========================================================================
|
|
1078
|
-
#getTextFromElement = (element) => {
|
|
1079
|
-
let text = '';
|
|
1080
|
-
// Heading trong Model chứa các text node con
|
|
1081
|
-
for (const child of element.getChildren()) {
|
|
1082
|
-
if (child.is('$text') || child.is('$textProxy')) {
|
|
1083
|
-
text += child.data;
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
return text;
|
|
1087
|
-
};
|
|
1088
|
-
#getTextFromRange = (range) => {
|
|
1089
|
-
let text = '';
|
|
1090
|
-
for (const item of range.getItems()) {
|
|
1091
|
-
// TextProxy là một phần của Text Node nằm trong Range
|
|
1092
|
-
if (item.is('$textProxy') || item.is('$text')) {
|
|
1093
|
-
text += item.data;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
return text;
|
|
1097
|
-
};
|
|
1098
|
-
// ========================================================================
|
|
1099
|
-
// COMMENT MANAGEMENT
|
|
1228
|
+
// 2. QUẢN LÝ COMMENT
|
|
1100
1229
|
// ========================================================================
|
|
1101
1230
|
comment = {
|
|
1102
1231
|
/**
|
|
@@ -1242,72 +1371,71 @@ class SdDocumentBuilder {
|
|
|
1242
1371
|
}
|
|
1243
1372
|
},
|
|
1244
1373
|
};
|
|
1245
|
-
//
|
|
1246
|
-
//
|
|
1247
|
-
//
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
// }
|
|
1374
|
+
// ========================================================================
|
|
1375
|
+
// 3. QUẢN LÝ VARIABLE
|
|
1376
|
+
// ========================================================================
|
|
1377
|
+
variable = {
|
|
1378
|
+
all: () => {
|
|
1379
|
+
if (!this.#editor)
|
|
1380
|
+
return [];
|
|
1381
|
+
const model = this.#editor.model;
|
|
1382
|
+
const root = model.document.getRoot();
|
|
1383
|
+
if (!root)
|
|
1384
|
+
return [];
|
|
1385
|
+
const variables = [];
|
|
1386
|
+
try {
|
|
1387
|
+
const range = model.createRangeIn(root);
|
|
1388
|
+
for (const item of range.getItems()) {
|
|
1389
|
+
// Sử dụng item.is('element', 'variable') là chính xác
|
|
1390
|
+
if (item.is('element', 'variable')) {
|
|
1391
|
+
variables.push({
|
|
1392
|
+
id: item.getAttribute('id'),
|
|
1393
|
+
uuid: item.getAttribute('uuid'),
|
|
1394
|
+
value: item.getAttribute('value'),
|
|
1395
|
+
display: item.getAttribute('display'),
|
|
1396
|
+
data: item.getAttribute('data'),
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
catch (e) {
|
|
1402
|
+
console.error(e);
|
|
1403
|
+
return [];
|
|
1404
|
+
}
|
|
1405
|
+
return variables;
|
|
1406
|
+
},
|
|
1407
|
+
scroll: (uuid) => {
|
|
1408
|
+
if (!this.#editor)
|
|
1409
|
+
return;
|
|
1410
|
+
const model = this.#editor.model;
|
|
1411
|
+
const root = model.document.getRoot();
|
|
1412
|
+
if (!root)
|
|
1413
|
+
return;
|
|
1414
|
+
let targetElement = null;
|
|
1415
|
+
const range = model.createRangeIn(root);
|
|
1416
|
+
for (const item of range.getItems()) {
|
|
1417
|
+
if (item.is('element', 'variable') && item.getAttribute('uuid') === uuid) {
|
|
1418
|
+
targetElement = item;
|
|
1419
|
+
break;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (targetElement) {
|
|
1423
|
+
const viewElement = this.#editor.editing.mapper.toViewElement(targetElement);
|
|
1424
|
+
if (viewElement) {
|
|
1425
|
+
const domElement = this.#editor.editing.view.domConverter.viewToDom(viewElement);
|
|
1426
|
+
if (domElement) {
|
|
1427
|
+
domElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1428
|
+
model.change(writer => {
|
|
1429
|
+
writer.setSelection(targetElement, 'on');
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
else {
|
|
1435
|
+
console.warn(`Variable với id ${uuid} không tìm thấy trong tài liệu.`);
|
|
1436
|
+
}
|
|
1437
|
+
},
|
|
1438
|
+
};
|
|
1311
1439
|
// ========================================================================
|
|
1312
1440
|
// 4. HÀM EXPORT DOCX (FULL HEADER/FOOTER + PAGE NUMBER)
|
|
1313
1441
|
// ========================================================================
|
|
@@ -1412,22 +1540,13 @@ class SdDocumentBuilder {
|
|
|
1412
1540
|
// Thêm '\ufeff' (BOM) để fix lỗi font tiếng Việt
|
|
1413
1541
|
const blob = new Blob(['\ufeff', fullHtml], { type: 'application/msword' });
|
|
1414
1542
|
SdUtilities.downloadBlob(blob, fileName);
|
|
1415
|
-
// 3. Convert & Save
|
|
1416
|
-
// asBlob(fullHtml, {
|
|
1417
|
-
// orientation: orientation as 'portrait' | 'landscape',
|
|
1418
|
-
// margins: { top: 720, right: 720, bottom: 720, left: 720 },
|
|
1419
|
-
// }).then(blob => {
|
|
1420
|
-
// if (blob instanceof Blob) {
|
|
1421
|
-
// SdUtilities.downloadBlob(blob, fileName);
|
|
1422
|
-
// }
|
|
1423
|
-
// });
|
|
1424
1543
|
}
|
|
1425
1544
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1426
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: SdDocumentBuilder, isStandalone: true, selector: "sd-document-builder", inputs: { option: "option", _disabled: ["disabled", "_disabled"] }, outputs: { contentChange: "contentChange" }, ngImport: i0, template: "<div class=\"builder-container\">\n <ckeditor\n style=\"width: 100%\"\n [editor]=\"Editor\" \n [config]=\"config\" \n (ready)=\"onReady($event)\"\n [disabled]=\"disabled\">\n </ckeditor>\n</div>", styles: ["@charset \"UTF-8\";.builder-container{background-color:#f3f4f6;height:100%;overflow-y:auto;width:100%;display:flex;flex-direction:column;align-items:center;padding-bottom:20px}:host{display:inline-block}:host ::ng-deep .ck-editor{display:flex;flex-direction:column;align-items:center;width:100%}:host ::ng-deep .ck-editor .ck-editor__top,:host ::ng-deep .ck-editor .ck-editor__main{border:none!important;box-shadow:none!important}:host ::ng-deep .ck-content{background-color:#fff;width:210mm;min-height:1123px;padding:20mm!important;box-sizing:border-box!important;box-shadow:0 10px 15px -3px #0000001a}:host ::ng-deep .ck-content h1,:host ::ng-deep .ck-content h2,:host ::ng-deep .ck-content h3,:host ::ng-deep .ck-content h4,:host ::ng-deep .ck-content h5,:host ::ng-deep .ck-content h6{font-weight:400}:host ::ng-deep .ck-content.ck-focused{outline:none!important;border-color:#d1d5db!important}:host ::ng-deep .ck-content.landscape{width:297mm}:host ::ng-deep .ck-content>*{max-width:100%!important;box-sizing:border-box!important}:host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content p{margin-left:0!important;margin-right:0!important;margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important}\n", "@
|
|
1545
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.17", type: SdDocumentBuilder, isStandalone: true, selector: "sd-document-builder", inputs: { option: "option", _disabled: ["disabled", "_disabled"] }, outputs: { contentChange: "contentChange" }, ngImport: i0, template: "<div class=\"builder-container\">\n <ckeditor\n style=\"width: 100%\"\n [editor]=\"Editor\" \n [config]=\"config\" \n (ready)=\"onReady($event)\"\n [disabled]=\"disabled\">\n </ckeditor>\n</div>", styles: ["@charset \"UTF-8\";::ng-deep .ck-editor{--ck-font-size-base: 11px !important;--ck-icon-size: 16px !important;--ck-content-font-family: \"Times New Roman\", serif !important;--ck-content-font-size: 13pt;--ck-content-line-height: 1.5;--ck-spacing-small: 2px !important;--ck-spacing-standard: 4px !important;--ck-spacing-large: 8px !important}::ng-deep .ck-editor .ck-editor__top{position:sticky;top:0;z-index:100;width:100%;min-width:600px;margin-bottom:10px}::ng-deep .ck-editor .ck-editor__top .ck-sticky-panel__content{border:none!important}::ng-deep .ck-editor .ck-editor__top .ck-toolbar{background:#fff!important;box-shadow:0 4px 6px -1px #0000001a!important;padding:8px!important}::ng-deep .ck-editor .ck-editor__top .ck-toolbar .ck-toolbar__items{display:flex;justify-content:center;flex-wrap:wrap;align-items:center}::ng-deep .ck-editor .ck-toolbar{min-height:32px!important;padding:2px!important}::ng-deep .ck-editor .ck-button{padding:2px 4px!important;min-height:24px!important}::ng-deep .ck-editor .ck-dropdown__button{min-height:24px!important}::ng-deep .ck.ck-toolbar{background:#f8f9fa!important;border-bottom:1px solid #e0e0e0!important}\n", "@charset \"UTF-8\";.builder-container{background-color:#f3f4f6;height:100%;overflow-y:auto;width:100%;display:flex;flex-direction:column;align-items:center;padding-bottom:20px}:host{display:inline-block}:host ::ng-deep .ck-editor{display:flex;flex-direction:column;align-items:center;width:100%}:host ::ng-deep .ck-editor .ck-editor__top,:host ::ng-deep .ck-editor .ck-editor__main{border:none!important;box-shadow:none!important}:host ::ng-deep .ck-content{background-color:#fff;width:210mm;min-height:1123px;padding:20mm!important;box-sizing:border-box!important;box-shadow:0 10px 15px -3px #0000001a}:host ::ng-deep .ck-content h1,:host ::ng-deep .ck-content h2,:host ::ng-deep .ck-content h3,:host ::ng-deep .ck-content h4,:host ::ng-deep .ck-content h5,:host ::ng-deep .ck-content h6{font-weight:400}:host ::ng-deep .ck-content.ck-focused{outline:none!important;border-color:#d1d5db!important}:host ::ng-deep .ck-content.landscape{width:297mm}:host ::ng-deep .ck-content>*{max-width:100%!important;box-sizing:border-box!important}:host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content p{margin-left:0!important;margin-right:0!important;margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important}\n", ":host ::ng-deep .ck-heading-highlight{background-color:#fef08a;animation:fadeOut 2s 8s forwards}@keyframes fadeOut{0%{background-color:#fef08a}to{background-color:transparent}}\n", ":host ::ng-deep .ck-comment-marker{background-color:#ffeb3b80;border-bottom:2px solid #fbc02d;transition:background-color .2s;cursor:pointer}:host ::ng-deep .ck-comment-marker:hover{background-color:#ffeb3bcc}:host ::ng-deep .ck-comment-marker.active-highlight{background-color:#ffeb3b;outline:2px dashed #f57f17}\n", "@charset \"UTF-8\";:host ::ng-deep .variable-widget{background-color:#e3f2fd;color:#1976d2;border:1px solid #90caf9!important;border-radius:4px;padding:2px 6px;font-weight:600;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;font-size:10px;-webkit-user-select:none;user-select:none;display:inline-block;margin:0 4px;vertical-align:middle;font-size:0;cursor:default}:host ::ng-deep .variable-widget:before{content:attr(data-display);font-size:10px}:host ::ng-deep .variable-widget:hover{background-color:#bbdefb;box-shadow:0 1px 2px #0000001a}:host ::ng-deep .variable-widget.ck-widget_selected{outline:2px solid #2196f3;background-color:#bbdefb}:host ::ng-deep .ck.ck-content .ck-widget,:host ::ng-deep .ck.ck-content .ck-widget:hover,:host ::ng-deep .ck.ck-content .ck-widget:focus,:host ::ng-deep .ck.ck-content .ck-widget.ck-widget_selected,:host ::ng-deep .ck.ck-content .ck-widget.ck-widget_selected:hover{outline:none!important;box-shadow:none!important}:host ::ng-deep .ck.ck-clipboard-drop-target-line{display:none!important}\n", ":host ::ng-deep .ck-editor__editable .ck-widget.table{float:none!important;display:block!important;max-width:100%!important;width:100%!important;margin:0!important;clear:both}:host ::ng-deep .ck-editor__editable table{table-layout:auto!important;width:100%!important;border-collapse:collapse;margin:0!important}:host ::ng-deep .ck-editor__editable table td,:host ::ng-deep .ck-editor__editable table th{word-wrap:break-word;white-space:normal!important;padding:.4em!important}:host ::ng-deep .ck-editor__editable table td img,:host ::ng-deep .ck-editor__editable table th img{max-width:100%;height:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i1.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableWatchdog", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }] });
|
|
1427
1546
|
}
|
|
1428
1547
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, decorators: [{
|
|
1429
1548
|
type: Component,
|
|
1430
|
-
args: [{ selector: 'sd-document-builder', standalone: true, imports: [CommonModule, CKEditorModule], template: "<div class=\"builder-container\">\n <ckeditor\n style=\"width: 100%\"\n [editor]=\"Editor\" \n [config]=\"config\" \n (ready)=\"onReady($event)\"\n [disabled]=\"disabled\">\n </ckeditor>\n</div>", styles: ["@charset \"UTF-8\";.builder-container{background-color:#f3f4f6;height:100%;overflow-y:auto;width:100%;display:flex;flex-direction:column;align-items:center;padding-bottom:20px}:host{display:inline-block}:host ::ng-deep .ck-editor{display:flex;flex-direction:column;align-items:center;width:100%}:host ::ng-deep .ck-editor .ck-editor__top,:host ::ng-deep .ck-editor .ck-editor__main{border:none!important;box-shadow:none!important}:host ::ng-deep .ck-content{background-color:#fff;width:210mm;min-height:1123px;padding:20mm!important;box-sizing:border-box!important;box-shadow:0 10px 15px -3px #0000001a}:host ::ng-deep .ck-content h1,:host ::ng-deep .ck-content h2,:host ::ng-deep .ck-content h3,:host ::ng-deep .ck-content h4,:host ::ng-deep .ck-content h5,:host ::ng-deep .ck-content h6{font-weight:400}:host ::ng-deep .ck-content.ck-focused{outline:none!important;border-color:#d1d5db!important}:host ::ng-deep .ck-content.landscape{width:297mm}:host ::ng-deep .ck-content>*{max-width:100%!important;box-sizing:border-box!important}:host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content p{margin-left:0!important;margin-right:0!important;margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important}\n", "@
|
|
1549
|
+
args: [{ selector: 'sd-document-builder', standalone: true, imports: [CommonModule, CKEditorModule], template: "<div class=\"builder-container\">\n <ckeditor\n style=\"width: 100%\"\n [editor]=\"Editor\" \n [config]=\"config\" \n (ready)=\"onReady($event)\"\n [disabled]=\"disabled\">\n </ckeditor>\n</div>", styles: ["@charset \"UTF-8\";::ng-deep .ck-editor{--ck-font-size-base: 11px !important;--ck-icon-size: 16px !important;--ck-content-font-family: \"Times New Roman\", serif !important;--ck-content-font-size: 13pt;--ck-content-line-height: 1.5;--ck-spacing-small: 2px !important;--ck-spacing-standard: 4px !important;--ck-spacing-large: 8px !important}::ng-deep .ck-editor .ck-editor__top{position:sticky;top:0;z-index:100;width:100%;min-width:600px;margin-bottom:10px}::ng-deep .ck-editor .ck-editor__top .ck-sticky-panel__content{border:none!important}::ng-deep .ck-editor .ck-editor__top .ck-toolbar{background:#fff!important;box-shadow:0 4px 6px -1px #0000001a!important;padding:8px!important}::ng-deep .ck-editor .ck-editor__top .ck-toolbar .ck-toolbar__items{display:flex;justify-content:center;flex-wrap:wrap;align-items:center}::ng-deep .ck-editor .ck-toolbar{min-height:32px!important;padding:2px!important}::ng-deep .ck-editor .ck-button{padding:2px 4px!important;min-height:24px!important}::ng-deep .ck-editor .ck-dropdown__button{min-height:24px!important}::ng-deep .ck.ck-toolbar{background:#f8f9fa!important;border-bottom:1px solid #e0e0e0!important}\n", "@charset \"UTF-8\";.builder-container{background-color:#f3f4f6;height:100%;overflow-y:auto;width:100%;display:flex;flex-direction:column;align-items:center;padding-bottom:20px}:host{display:inline-block}:host ::ng-deep .ck-editor{display:flex;flex-direction:column;align-items:center;width:100%}:host ::ng-deep .ck-editor .ck-editor__top,:host ::ng-deep .ck-editor .ck-editor__main{border:none!important;box-shadow:none!important}:host ::ng-deep .ck-content{background-color:#fff;width:210mm;min-height:1123px;padding:20mm!important;box-sizing:border-box!important;box-shadow:0 10px 15px -3px #0000001a}:host ::ng-deep .ck-content h1,:host ::ng-deep .ck-content h2,:host ::ng-deep .ck-content h3,:host ::ng-deep .ck-content h4,:host ::ng-deep .ck-content h5,:host ::ng-deep .ck-content h6{font-weight:400}:host ::ng-deep .ck-content.ck-focused{outline:none!important;border-color:#d1d5db!important}:host ::ng-deep .ck-content.landscape{width:297mm}:host ::ng-deep .ck-content>*{max-width:100%!important;box-sizing:border-box!important}:host ::ng-deep .ck-content img{max-width:100%!important;height:auto!important;object-fit:contain}:host ::ng-deep .ck-content p{margin-left:0!important;margin-right:0!important;margin-bottom:var(--ck-spacing-large);text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important}\n", ":host ::ng-deep .ck-heading-highlight{background-color:#fef08a;animation:fadeOut 2s 8s forwards}@keyframes fadeOut{0%{background-color:#fef08a}to{background-color:transparent}}\n", ":host ::ng-deep .ck-comment-marker{background-color:#ffeb3b80;border-bottom:2px solid #fbc02d;transition:background-color .2s;cursor:pointer}:host ::ng-deep .ck-comment-marker:hover{background-color:#ffeb3bcc}:host ::ng-deep .ck-comment-marker.active-highlight{background-color:#ffeb3b;outline:2px dashed #f57f17}\n", "@charset \"UTF-8\";:host ::ng-deep .variable-widget{background-color:#e3f2fd;color:#1976d2;border:1px solid #90caf9!important;border-radius:4px;padding:2px 6px;font-weight:600;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif;font-size:10px;-webkit-user-select:none;user-select:none;display:inline-block;margin:0 4px;vertical-align:middle;font-size:0;cursor:default}:host ::ng-deep .variable-widget:before{content:attr(data-display);font-size:10px}:host ::ng-deep .variable-widget:hover{background-color:#bbdefb;box-shadow:0 1px 2px #0000001a}:host ::ng-deep .variable-widget.ck-widget_selected{outline:2px solid #2196f3;background-color:#bbdefb}:host ::ng-deep .ck.ck-content .ck-widget,:host ::ng-deep .ck.ck-content .ck-widget:hover,:host ::ng-deep .ck.ck-content .ck-widget:focus,:host ::ng-deep .ck.ck-content .ck-widget.ck-widget_selected,:host ::ng-deep .ck.ck-content .ck-widget.ck-widget_selected:hover{outline:none!important;box-shadow:none!important}:host ::ng-deep .ck.ck-clipboard-drop-target-line{display:none!important}\n", ":host ::ng-deep .ck-editor__editable .ck-widget.table{float:none!important;display:block!important;max-width:100%!important;width:100%!important;margin:0!important;clear:both}:host ::ng-deep .ck-editor__editable table{table-layout:auto!important;width:100%!important;border-collapse:collapse;margin:0!important}:host ::ng-deep .ck-editor__editable table td,:host ::ng-deep .ck-editor__editable table th{word-wrap:break-word;white-space:normal!important;padding:.4em!important}:host ::ng-deep .ck-editor__editable table td img,:host ::ng-deep .ck-editor__editable table th img{max-width:100%;height:auto}\n"] }]
|
|
1431
1550
|
}], propDecorators: { option: [{
|
|
1432
1551
|
type: Input,
|
|
1433
1552
|
args: [{ required: true }]
|