@sd-angular/core 19.0.0-beta.12 → 19.0.0-beta.14

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 (43) hide show
  1. package/assets/scss/ckeditor5.scss +59 -2
  2. package/components/document-builder/src/document-builder.component.d.ts +7 -2
  3. package/components/document-builder/src/document-builder.model.d.ts +5 -1
  4. package/components/document-builder/src/plugins/highlight-range/highlight-range.plugin.d.ts +4 -0
  5. package/components/document-builder/src/plugins/image-custom/image-custom.plugin.d.ts +31 -0
  6. package/components/document-builder/src/plugins/index.d.ts +1 -0
  7. package/components/index.d.ts +1 -0
  8. package/components/mini-editor/index.d.ts +2 -0
  9. package/components/mini-editor/src/mini-editor.component.d.ts +90 -0
  10. package/components/mini-editor/src/mini-editor.model.d.ts +42 -0
  11. package/components/view/index.d.ts +1 -0
  12. package/components/view/src/view.component.d.ts +14 -0
  13. package/directives/index.d.ts +1 -0
  14. package/directives/src/sd-href.directive.d.ts +9 -0
  15. package/fesm2022/sd-angular-core-components-document-builder.mjs +479 -48
  16. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  17. package/fesm2022/sd-angular-core-components-mini-editor.mjs +326 -0
  18. package/fesm2022/sd-angular-core-components-mini-editor.mjs.map +1 -0
  19. package/fesm2022/sd-angular-core-components-view.mjs +88 -0
  20. package/fesm2022/sd-angular-core-components-view.mjs.map +1 -0
  21. package/fesm2022/sd-angular-core-components-workflow.mjs +16 -26
  22. package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
  23. package/fesm2022/sd-angular-core-components.mjs +1 -0
  24. package/fesm2022/sd-angular-core-components.mjs.map +1 -1
  25. package/fesm2022/sd-angular-core-directives.mjs +51 -2
  26. package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
  27. package/fesm2022/sd-angular-core-forms-radio.mjs +3 -2
  28. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  29. package/fesm2022/sd-angular-core-forms-select.mjs +5 -3
  30. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  31. package/fesm2022/sd-angular-core-forms-textarea.mjs +2 -2
  32. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  33. package/fesm2022/sd-angular-core-modules-layout.mjs +52 -17
  34. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  35. package/fesm2022/sd-angular-core-modules-oidc.mjs +0 -2
  36. package/fesm2022/sd-angular-core-modules-oidc.mjs.map +1 -1
  37. package/modules/layout/components/sidebar-v1/components/sidebar/sidebar.component.d.ts +1 -0
  38. package/modules/layout/components/sidebar-v1/components/user/user.component.d.ts +5 -2
  39. package/modules/layout/configurations/layout.configuration.d.ts +3 -0
  40. package/modules/layout/services/storage/storage.service.d.ts +1 -0
  41. package/package.json +32 -24
  42. package/sd-angular-core-19.0.0-beta.14.tgz +0 -0
  43. package/sd-angular-core-19.0.0-beta.12.tgz +0 -0
@@ -3,7 +3,7 @@ import { EventEmitter, Output, Input, Component } from '@angular/core';
3
3
  import { CommonModule } from '@angular/common';
4
4
  import * as i1 from '@ckeditor/ckeditor5-angular';
5
5
  import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
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';
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, ImageBlock, Indent, IndentBlock } from 'ckeditor5';
7
7
  import { Subscription, Subject, throttleTime } from 'rxjs';
8
8
  import { SdResolveMaybeAsync, hslToHex, rgbToHex, SdUtilities } from '@sd-angular/core/utilities';
9
9
  import { v4 } from 'uuid';
@@ -200,7 +200,7 @@ class CommentPlugin extends Plugin {
200
200
  // BẮN EVENT RA NGOÀI - KHÔNG TỰ ADD MARKER
201
201
  // Angular component sẽ xử lý logic (mở modal, validation, etc.)
202
202
  // và gọi lại hàm addComment() nếu cần
203
- option.onAddComment(range);
203
+ option.onAddComment({ range, selectedText });
204
204
  });
205
205
  return view;
206
206
  });
@@ -253,8 +253,8 @@ class VariablePlugin extends Plugin {
253
253
  isObject: true,
254
254
  allowAttributes: ['id', 'uuid', 'value', 'display'],
255
255
  });
