@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.
Files changed (61) hide show
  1. package/components/document-builder/src/document-builder.component.d.ts +7 -6
  2. package/components/document-builder/src/document-builder.config.d.ts +21 -0
  3. package/components/document-builder/src/document-builder.model.d.ts +1 -0
  4. package/components/document-builder/src/document-builder.utils.d.ts +10 -0
  5. package/components/document-builder/src/plugins/heading/heading.plugin.d.ts +4 -0
  6. package/components/document-builder/src/plugins/{image-upload.plugin.d.ts → image-upload/image-upload.plugin.d.ts} +0 -4
  7. package/components/document-builder/src/plugins/index.d.ts +6 -5
  8. package/components/table/src/models/table-item.model.d.ts +2 -1
  9. package/fesm2022/sd-angular-core-components-document-builder.mjs +541 -422
  10. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  11. package/fesm2022/sd-angular-core-components-table.mjs +138 -72
  12. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  13. package/fesm2022/sd-angular-core-components-workflow.mjs +23 -23
  14. package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
  15. package/fesm2022/sd-angular-core-forms-autocomplete.mjs +24 -2
  16. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  17. package/fesm2022/sd-angular-core-forms-date.mjs +15 -3
  18. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  19. package/fesm2022/sd-angular-core-forms-datetime.mjs +17 -3
  20. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  21. package/fesm2022/sd-angular-core-forms-input-number.mjs +18 -3
  22. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  23. package/fesm2022/sd-angular-core-forms-input.mjs +20 -6
  24. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  25. package/fesm2022/sd-angular-core-forms-radio.mjs +17 -2
  26. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  27. package/fesm2022/sd-angular-core-forms-select.mjs +15 -2
  28. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  29. package/fesm2022/sd-angular-core-forms-textarea.mjs +21 -2
  30. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  31. package/fesm2022/sd-angular-core-modules-auth.mjs +5 -5
  32. package/fesm2022/sd-angular-core-modules-auth.mjs.map +1 -1
  33. package/fesm2022/sd-angular-core-modules-layout.mjs +1 -1
  34. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  35. package/fesm2022/sd-angular-core-pipes.mjs +21 -1
  36. package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
  37. package/fesm2022/sd-angular-core-services-confirm.mjs +1 -1
  38. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  39. package/fesm2022/sd-angular-core-utilities-extensions.mjs +66 -1
  40. package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
  41. package/fesm2022/sd-angular-core-utilities-models.mjs +2 -2
  42. package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
  43. package/forms/autocomplete/src/autocomplete.component.d.ts +5 -1
  44. package/forms/date/src/date.component.d.ts +4 -1
  45. package/forms/datetime/src/datetime.component.d.ts +4 -1
  46. package/forms/input/src/input.component.d.ts +6 -4
  47. package/forms/input-number/src/input-number.component.d.ts +4 -1
  48. package/forms/radio/src/radio.component.d.ts +5 -1
  49. package/forms/select/src/select.component.d.ts +5 -1
  50. package/forms/textarea/src/textarea.component.d.ts +3 -1
  51. package/modules/auth/guards/portal.guard.d.ts +3 -3
  52. package/package.json +56 -56
  53. package/pipes/index.d.ts +1 -0
  54. package/pipes/src/empty.pipe.d.ts +7 -0
  55. package/utilities/extensions/index.d.ts +1 -0
  56. package/utilities/extensions/src/color.extension.d.ts +20 -0
  57. package/utilities/models/src/pattern.model.d.ts +2 -2
  58. /package/components/document-builder/src/plugins/{comment.plugin.d.ts → comment/comment.plugin.d.ts} +0 -0
  59. /package/components/document-builder/src/plugins/{page-orientation.plugin.d.ts → page-orientation/page-orientation.plugin.d.ts} +0 -0
  60. /package/components/document-builder/src/plugins/{table-fit.plugin.d.ts → table-fit/table-fit.plugin.d.ts} +0 -0
  61. /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
- // Icon khổ dọc (Mặc định cũ)
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
- const componentFactory = editor.ui.componentFactory;
145
- // Đăng ký nút tên là 'pageOrientation'
146
- componentFactory.add('pageOrientation', locale => {
147
- const view = new ButtonView(locale);
148
- this.buttonView = view;
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
- // --- 1. CONVERSION: MODEL MARKER -> VIEW CSS ---
210
- // Biến Marker thành Highlight màu vàng
211
- editor.conversion.for('editingDowncast').markerToHighlight({
212
- model: 'comment', // Khớp với prefix của markerId (ví dụ: comment:12345)
213
- view: {
214
- classes: 'ck-comment-marker', // Class CSS sẽ được gắn vào thẻ <span> bao quanh text
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
- // --- 3. ĐĂNG KÝ UI COMPONENT: 'addCommentBtn' ---
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
- // ẨN BUTTON NẾU KHÔNG onAddComment
167
+ // Ẩn button nếu không 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, // Mặc định disable
179
+ isEnabled: false,
239
180
  });
240
- // 4. Logic Enable/Disable: Dựa theo Selection
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
- // 5. Logic Execute: Khi bấm nút
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
- // BẮN EVENT RA NGOÀI - KHÔNG TỰ ADD MARKER
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
- // A. Chèn biến
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
- // B. Đặt con trỏ ra sau biến
341
+ // 4.2. Đặt con trỏ ra sau biến
375
342
  writer.setSelection(variableElem, 'after');
376
- // C. [QUAN TRỌNG] XÓA SẠCH CÁC MARKER DROP
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
- // FIX LỖI: Focus lại vào editor để xóa các artifact của việc kéo thả
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
- // [BỔ SUNG] XÓA CLASS RÁC TRÊN VIEW (NẾU MARKER KHÔNG HẾT)
388
- // Đôi khi View chưa kịp render lại, ta ép xóa class thủ công trên root nếu cần
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
- PageOrientationPlugin,
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
- 'tableColumn',
795
- 'tableRow',
796
- 'mergeTableCells',
797
- '|',
798
- 'tableProperties', // <--- Nút chỉnh thuộc tính bảng (Viền, Màu, Width)
799
- 'tableCellProperties',
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
- // https://onemount.atlassian.net/browse/SM-1862
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
- // Đăng ký sự kiện lắng nghe Selection để làm Comment
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
- // ĐĂNG SỰ KIỆN LẮNG NGHE THAY ĐỔI NỘI DUNG
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
- // Kho lưu trữ tham chiếu Model của Heading (để phục vụ scroll)
997
- #headingElementsMap = new Map();
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
- getHeadings: () => {
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
- const editor = this.#editor;
1048
- const view = editor.editing.view;
1049
- // 2. Chuyển đổi Model Element -> View Element
1050
- // mapper.toViewElement sẽ trả về thẻ HTML ảo (View Element) tương ứng (ví dụ thẻ <h2>)
1051
- const viewElement = editor.editing.mapper.toViewElement(modelElement);
1186
+ this.#editor.model.change(writer => {
1187
+ // Xóa marker
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
- // 3. Từ View Element ảo -> Lấy ra DOM thật (HTMLElement)
1054
- const domElement = view.domConverter.mapViewToDom(viewElement);
1206
+ const domElement = this.#editor.editing.view.domConverter.viewToDom(viewElement);
1055
1207
  if (domElement) {
1056
- // 4. Dùng hàm native của trình duyệt để cuộn
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
- // HELPER: LẤY TEXT TỪ ELEMENT (Đệ quy nhẹ)
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 COMMENT
1100
1229
  // ========================================================================
1101
1230
  comment = {
1102
1231
  /**
@@ -1242,72 +1371,71 @@ class SdDocumentBuilder {
1242
1371
  }
1243
1372
  },
1244
1373
  };
1245
- // exportDocx(fileName: string = 'document.docx'): void {
1246
- // if (!this.#editor) return;
1247
- // // 1. Kiểm tra xem Editor đang ở chế độ Landscape hay Portrait
1248
- // // (Dựa vào class 'landscape' mà Plugin PageOrientation đã toggle trên View)
1249
- // const rootElement = this.#editor.editing.view.document.getRoot();
1250
- // const isLandscape = rootElement?.hasClass('landscape');
1251
- // const orientation = isLandscape ? 'landscape' : 'portrait';
1252
- // // 2. Lấy nội dung HTML
1253
- // const contentHtml = this.#editor.getData();
1254
- // // 3. Chuẩn bị HTML với CSS @page động
1255
- // const fullHtml = `
1256
- // <!DOCTYPE html>
1257
- // <html>
1258
- // <head>
1259
- // <meta charset="UTF-8">
1260
- // <style>
1261
- // /* --- CẤU HÌNH KHỔ GIẤY DỰA TRÊN TRẠNG THÁI --- */
1262
- // @page {
1263
- // size: A4 ${orientation}; /* Thêm portrait hoặc landscape vào đây */
1264
- // margin: 20mm;
1265
- // }
1266
- // body {
1267
- // font-family: 'Times New Roman', serif;
1268
- // font-size: 13pt;
1269
- // line-height: 1.5;
1270
- // }
1271
- // /* --- STYLE CHO BẢNG BIỂU --- */
1272
- // table {
1273
- // width: 100%;
1274
- // border-collapse: collapse;
1275
- // }
1276
- // td, th {
1277
- // border: 1px solid black;
1278
- // padding: 5px;
1279
- // }
1280
- // /* --- STYLE CHO BIẾN --- */
1281
- // .variable-widget {
1282
- // color: #1565c0;
1283
- // background-color: #e3f2fd;
1284
- // font-weight: bold;
1285
- // border: 1px solid #90caf9;
1286
- // padding: 0 4px;
1287
- // border-radius: 4px;
1288
- // }
1289
- // /* --- ẨN COMMENT KHI IN --- */
1290
- // .ck-comment-marker {
1291
- // background-color: transparent;
1292
- // border: none;
1293
- // }
1294
- // </style>
1295
- // </head>
1296
- // <body>
1297
- // ${contentHtml}
1298
- // </body>
1299
- // </html>
1300
- // `;
1301
- // // 3. Convert sang Blob (Dạng file Binary)
1302
- // asBlob(fullHtml, {
1303
- // orientation: 'portrait', // 'portrait' hoặc 'landscape'
1304
- // margins: { top: 720, right: 720, bottom: 720, left: 720 }, // Đơn vị twips (1440 twips = 1 inch)
1305
- // }).then(blob => {
1306
- // if (blob instanceof Blob) {
1307
- // SdUtilities.downloadBlob(blob, fileName);
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", "@charset \"UTF-8\";:host ::ng-deep .ck-comment-marker{background-color:#ffeb3b80;border-bottom:2px solid #fbc02d;cursor:pointer;transition:background-color .2s}: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;cursor:default;-webkit-user-select:none;user-select:none;display:inline-block;margin:0 4px;vertical-align:middle;font-size:0}: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-clipboard-drop-target-line{display:none!important}: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}\n", "@charset \"UTF-8\";: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", "@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"], 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"] }] });
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", "@charset \"UTF-8\";:host ::ng-deep .ck-comment-marker{background-color:#ffeb3b80;border-bottom:2px solid #fbc02d;cursor:pointer;transition:background-color .2s}: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;cursor:default;-webkit-user-select:none;user-select:none;display:inline-block;margin:0 4px;vertical-align:middle;font-size:0}: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-clipboard-drop-target-line{display:none!important}: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}\n", "@charset \"UTF-8\";: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", "@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"] }]
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 }]