256
- // 1. Model -> HTML trên giao diện
257
- conversion.for('editingDowncast').elementToElement({
256
+ // 2. Model -> HTML
257
+ conversion.for('downcast').elementToElement({
258
258
  model: 'variable',
259
259
  view: (modelItem, { writer: viewWriter }) => {
260
260
  const id = modelItem.getAttribute('id');
@@ -273,31 +273,22 @@ class VariablePlugin extends Plugin {
273
273
  return toWidget(widgetElement, viewWriter);
274
274
  },
275
275
  });
276
- // 2. Model -> HTML gửi lên BE
277
- conversion.for('dataDowncast').elementToElement({
278
- model: 'variable',
279
- view: (modelItem, { writer: viewWriter }) => {
280
- const id = modelItem.getAttribute('id');
281
- const uuid = modelItem.getAttribute('uuid');
282
- const display = modelItem.getAttribute('display');
283
- const value = modelItem.getAttribute('value');
284
- const widgetElement = viewWriter.createContainerElement('span', {
285
- class: 'variable-widget',
286
- 'data-id': id,
287
- 'data-uuid': uuid,
288
- 'data-value': value,
289
- 'data-display': display,
290
- });
291
- const innerText = viewWriter.createText(`{{${display}}}`);
292
- viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), innerText);
293
- return widgetElement;
294
- },
295
- });
296
- // 3. HTML -> model của CDK
276
+ // 3. HTML -> Model
297
277
  conversion.for('upcast').elementToElement({
278
+ // NOTE: Khi bổ sung thêm attribute vào element variable, dev nên bổ sung thêm "[atribute]: true" vào view
279
+ // Để:
280
+ // - Nếu lọc chính xác sẽ không sinh ra thẻ span thừa bọc ngoài
281
+ // - Nếu chưa bổ sung thì sẽ sinh ra thẻ <span> bọc ngoài kèm [atribute] chưa lọc
298
282
  view: {
299
283
  name: 'span',
300
- classes: 'variable-widget',
284
+ classes: 'variable-widget ck-widget',
285
+ attributes: {
286
+ 'data-id': true,
287
+ 'data-uuid': true,
288
+ 'data-value': true,
289
+ 'data-display': true,
290
+ contenteditable: true,
291
+ },
301
292
  },
302
293
  model: (viewElement, { writer: modelWriter }) => {
303
294
  return modelWriter.createElement('variable', {
@@ -510,6 +501,52 @@ class VariablePlugin extends Plugin {
510
501
  class TableFitPlugin extends Plugin {
511
502
  init() {
512
503
  const editor = this.editor;
504
+ const applyTableDefaults = (writer, tableElement) => {
505
+ if (!tableElement)
506
+ return;
507
+ // Always force table width to 100% on paste/insert
508
+ writer.setAttribute('tableWidth', '100%', tableElement);
509
+ };
510
+ const applyCellBorders = (writer, tableElement) => {
511
+ if (!tableElement)
512
+ return;
513
+ for (const row of tableElement.getChildren()) {
514
+ for (const cell of row.getChildren()) {
515
+ const hasBorderColor = cell.getAttribute('tableCellBorderColor');
516
+ const hasBorderStyle = cell.getAttribute('tableCellBorderStyle');
517
+ const hasBorderWidth = cell.getAttribute('tableCellBorderWidth');
518
+ const hasPadding = cell.getAttribute('tableCellPadding');
519
+ if (!hasBorderColor) {
520
+ writer.setAttribute('tableCellBorderColor', '#000000', cell);
521
+ }
522
+ if (!hasBorderStyle) {
523
+ writer.setAttribute('tableCellBorderStyle', 'solid', cell);
524
+ }
525
+ if (!hasBorderWidth) {
526
+ writer.setAttribute('tableCellBorderWidth', '1pt', cell);
527
+ }
528
+ if (!hasPadding) {
529
+ writer.setAttribute('tableCellPadding', '0.4em', cell);
530
+ }
531
+ }
532
+ }
533
+ };
534
+ const listenAndApplyOnExecute = (commandName) => {
535
+ const cmd = editor.commands.get(commandName);
536
+ if (!cmd)
537
+ return;
538
+ this.listenTo(cmd, 'execute', () => {
539
+ editor.model.change(writer => {
540
+ const selection = editor.model.document.selection;
541
+ const position = selection.getFirstPosition();
542
+ if (!position)
543
+ return;
544
+ const tableElement = position.findAncestor('table');
545
+ applyTableDefaults(writer, tableElement);
546
+ applyCellBorders(writer, tableElement);
547
+ });
548
+ });
549
+ };
513
550
  // Can thiệp vào quá trình convert từ View (HTML Paste) sang Model
514
551
  editor.conversion.for('upcast').add(dispatcher => {
515
552
  dispatcher.on('element:table', (evt, data, conversionApi) => {
@@ -523,7 +560,8 @@ class TableFitPlugin extends Plugin {
523
560
  for (const item of modelRange.getItems()) {
524
561
  if (item.is('element', 'table')) {
525
562
  editor.model.change(writer => {
526
- writer.setAttribute('tableWidth', '100%', item);
563
+ applyTableDefaults(writer, item);
564
+ applyCellBorders(writer, item);
527
565
  });
528
566
  }
529
567
  }
@@ -533,6 +571,52 @@ class TableFitPlugin extends Plugin {
533
571
  }, { priority: 'low' });
534
572
  // Chạy sau cùng để ghi đè các logic mặc định
535
573
  });
574
+ const findInnerTable = (viewElement) => {
575
+ if (!viewElement)
576
+ return null;
577
+ if (viewElement.name === 'table')
578
+ return viewElement;
579
+ for (const child of viewElement.getChildren()) {
580
+ if (child.name === 'table')
581
+ return child;
582
+ const found = findInnerTable(child);
583
+ if (found)
584
+ return found;
585
+ }
586
+ return null;
587
+ };
588
+ editor.conversion.for('downcast').add(dispatcher => {
589
+ // Handle when tableWidth attribute changes
590
+ dispatcher.on('attribute:tableWidth:table', (evt, data, conversionApi) => {
591
+ const viewWriter = conversionApi.writer;
592
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
593
+ if (!viewElement)
594
+ return;
595
+ const innerTable = findInnerTable(viewElement);
596
+ if (!innerTable)
597
+ return;
598
+ viewWriter.setStyle('border-collapse', 'collapse', innerTable);
599
+ viewWriter.setStyle('margin', '0', innerTable);
600
+ viewWriter.setStyle('width', '100%', innerTable);
601
+ // Set width on the wrapper container (ck-widget) as well
602
+ viewWriter.setStyle('width', '100%', viewElement);
603
+ });
604
+ // Handle when table is first inserted
605
+ dispatcher.on('insert:table', (evt, data, conversionApi) => {
606
+ const viewWriter = conversionApi.writer;
607
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
608
+ if (!viewElement)
609
+ return;
610
+ const innerTable = findInnerTable(viewElement);
611
+ if (!innerTable)
612
+ return;
613
+ viewWriter.setStyle('border-collapse', 'collapse', innerTable);
614
+ viewWriter.setStyle('margin', '0', innerTable);
615
+ viewWriter.setStyle('width', '100%', innerTable);
616
+ // Set width on the wrapper container (ck-widget) as well
617
+ viewWriter.setStyle('width', '100%', viewElement);
618
+ });
619
+ });
536
620
  // Lắng nghe lệnh insertTable để can thiệp ngay sau khi bảng được tạo
537
621
  const insertTableCommand = editor.commands.get('insertTable');
538
622
  if (insertTableCommand) {
@@ -549,11 +633,20 @@ class TableFitPlugin extends Plugin {
549
633
  const tableElement = position.findAncestor('table');
550
634
  if (tableElement) {
551
635
  // Ép width 100% cho bảng mới vẽ
552
- writer.setAttribute('tableWidth', '100%', tableElement);
636
+ applyTableDefaults(writer, tableElement);
637
+ // Apply border style 1pt solid black
638
+ writer.setAttribute('tableBorderColor', '#000000', tableElement);
639
+ writer.setAttribute('tableBorderStyle', 'solid', tableElement);
640
+ writer.setAttribute('tableBorderWidth', '1pt', tableElement);
641
+ applyCellBorders(writer, tableElement);
553
642
  }
554
643
  });
555
644
  });
556
645
  }
646
+ listenAndApplyOnExecute('insertTableRowAbove');
647
+ listenAndApplyOnExecute('insertTableRowBelow');
648
+ listenAndApplyOnExecute('insertTableColumnLeft');
649
+ listenAndApplyOnExecute('insertTableColumnRight');
557
650
  }
558
651
  }
559
652
 
@@ -601,6 +694,243 @@ class Base64UploadAdapter {
601
694
  }
602
695
  }
603
696
 
697
+ class ImageCustomPlugin extends Plugin {
698
+ static get pluginName() {
699
+ return 'ImageCustomPlugin';
700
+ }
701
+ init() {
702
+ const editor = this.editor;
703
+ // Thiết lập style mặc định là alignCenter khi chèn ảnh
704
+ editor.commands.get('imageUpload')?.on('execute', (evt, args) => {
705
+ // Đặt style mặc định sau khi ảnh được chèn
706
+ setTimeout(() => {
707
+ const selection = editor.model.document.selection;
708
+ const imageElement = selection.getSelectedElement();
709
+ if (imageElement && (imageElement.name === 'imageBlock' || imageElement.name === 'imageInline')) {
710
+ const currentStyle = imageElement.getAttribute('imageStyle');
711
+ // Chỉ đặt mặc định nếu chưa có style nào
712
+ if (!currentStyle) {
713
+ editor.model.change(writer => {
714
+ writer.setAttribute('imageStyle', 'alignCenter', imageElement);
715
+ });
716
+ }
717
+ }
718
+ }, 0);
719
+ });
720
+ // Downcast: Model -> View (HTML output)
721
+ // CKEditor 5 có 2 loại ảnh: imageBlock và imageInline
722
+ editor.conversion.for('downcast').add(dispatcher => {
723
+ // Xử lý ảnh block (được wrap trong figure)
724
+ dispatcher.on('insert:imageBlock', (evt, data, conversionApi) => {
725
+ this.handleImageInsert(evt, data, conversionApi);
726
+ }, { priority: 'low' });
727
+ // Xử lý ảnh inline
728
+ dispatcher.on('insert:imageInline', (evt, data, conversionApi) => {
729
+ this.handleImageInsert(evt, data, conversionApi);
730
+ }, { priority: 'low' });
731
+ // Xử lý thay đổi attribute cho cả 2 loại ảnh
732
+ ['imageBlock', 'imageInline'].forEach(imageType => {
733
+ // Xử lý thay đổi src
734
+ dispatcher.on(`attribute:src:${imageType}`, (evt, data, conversionApi) => {
735
+ this.handleImageAttributeChange(evt, data, conversionApi);
736
+ }, { priority: 'low' });
737
+ // Xử lý thay đổi width
738
+ dispatcher.on(`attribute:width:${imageType}`, (evt, data, conversionApi) => {
739
+ this.handleImageAttributeChange(evt, data, conversionApi);
740
+ }, { priority: 'low' });
741
+ // Xử lý thay đổi height
742
+ dispatcher.on(`attribute:height:${imageType}`, (evt, data, conversionApi) => {
743
+ this.handleImageAttributeChange(evt, data, conversionApi);
744
+ }, { priority: 'low' });
745
+ // Xử lý thay đổi imageStyle (căn chỉnh) - thêm float inline style
746
+ dispatcher.on(`attribute:imageStyle:${imageType}`, (evt, data, conversionApi) => {
747
+ this.handleImageStyleChange(evt, data, conversionApi);
748
+ }, { priority: 'low' });
749
+ });
750
+ });
751
+ // Xử lý upcast (HTML paste) - xóa aspect-ratio từ HTML đầu vào
752
+ editor.conversion.for('upcast').add(dispatcher => {
753
+ dispatcher.on('element:img', (evt, data, conversionApi) => {
754
+ const viewItem = data.viewItem;
755
+ if (!viewItem)
756
+ return;
757
+ // Kiểm tra viewItem có các method cần thiết
758
+ if (typeof viewItem.getStyle !== 'function')
759
+ return;
760
+ // Xóa aspect-ratio từ inline styles nếu có
761
+ const hasAspectRatio = viewItem.getStyle('aspect-ratio');
762
+ if (hasAspectRatio && typeof viewItem.removeStyle === 'function') {
763
+ viewItem.removeStyle('aspect-ratio');
764
+ }
765
+ // Đặt custom styles nếu _styles map tồn tại
766
+ if (viewItem._styles && typeof viewItem._styles.set === 'function') {
767
+ viewItem._styles.set('margin', '0');
768
+ viewItem._styles.set('border', '0');
769
+ viewItem._styles.set('max-width', '100%');
770
+ viewItem._styles.set('height', 'auto');
771
+ }
772
+ }, { priority: 'high' });
773
+ });
774
+ }
775
+ /**
776
+ * Xử lý sự kiện chèn ảnh
777
+ */
778
+ handleImageInsert(evt, data, conversionApi) {
779
+ const viewWriter = conversionApi.writer;
780
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
781
+ if (!viewElement)
782
+ return;
783
+ // viewElement có thể là figure (cho block) hoặc img itself (cho inline)
784
+ // Tìm element img thực tế
785
+ const imgElement = this.findImgElement(viewElement);
786
+ if (!imgElement)
787
+ return;
788
+ this.applyCustomStyles(viewWriter, imgElement);
789
+ }
790
+ /**
791
+ * Xử lý sự kiện thay đổi attribute ảnh
792
+ */
793
+ handleImageAttributeChange(evt, data, conversionApi) {
794
+ const viewWriter = conversionApi.writer;
795
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
796
+ if (!viewElement)
797
+ return;
798
+ const imgElement = this.findImgElement(viewElement);
799
+ if (!imgElement)
800
+ return;
801
+ this.applyCustomStyles(viewWriter, imgElement);
802
+ }
803
+ /**
804
+ * Xử lý sự kiện thay đổi style ảnh - thêm float inline style cho căn chỉnh
805
+ */
806
+ handleImageStyleChange(evt, data, conversionApi) {
807
+ const viewWriter = conversionApi.writer;
808
+ const viewElement = conversionApi.mapper.toViewElement(data.item);
809
+ if (!viewElement)
810
+ return;
811
+ // Tìm container ck-widget (element figure)
812
+ const widgetElement = this.findWidgetElement(viewElement);
813
+ if (!widgetElement)
814
+ return;
815
+ // Lấy giá trị imageStyle (căn chỉnh)
816
+ const imageStyle = data.item.getAttribute('imageStyle');
817
+ // Xóa các style hiện có trước
818
+ viewWriter.removeStyle('float', widgetElement);
819
+ viewWriter.removeStyle('margin', widgetElement);
820
+ viewWriter.removeStyle('text-align', widgetElement);
821
+ // Áp dụng style dựa trên căn chỉnh ảnh
822
+ // Các options đã cấu hình: ['inline', 'alignLeft', 'alignRight', 'alignCenter']
823
+ switch (imageStyle) {
824
+ case 'inline':
825
+ // Ảnh inline - không style đặc biệt, chỉ flow inline
826
+ break;
827
+ case 'alignLeft':
828
+ // Float trái
829
+ viewWriter.setStyle('float', 'left', widgetElement);
830
+ viewWriter.setStyle('margin', '0 16px 16px 0', widgetElement);
831
+ break;
832
+ case 'alignRight':
833
+ // Float phải
834
+ viewWriter.setStyle('float', 'right', widgetElement);
835
+ viewWriter.setStyle('margin', '0 0 16px 16px', widgetElement);
836
+ break;
837
+ case 'alignCenter':
838
+ case 'block':
839
+ // Căn giữa
840
+ viewWriter.setStyle('text-align', 'center', widgetElement);
841
+ viewWriter.setStyle('margin', '16px auto', widgetElement);
842
+ break;
843
+ default:
844
+ // Mặc định - căn giữa
845
+ viewWriter.setStyle('text-align', 'center', widgetElement);
846
+ viewWriter.setStyle('margin', '16px auto', widgetElement);
847
+ break;
848
+ }
849
+ // Áp dụng custom styles cơ bản cho element img
850
+ const imgElement = this.findImgElement(viewElement);
851
+ if (imgElement) {
852
+ this.applyCustomStyles(viewWriter, imgElement);
853
+ }
854
+ }
855
+ /**
856
+ * Áp dụng custom styles cho element ảnh
857
+ */
858
+ applyCustomStyles(viewWriter, imgElement) {
859
+ // Xóa aspect-ratio nếu tồn tại
860
+ if (imgElement.getStyle('aspect-ratio')) {
861
+ viewWriter.removeStyle('aspect-ratio', imgElement);
862
+ }
863
+ // Áp dụng custom styles
864
+ viewWriter.setStyle('margin', '0', imgElement);
865
+ viewWriter.setStyle('border', '0', imgElement);
866
+ viewWriter.setStyle('max-width', '100%', imgElement);
867
+ viewWriter.setStyle('height', 'auto', imgElement);
868
+ }
869
+ /**
870
+ * Tìm container ck-widget (element figure)
871
+ * CKEditor wrap ảnh block trong <figure class="ck-widget"><img></figure>
872
+ */
873
+ findWidgetElement(viewElement) {
874
+ if (!viewElement)
875
+ return null;
876
+ // Nếu đây là element figure itself
877
+ if (viewElement.name === 'figure') {
878
+ return viewElement;
879
+ }
880
+ // Cho ảnh inline, trả về element itself (span wrapper)
881
+ if (viewElement.name === 'span') {
882
+ return viewElement;
883
+ }
884
+ // Tìm ngược lên tree để tìm figure/ck-widget
885
+ let current = viewElement;
886
+ while (current) {
887
+ if (current.name === 'figure' || current.name === 'span') {
888
+ return current;
889
+ }
890
+ // Di chuyển lên parent
891
+ if (current.parent) {
892
+ current = current.parent;
893
+ }
894
+ else {
895
+ break;
896
+ }
897
+ }
898
+ // Nếu không tìm thấy widget, trả về element gốc
899
+ return viewElement;
900
+ }
901
+ /**
902
+ * Tìm element img thực tế bên trong widget structure
903
+ * CKEditor wrap ảnh block trong <figure class="ck-widget"><img></figure>
904
+ */
905
+ findImgElement(viewElement) {
906
+ if (!viewElement)
907
+ return null;
908
+ // Nếu đây là element img itself
909
+ if (viewElement.name === 'img') {
910
+ return viewElement;
911
+ }
912
+ // Cho structure widget của CKEditor, tìm đệ quy
913
+ // Ảnh block: figure > span > img
914
+ // Ảnh thường được wrap trong một container
915
+ const queue = [viewElement];
916
+ while (queue.length > 0) {
917
+ const current = queue.shift();
918
+ if (!current)
919
+ continue;
920
+ if (current.name === 'img') {
921
+ return current;
922
+ }
923
+ // Thêm children vào queue
924
+ if (current.getChildren) {
925
+ for (const child of current.getChildren()) {
926
+ queue.push(child);
927
+ }
928
+ }
929
+ }
930
+ return null;
931
+ }
932
+ }
933
+
604
934
  // Icon khổ dọc (Mặc định cũ)
605
935
  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>';
606
936
  // Icon khổ ngang (Mới)
@@ -864,6 +1194,18 @@ function normalize(content) {
864
1194
  return normalized;
865
1195
  }
866
1196
 
1197
+ class HighlightRangePlugin extends Plugin {
1198
+ init() {
1199
+ const editor = this.editor;
1200
+ editor.conversion.for('editingDowncast').markerToHighlight({
1201
+ model: 'highlightRange',
1202
+ view: {
1203
+ classes: 'highlight-range',
1204
+ },
1205
+ });
1206
+ }
1207
+ }
1208
+
867
1209
  class SdDocumentBuilder {
868
1210
  option;
869
1211
  disabled = false;
@@ -918,13 +1260,18 @@ class SdDocumentBuilder {
918
1260
  ImageCaption,
919
1261
  ImageResize,
920
1262
  ImageStyle,
1263
+ ImageBlock,
921
1264
  // Custom Plugin
922
1265
  HeadingPlugin,
923
1266
  CommentPlugin,
924
1267
  VariablePlugin,
925
1268
  TableFitPlugin,
926
- ImageUploadPlugin,
1269
+ Indent,
1270
+ IndentBlock,
927
1271
  PageOrientationPlugin,
1272
+ ImageUploadPlugin,
1273
+ ImageCustomPlugin,
1274
+ HighlightRangePlugin,
928
1275
  ],
929
1276
  toolbar: {
930
1277
  items: [
@@ -959,7 +1306,18 @@ class SdDocumentBuilder {
959
1306
  shouldNotGroupWhenFull: true,
960
1307
  },
961
1308
  image: {
962
- toolbar: ['toggleImageCaption', '|', 'imageStyle:inline', 'imageStyle:block', 'imageStyle:side'],
1309
+ styles: {
1310
+ options: ['inline', 'alignLeft', 'alignRight', 'alignCenter'],
1311
+ },
1312
+ toolbar: [
1313
+ 'imageStyle:inline',
1314
+ 'imageStyle:alignCenter',
1315
+ {
1316
+ name: 'imageStyle:alignDropdown',
1317
+ items: ['imageStyle:alignLeft', 'imageStyle:alignRight'],
1318
+ defaultItem: 'imageStyle:alignLeft',
1319
+ },
1320
+ ],
963
1321
  },
964
1322
  fontSize: {
965
1323
  options: this.#fontSizeOptions,
@@ -985,13 +1343,29 @@ class SdDocumentBuilder {
985
1343
  contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', '|', 'tableProperties', 'tableCellProperties'],
986
1344
  tableProperties: {
987
1345
  borderColors: this.#sharedColors,
1346
+ backgroundColors: this.#sharedColors,
988
1347
  colorPicker: this.#colorPickerConfig,
1348
+ defaultProperties: {
1349
+ borderStyle: 'solid',
1350
+ borderWidth: '1px',
1351
+ borderColor: '#ccc',
1352
+ },
989
1353
  },
990
1354
  tableCellProperties: {
991
1355
  borderColors: this.#sharedColors,
1356
+ backgroundColors: this.#sharedColors,
992
1357
  colorPicker: this.#colorPickerConfig,
1358
+ defaultProperties: {
1359
+ borderStyle: 'solid',
1360
+ borderWidth: '1px',
1361
+ borderColor: '#ccc',
1362
+ },
993
1363
  },
994
1364
  },
1365
+ indentBlock: {
1366
+ offset: 48, // Đơn vị px cho mỗi mức indent (tương đương 0.5 inch)
1367
+ unit: 'px',
1368
+ },
995
1369
  // Quan trọng: Cho phép paste style từ Word nhưng bỏ qua margin/padding
996
1370
  htmlSupport: {
997
1371
  allow: [
@@ -1043,6 +1417,27 @@ class SdDocumentBuilder {
1043
1417
  const content = editor.getData();
1044
1418
  this.#contentChangeSubject.next(content);
1045
1419
  });
1420
+ try {
1421
+ // Manual keybinding cho Tab nếu cần
1422
+ editor.keystrokes.set('Tab', (evt, cancel) => {
1423
+ const command = editor.commands.get('indentBlock');
1424
+ if (command && command.isEnabled) {
1425
+ editor.execute('indentBlock');
1426
+ cancel();
1427
+ }
1428
+ });
1429
+ // Manual keybinding cho Shift+Tab
1430
+ editor.keystrokes.set('Shift+Tab', (evt, cancel) => {
1431
+ const command = editor.commands.get('outdentBlock');
1432
+ if (command && command.isEnabled) {
1433
+ editor.execute('outdentBlock');
1434
+ cancel();
1435
+ }
1436
+ });
1437
+ }
1438
+ catch (error) {
1439
+ console.warn('Error setting up indent keybindings:', error);
1440
+ }
1046
1441
  this.#updateState();
1047
1442
  }
1048
1443
  setContent = (html) => {
@@ -1257,21 +1652,28 @@ class SdDocumentBuilder {
1257
1652
  all: () => {
1258
1653
  if (!this.#editor)
1259
1654
  return [];
1260
- const markers = this.#editor.model.markers;
1261
- const comments = [];
1262
- // Duyệt qua tất cả markers trong Model
1263
- for (const marker of markers) {
1264
- // Chỉ lấy marker do plugin comment tạo ra (prefix 'comment:')
1265
- if (marker.name.startsWith('comment:')) {
1266
- // Lấy text nằm trong vùng marker đó
1267
- const currentText = this.#getTextFromRange(marker.getRange());
1268
- comments.push({
1269
- markerId: marker.name,
1270
- selectedText: currentText,
1271
- });
1655
+ const editableElement = this.#editor.ui.view.editable.element;
1656
+ if (!editableElement)
1657
+ return [];
1658
+ const commentMarkers = editableElement.querySelectorAll('.ck-comment-marker[data-comment-id^="comment:"]') || [];
1659
+ const commentsMap = new Map();
1660
+ commentMarkers.forEach(el => {
1661
+ const markerId = el.getAttribute('data-comment-id');
1662
+ if (markerId) {
1663
+ const existing = commentsMap.get(markerId);
1664
+ const text = el.textContent || '';
1665
+ if (existing) {
1666
+ existing.selectedText += text;
1667
+ }
1668
+ else {
1669
+ commentsMap.set(markerId, {
1670
+ markerId,
1671
+ selectedText: text,
1672
+ });
1673
+ }
1272
1674
  }
1273
- }
1274
- return comments;
1675
+ });
1676
+ return Array.from(commentsMap.values());
1275
1677
  },
1276
1678
  /**
1277
1679
  * Thêm comment vào vùng text đang được chọn
@@ -1279,7 +1681,11 @@ class SdDocumentBuilder {
1279
1681
  * @param data - Dữ liệu extra data
1280
1682
  * @returns SdDocumentBuilderComment hoặc null nếu không có text được chọn
1281
1683
  */
1282
- add: (range, comment, data) => {
1684
+ add: (range, comment, args) => {
1685
+ // markerIdExternal khi truyền từ bên ngoài vào sẽ có thể là Date.now() hoặc uuidv4().
1686
+ // Miễn là đảm bảo markerIdExternal là unique.
1687
+ // Phục vụ cho case gọi API comment thành công thì mới sinh ra markerId.
1688
+ const { markerIdExternal, data } = args ?? {};
1283
1689
  if (!this.#editor)
1284
1690
  return null;
1285
1691
  const model = this.#editor.model;
@@ -1292,7 +1698,7 @@ class SdDocumentBuilder {
1292
1698
  return null;
1293
1699
  }
1294
1700
  // 4. Tạo ID unique cho marker
1295
- const markerId = `comment:${Date.now()}`;
1701
+ const markerId = markerIdExternal ? `comment:${markerIdExternal}` : `comment:${Date.now()}`;
1296
1702
  // 5. Tạo marker trong model
1297
1703
  model.change(writer => {
1298
1704
  writer.addMarker(markerId, {
@@ -1570,12 +1976,37 @@ class SdDocumentBuilder {
1570
1976
  const blob = new Blob(['\ufeff', fullHtml], { type: 'application/msword' });
1571
1977
  SdUtilities.downloadBlob(blob, fileName);
1572
1978
  }
1979
+ hightSelectRange = (range) => {
1980
+ if (!range)
1981
+ return;
1982
+ const editor = this.#editor;
1983
+ editor.model.change(writer => {
1984
+ // Xóa marker cũ (nếu có)
1985
+ if (editor.model.markers.has('highlightRange')) {
1986
+ writer.removeMarker('highlightRange');
1987
+ }
1988
+ // Tạo marker mới
1989
+ writer.addMarker('highlightRange', {
1990
+ usingOperation: false, // Không lưu vào lịch sử Undo/Redo
1991
+ affectsData: false, // Không ảnh hưởng đến data lấy ra (getData)
1992
+ range: range,
1993
+ });
1994
+ });
1995
+ };
1996
+ removeHighlightSeclectRange = () => {
1997
+ const editor = this.#editor;
1998
+ editor.model.change(writer => {
1999
+ if (editor.model.markers.has('highlightRange')) {
2000
+ writer.removeMarker('highlightRange');
2001
+ }
2002
+ });
2003
+ };
1573
2004
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
1574
- 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}\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\";::ng-deep .ck-clipboard-drop-target-line{display:none!important}: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}\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"] }] });
2005
+ 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}:host{display:inline-block}:host ::ng-deep .ck-editor{display:flex;flex-direction:column;align-items:center;width:100%;--ck-content-font-family: \"Times New Roman\", serif !important;--ck-content-font-size: 13pt;--ck-content-line-height: 1.5}:host ::ng-deep .ck-editor__top,:host ::ng-deep .ck-editor__main{border:none!important;box-shadow:none!important}:host ::ng-deep .ck-editor__top{position:sticky;top:0;z-index:100;width:100%;min-width:600px;margin-bottom:10px}:host ::ng-deep .ck-editor__top .ck-sticky-panel__content{border:none!important}:host ::ng-deep .ck-editor__top .ck-toolbar{background:#fff!important;box-shadow:0 4px 6px -1px #0000001a!important;padding:8px!important}:host ::ng-deep .ck-editor__top .ck-toolbar .ck-toolbar__items{display:flex;justify-content:center;flex-wrap:wrap;align-items:center}: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.landscape{width:297mm}:host ::ng-deep .ck-content>*{max-width:100%!important;box-sizing:border-box!important}:host ::ng-deep .ck-content p{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}\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\";::ng-deep .ck-clipboard-drop-target-line{display:none!important}: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}\n", "", ":host ::ng-deep .highlight-range{background-color:#ffeb3b80;border-bottom:2px solid #fbc02d;transition:background-color .2s;cursor:pointer}\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"] }] });
1575
2006
  }
1576
2007
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, decorators: [{
1577
2008
  type: Component,
1578
- 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}\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\";::ng-deep .ck-clipboard-drop-target-line{display:none!important}: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}\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"] }]
2009
+ 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}:host{display:inline-block}:host ::ng-deep .ck-editor{display:flex;flex-direction:column;align-items:center;width:100%;--ck-content-font-family: \"Times New Roman\", serif !important;--ck-content-font-size: 13pt;--ck-content-line-height: 1.5}:host ::ng-deep .ck-editor__top,:host ::ng-deep .ck-editor__main{border:none!important;box-shadow:none!important}:host ::ng-deep .ck-editor__top{position:sticky;top:0;z-index:100;width:100%;min-width:600px;margin-bottom:10px}:host ::ng-deep .ck-editor__top .ck-sticky-panel__content{border:none!important}:host ::ng-deep .ck-editor__top .ck-toolbar{background:#fff!important;box-shadow:0 4px 6px -1px #0000001a!important;padding:8px!important}:host ::ng-deep .ck-editor__top .ck-toolbar .ck-toolbar__items{display:flex;justify-content:center;flex-wrap:wrap;align-items:center}: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.landscape{width:297mm}:host ::ng-deep .ck-content>*{max-width:100%!important;box-sizing:border-box!important}:host ::ng-deep .ck-content p{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}\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\";::ng-deep .ck-clipboard-drop-target-line{display:none!important}: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}\n", ":host ::ng-deep .highlight-range{background-color:#ffeb3b80;border-bottom:2px solid #fbc02d;transition:background-color .2s;cursor:pointer}\n"] }]
1579
2010
  }], propDecorators: { option: [{
1580
2011
  type: Input,
1581
2012
  args: [{ required: true }]