@sd-angular/core 19.0.0-beta.36 → 19.0.0-beta.38

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 (39) hide show
  1. package/assets/scss/core/bootstrap.scss +17 -0
  2. package/assets/scss/core/grid.scss +40 -0
  3. package/assets/scss/sd-core.scss +1 -0
  4. package/components/avatar/src/avatar.component.d.ts +2 -1
  5. package/components/button/src/button.component.d.ts +26 -27
  6. package/components/document-builder/index.d.ts +1 -0
  7. package/components/document-builder/src/document-builder.component.d.ts +3 -42
  8. package/components/document-builder/src/document-builder.model.d.ts +3 -14
  9. package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.d.ts +43 -0
  10. package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.model.d.ts +50 -0
  11. package/components/document-builder/src/plugins/index.d.ts +2 -1
  12. package/components/table/src/components/selector-action/action-filter.pipe.d.ts +11 -10
  13. package/components/table/src/models/table-option-selector.model.d.ts +11 -10
  14. package/fesm2022/sd-angular-core-components-avatar.mjs +15 -13
  15. package/fesm2022/sd-angular-core-components-avatar.mjs.map +1 -1
  16. package/fesm2022/sd-angular-core-components-button.mjs +63 -96
  17. package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
  18. package/fesm2022/sd-angular-core-components-document-builder.mjs +729 -255
  19. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  20. package/fesm2022/sd-angular-core-components-import-excel.mjs +1 -1
  21. package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
  22. package/fesm2022/sd-angular-core-components-preview.mjs +1 -1
  23. package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
  24. package/fesm2022/sd-angular-core-components-table.mjs +6 -6
  25. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  26. package/fesm2022/sd-angular-core-components-upload-file.mjs +1 -1
  27. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  28. package/fesm2022/sd-angular-core-components-workflow.mjs +11 -11
  29. package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
  30. package/fesm2022/sd-angular-core-modules-layout.mjs +3 -3
  31. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  32. package/fesm2022/sd-angular-core-services-confirm.mjs +1 -1
  33. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  34. package/package.json +52 -52
  35. package/sd-angular-core-19.0.0-beta.38.tgz +0 -0
  36. package/utilities/models/index.d.ts +1 -0
  37. package/utilities/models/src/unwrap-signal.model.d.ts +6 -0
  38. package/components/document-builder/src/plugins/comment/comment.plugin.d.ts +0 -4
  39. package/sd-angular-core-19.0.0-beta.36.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, ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter, ClipboardPipeline, FontFamily, Heading, List, Table, TableToolbar, TableProperties, TableCellProperties, TableColumnResize, PageBreak, Undo, Subscript, Superscript, Image, ImageUpload, ImageToolbar, ImageCaption, ImageResize, ImageStyle, ImageBlock, Indent, IndentBlock } from 'ckeditor5';
6
+ import { Plugin, ButtonView, ClassicEditor, Essentials, Paragraph, Bold, Italic, Underline, FontSize, FontColor, FontBackgroundColor, Alignment, Widget, toWidget, ViewUpcastWriter, Matcher, ViewDocument, ViewDomConverter, ClipboardPipeline, ContextualBalloon, FontFamily, Heading, List, Table, TableToolbar, TableProperties, TableCellProperties, TableColumnResize, 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';
@@ -363,99 +363,6 @@ function createHeadingHandler(editor, modelName, headingDefaults) {
363
363
  };
364
364
  }
365
365
 
366
- class CommentPlugin extends Plugin {
367
- init() {
368
- const editor = this.editor;
369
- // 1. MODEL MARKER -> VIEW CSS
370
- editor.conversion.for('downcast').markerToHighlight({
371
- model: 'comment',
372
- view: data => {
373
- return {
374
- classes: 'ck-comment-marker',
375
- attributes: {
376
- 'data-comment-id': data.markerName,
377
- },
378
- };
379
- },
380
- });
381
- // 2. ĐĂNG KÝ UI COMPONENT: 'addCommentBtn'
382
- editor.ui.componentFactory.add('addCommentBtn', locale => {
383
- const view = new ButtonView(locale);
384
- // Lấy config từ Angular
385
- const config = editor.config;
386
- const getOption = config.get('getOption');
387
- const option = getOption?.();
388
- // Ẩn button nếu không có onAddComment
389
- if (!option?.onAddComment) {
390
- view.set({
391
- label: '',
392
- isVisible: false,
393
- });
394
- return view;
395
- }
396
- view.set({
397
- label: 'Thêm bình luận',
398
- 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>',
399
- tooltip: true,
400
- isEnabled: false,
401
- });
402
- // Logic Enable/Disable: Dựa theo Selection
403
- const selection = editor.model.document.selection;
404
- // Lắng nghe sự kiện change selection để bật/tắt nút
405
- this.listenTo(selection, 'change', () => {
406
- // Enable khi có bôi đen text (không phải collapsed)
407
- view.isEnabled = !selection.isCollapsed;
408
- });
409
- // Logic Execute: Khi bấm nút
410
- this.listenTo(view, 'execute', () => {
411
- const range = selection.getFirstRange();
412
- if (!range || !option?.onAddComment)
413
- return;
414
- // Helper lấy text thuần từ range
415
- let selectedText = '';
416
- for (const item of range.getItems()) {
417
- if (item.is('$text') || item.is('$textProxy')) {
418
- selectedText += item.data;
419
- }
420
- }
421
- // BẮN EVENT RA NGOÀI - KHÔNG TỰ ADD MARKER
422
- // Angular component sẽ xử lý logic (mở modal, validation, etc.)
423
- // và gọi lại hàm addComment() nếu cần
424
- option.onAddComment({ range, selectedText });
425
- });
426
- return view;
427
- });
428
- // 3. Xử lý sự kiện Copy (Clipboard Output)
429
- this.listenTo(editor.editing.view.document, 'clipboardOutput', (_, data) => {
430
- const isCopyOrCut = data.method === 'copy' || data.method === 'cut';
431
- // Nếu không phải hành động copy hoặc cut thì thoát hàm
432
- if (!isCopyOrCut) {
433
- return;
434
- }
435
- const content = data.content;
436
- editor.editing.view.change(writer => {
437
- // Tạo range bao quanh toàn bộ nội dung clipboard
438
- const range = writer.createRangeIn(content);
439
- // Mảng chứa các item cần xử lý
440
- const itemsToClean = [];
441
- // 1. Duyệt qua để tìm các thẻ có class ck-comment-marker
442
- for (const item of range.getItems()) {
443
- if (item.is('element') && item.hasClass('ck-comment-marker')) {
444
- itemsToClean.push(item);
445
- }
446
- }
447
- // 2. Thực hiện xóa Class và Attribute
448
- for (const item of itemsToClean) {
449
- // Xóa class 'ck-comment-marker'
450
- writer.removeClass('ck-comment-marker', item);
451
- // Xóa thuộc tính 'data-comment-id'
452
- writer.removeAttribute('data-comment-id', item);
453
- }
454
- });
455
- });
456
- }
457
- }
458
-
459
366
  class VariablePlugin extends Plugin {
460
367
  // Khai báo dependencies nếu cần (VD: Widget)
461
368
  static get requires() {
@@ -3216,6 +3123,703 @@ class BlockSpace extends Plugin {
3216
3123
  }
3217
3124
  }
3218
3125
 
3126
+ var _a;
3127
+ class CkCommentPlugin extends Plugin {
3128
+ static get pluginName() {
3129
+ return 'CkComment';
3130
+ }
3131
+ static get requires() {
3132
+ return [ContextualBalloon];
3133
+ }
3134
+ #comments = new Map();
3135
+ #selectedId = null;
3136
+ #pendingId = null; // ID cho pending highlight
3137
+ #isCreatingPending = false; // Flag để prevent clearing pending khi đang tạo
3138
+ #balloon;
3139
+ #config = {};
3140
+ // Hằng số ID cho pending marker
3141
+ static PENDING_MARKER_ID = '__pending_comment__';
3142
+ // Số node tìm kiếm mặc định khi path không chính xác
3143
+ static DEFAULT_SEARCH_RANGE = 5;
3144
+ // Màu sắc mặc định cho markers
3145
+ static DEFAULT_COLORS = {
3146
+ marker: 'rgba(59, 130, 246, 0.2)',
3147
+ markerSelected: 'rgba(59, 130, 246, 0.5)',
3148
+ markerPending: 'rgba(245, 158, 11, 0.4)',
3149
+ markerModified: 'rgba(255, 193, 7, 0.4)',
3150
+ markerBroken: 'rgba(220, 53, 69, 0.3)',
3151
+ };
3152
+ /**
3153
+ * Debug log - chỉ log khi debug config là true
3154
+ */
3155
+ #log(...args) {
3156
+ if (this.#config.debug) {
3157
+ console.log('[CkCommentPlugin]', ...args);
3158
+ }
3159
+ }
3160
+ /**
3161
+ * Debug warn - chỉ warn khi debug config là true
3162
+ */
3163
+ #warn(...args) {
3164
+ if (this.#config.debug) {
3165
+ console.warn('[CkCommentPlugin]', ...args);
3166
+ }
3167
+ }
3168
+ /**
3169
+ * Lấy màu sắc đã merge với default
3170
+ */
3171
+ #getColors() {
3172
+ return { ..._a.DEFAULT_COLORS, ...this.#config.colors };
3173
+ }
3174
+ init() {
3175
+ const editor = this.editor;
3176
+ this.#balloon = editor.plugins.get(ContextualBalloon);
3177
+ this.#log('init() called');
3178
+ // Thiết lập marker to highlight conversion
3179
+ this.#setupMarkerConversion();
3180
+ // Thiết lập click handler cho markers
3181
+ this.#setupMarkerClickHandler();
3182
+ // Thiết lập toolbar button
3183
+ this.#setupToolbarButton();
3184
+ // Thiết lập ContextualBalloon cho text selection (tùy chọn)
3185
+ this.#setupContextualBalloon();
3186
+ // Theo dõi thay đổi nội dung để cập nhật trạng thái comment
3187
+ this.#setupChangeTracking();
3188
+ }
3189
+ // ========================================================================
3190
+ // TOOLBAR BUTTON
3191
+ // ========================================================================
3192
+ #setupToolbarButton() {
3193
+ const editor = this.editor;
3194
+ editor.ui.componentFactory.add('ckCommentBtn', locale => {
3195
+ const view = new ButtonView(locale);
3196
+ view.set({
3197
+ label: 'Bình luận',
3198
+ icon: '<svg width="16px" height="16px" 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>',
3199
+ tooltip: true,
3200
+ isEnabled: false,
3201
+ });
3202
+ // Enable khi có selection và không phải chỉ khoảng trắng
3203
+ const selection = editor.model.document.selection;
3204
+ this.listenTo(selection, 'change', () => {
3205
+ const isCollapsed = selection.isCollapsed;
3206
+ const range = selection.getFirstRange();
3207
+ // Kiểm tra xem selection có content không phải khoảng trắng không
3208
+ let hasValidContent = false;
3209
+ if (range && !isCollapsed) {
3210
+ const text = this.#getTextFromRange(range);
3211
+ hasValidContent = text.trim().length > 0;
3212
+ }
3213
+ view.isEnabled = hasValidContent;
3214
+ });
3215
+ // Xử lý khi click button
3216
+ this.listenTo(view, 'execute', () => {
3217
+ this.#log('Toolbar button clicked');
3218
+ const selectionData = this.#getSelectionData();
3219
+ if (selectionData) {
3220
+ // Set flag để prevent clearing pending khi selection change
3221
+ this.#isCreatingPending = true;
3222
+ this.#log('Calling onPendingComment callback');
3223
+ this.#config.onPendingComment?.({
3224
+ id: '',
3225
+ startPath: selectionData.startPath,
3226
+ endPath: selectionData.endPath,
3227
+ originalText: selectionData.text,
3228
+ currentText: selectionData.text,
3229
+ status: 'normal',
3230
+ });
3231
+ // Reset flag sau một khoảng ngắn để cho phép clear nếu selection thực sự thay đổi
3232
+ setTimeout(() => {
3233
+ this.#isCreatingPending = false;
3234
+ }, 100);
3235
+ }
3236
+ });
3237
+ return view;
3238
+ });
3239
+ }
3240
+ // ========================================================================
3241
+ // MARKER CONVERSION
3242
+ // ========================================================================
3243
+ #setupMarkerConversion() {
3244
+ const self = this;
3245
+ this.editor.conversion.for('editingDowncast').markerToHighlight({
3246
+ model: 'comment',
3247
+ view: (data) => {
3248
+ const markerName = data.markerName;
3249
+ const commentId = markerName.replace('comment:', '');
3250
+ const classes = ['ck-comment-marker'];
3251
+ const colors = self.#getColors();
3252
+ // Kiểm tra xem có phải pending marker không
3253
+ if (commentId === _a.PENDING_MARKER_ID) {
3254
+ classes.push('ck-comment-pending');
3255
+ return {
3256
+ classes: classes,
3257
+ attributes: {
3258
+ 'data-comment-id': commentId,
3259
+ style: `--comment-pending-bg: ${colors.markerPending}`,
3260
+ },
3261
+ };
3262
+ }
3263
+ const comment = self.#comments.get(commentId);
3264
+ // Build CSS variables based on status
3265
+ const cssVars = [`--comment-bg: ${colors.marker}`];
3266
+ if (comment) {
3267
+ classes.push(`ck-comment-${comment.status}`);
3268
+ if (commentId === self.#selectedId) {
3269
+ classes.push('ck-comment-selected');
3270
+ cssVars.push(`--comment-selected-bg: ${colors.markerSelected}`);
3271
+ }
3272
+ // Add status-specific colors
3273
+ if (comment.status === 'modified') {
3274
+ cssVars.push(`--comment-modified-bg: ${colors.markerModified}`);
3275
+ }
3276
+ else if (comment.status === 'broken') {
3277
+ cssVars.push(`--comment-broken-bg: ${colors.markerBroken}`);
3278
+ }
3279
+ }
3280
+ return {
3281
+ classes: classes,
3282
+ attributes: {
3283
+ 'data-comment-id': commentId,
3284
+ style: cssVars.join('; '),
3285
+ },
3286
+ };
3287
+ },
3288
+ });
3289
+ }
3290
+ // ========================================================================
3291
+ // CLICK HANDLER
3292
+ // ========================================================================
3293
+ #setupMarkerClickHandler() {
3294
+ const viewDocument = this.editor.editing.view.document;
3295
+ // Lắng nghe cả click và mousedown để đảm bảo bắt được event
3296
+ viewDocument.on('mousedown', (evt, data) => {
3297
+ this.#log('Mousedown event triggered, data:', data);
3298
+ this.#handleMarkerClick(evt, data);
3299
+ });
3300
+ viewDocument.on('click', (evt, data) => {
3301
+ this.#log('Click event triggered, data:', data);
3302
+ this.#handleMarkerClick(evt, data);
3303
+ });
3304
+ }
3305
+ #handleMarkerClick(evt, data) {
3306
+ const viewElement = data.target;
3307
+ let element = viewElement;
3308
+ this.#log('Target element:', element, 'hasClass:', typeof element?.hasClass);
3309
+ // Duyệt lên cây để tìm comment marker
3310
+ while (element) {
3311
+ const hasMarkerClass = element.hasClass?.('ck-comment-marker');
3312
+ this.#log('Checking element, hasMarkerClass:', hasMarkerClass);
3313
+ if (hasMarkerClass) {
3314
+ const commentId = element.getAttribute('data-comment-id');
3315
+ this.#log('Found marker with commentId:', commentId);
3316
+ this.selectComment(commentId, false);
3317
+ evt.stop();
3318
+ return;
3319
+ }
3320
+ element = element.parent;
3321
+ }
3322
+ // Click ngoài markers - xóa selection
3323
+ if (this.#selectedId) {
3324
+ this.#log('Click outside markers, clearing selection');
3325
+ this.#selectedId = null;
3326
+ this.#refreshView();
3327
+ }
3328
+ }
3329
+ // ========================================================================
3330
+ // CONTEXTUAL BALLOON
3331
+ // ========================================================================
3332
+ #setupContextualBalloon() {
3333
+ const editor = this.editor;
3334
+ const selection = editor.model.document.selection;
3335
+ this.#log('#setupContextualBalloon initialized');
3336
+ // Lắng nghe selection changes
3337
+ this.listenTo(selection, 'change:range', () => {
3338
+ this.#log('Selection change:range, isCollapsed:', selection.isCollapsed);
3339
+ // Xóa pending nếu selection thay đổi sang text khác
3340
+ // NHƯNG không xóa nếu đang trong quá trình tạo pending
3341
+ if (this.#pendingId && !this.#isCreatingPending) {
3342
+ this.#log('Selection changed, clearing pending');
3343
+ this.clearPendingSelection();
3344
+ }
3345
+ else if (this.#isCreatingPending) {
3346
+ this.#log('Skipping clear pending - isCreatingPending flag is set');
3347
+ }
3348
+ if (!selection.isCollapsed) {
3349
+ const range = selection.getFirstRange();
3350
+ if (range) {
3351
+ // Chỉ hiện balloon khi selection có content không phải khoảng trắng
3352
+ const text = this.#getTextFromRange(range);
3353
+ if (text.trim().length > 0) {
3354
+ this.#showBalloon(range);
3355
+ }
3356
+ else {
3357
+ this.#hideBalloon();
3358
+ }
3359
+ }
3360
+ }
3361
+ else {
3362
+ this.#hideBalloon();
3363
+ }
3364
+ });
3365
+ // Ẩn balloon khi focus thay đổi
3366
+ this.listenTo(editor.ui, 'update', () => {
3367
+ if (selection.isCollapsed) {
3368
+ this.#hideBalloon();
3369
+ }
3370
+ });
3371
+ }
3372
+ #showBalloon(range) {
3373
+ this.#log('#showBalloon called, range:', range);
3374
+ const editor = this.editor;
3375
+ // Ẩn balloon hiện tại trước
3376
+ this.#hideBalloon();
3377
+ // Tạo balloon button
3378
+ const buttonView = new ButtonView(editor.locale);
3379
+ buttonView.set({
3380
+ label: 'Bình luận',
3381
+ icon: '<svg width="16px" height="16px" 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>',
3382
+ tooltip: true,
3383
+ withText: true,
3384
+ });
3385
+ // Xử lý khi click button
3386
+ this.listenTo(buttonView, 'execute', () => {
3387
+ this.#log('Balloon button clicked');
3388
+ const selection = this.#getSelectionData();
3389
+ this.#log('Selection data:', selection);
3390
+ if (selection) {
3391
+ // Set flag để prevent clearing pending khi selection change
3392
+ this.#isCreatingPending = true;
3393
+ this.#log('Calling onPendingComment callback');
3394
+ this.#config.onPendingComment?.({
3395
+ id: '',
3396
+ startPath: selection.startPath,
3397
+ endPath: selection.endPath,
3398
+ originalText: selection.text,
3399
+ currentText: selection.text,
3400
+ status: 'normal',
3401
+ });
3402
+ // Reset flag sau một khoảng ngắn
3403
+ setTimeout(() => {
3404
+ this.#isCreatingPending = false;
3405
+ }, 100);
3406
+ }
3407
+ this.#hideBalloon();
3408
+ });
3409
+ // Thêm vào balloon
3410
+ try {
3411
+ this.#balloon.add({
3412
+ view: buttonView,
3413
+ position: {
3414
+ target: () => {
3415
+ const viewRange = editor.editing.mapper.toViewRange(range);
3416
+ return editor.editing.view.domConverter.viewRangeToDom(viewRange);
3417
+ },
3418
+ },
3419
+ });
3420
+ this.#balloonView = buttonView;
3421
+ this.#log('Balloon added successfully');
3422
+ }
3423
+ catch (e) {
3424
+ this.#warn('Error adding balloon:', e);
3425
+ }
3426
+ }
3427
+ #balloonView = null;
3428
+ #hideBalloon() {
3429
+ if (this.#balloonView) {
3430
+ this.#balloon.remove(this.#balloonView);
3431
+ this.#balloonView = null;
3432
+ }
3433
+ }
3434
+ // ========================================================================
3435
+ // CHANGE TRACKING
3436
+ // ========================================================================
3437
+ #setupChangeTracking() {
3438
+ const editor = this.editor;
3439
+ // Lắng nghe thay đổi dữ liệu
3440
+ editor.model.document.on('change:data', () => {
3441
+ this.#updateCommentStatuses();
3442
+ });
3443
+ // Lắng nghe thay đổi marker
3444
+ editor.model.document.on('change:markers', () => {
3445
+ this.#updateCommentStatuses();
3446
+ });
3447
+ }
3448
+ #updateCommentStatuses() {
3449
+ let hasChanges = false;
3450
+ this.#comments.forEach((comment, id) => {
3451
+ const marker = this.editor.model.markers.get(`comment:${id}`);
3452
+ if (marker) {
3453
+ const range = marker.getRange();
3454
+ const currentText = this.#getTextFromRange(range);
3455
+ // Tự động cập nhật paths (CKEditor duy trì chúng)
3456
+ const newStartPath = Array.from(range.start.path);
3457
+ const newEndPath = Array.from(range.end.path);
3458
+ const pathChanged = JSON.stringify(comment.startPath) !== JSON.stringify(newStartPath) ||
3459
+ JSON.stringify(comment.endPath) !== JSON.stringify(newEndPath);
3460
+ const textChanged = currentText !== comment.currentText;
3461
+ if (pathChanged || textChanged) {
3462
+ hasChanges = true;
3463
+ comment.startPath = newStartPath;
3464
+ comment.endPath = newEndPath;
3465
+ comment.currentText = currentText;
3466
+ // Cập nhật trạng thái
3467
+ if (currentText === comment.originalText) {
3468
+ comment.status = 'normal';
3469
+ }
3470
+ else if (currentText.length === 0) {
3471
+ comment.status = 'broken';
3472
+ }
3473
+ else {
3474
+ comment.status = 'modified';
3475
+ }
3476
+ }
3477
+ }
3478
+ else {
3479
+ // Không tìm thấy marker - bị hỏng
3480
+ if (comment.status !== 'broken') {
3481
+ hasChanges = true;
3482
+ comment.status = 'broken';
3483
+ }
3484
+ }
3485
+ });
3486
+ if (hasChanges) {
3487
+ this.#refreshView();
3488
+ this.#fireOnChange();
3489
+ }
3490
+ }
3491
+ // ========================================================================
3492
+ // PUBLIC API
3493
+ // ========================================================================
3494
+ /**
3495
+ * Thiết lập config với callbacks
3496
+ */
3497
+ setConfig(config) {
3498
+ this.#config = config;
3499
+ }
3500
+ /**
3501
+ * Thêm comment và tạo marker
3502
+ */
3503
+ addComment(comment) {
3504
+ if (this.#comments.has(comment.id)) {
3505
+ this.#warn(`Comment with id ${comment.id} already exists`);
3506
+ return false;
3507
+ }
3508
+ // Tạo marker
3509
+ const success = this.#createMarker(comment);
3510
+ // Lưu comment (với trạng thái broken nếu marker thất bại)
3511
+ const storedComment = success ? { ...comment } : { ...comment, status: 'broken' };
3512
+ this.#comments.set(comment.id, storedComment);
3513
+ this.#refreshView();
3514
+ this.#fireOnChange();
3515
+ // Chỉ fire onAddComment callback KHI thêm thành công (không phải broken)
3516
+ if (success) {
3517
+ this.#config.onAddComment?.(storedComment);
3518
+ }
3519
+ return true;
3520
+ }
3521
+ /**
3522
+ * Xóa comment theo id
3523
+ */
3524
+ removeComment(id) {
3525
+ const comment = this.#comments.get(id);
3526
+ if (!comment) {
3527
+ return false;
3528
+ }
3529
+ // Xóa marker
3530
+ this.editor.model.change(writer => {
3531
+ writer.removeMarker(`comment:${id}`);
3532
+ });
3533
+ // Xóa khỏi map
3534
+ this.#comments.delete(id);
3535
+ // Xóa selection nếu bị xóa
3536
+ if (this.#selectedId === id) {
3537
+ this.#selectedId = null;
3538
+ }
3539
+ this.#refreshView();
3540
+ this.#fireOnChange();
3541
+ return true;
3542
+ }
3543
+ /**
3544
+ * Chọn comment theo id - chỉ thêm class highlight, không bôi đen text
3545
+ */
3546
+ selectComment(id, scrollIntoView = true) {
3547
+ this.#log('selectComment called with id:', id, 'hasComment:', this.#comments.has(id));
3548
+ if (!this.#comments.has(id)) {
3549
+ this.#warn('Comment not found:', id);
3550
+ return;
3551
+ }
3552
+ this.#selectedId = id;
3553
+ this.#refreshView();
3554
+ if (scrollIntoView) {
3555
+ this.#scrollToComment(id);
3556
+ }
3557
+ this.#log('Firing onSelectComment callback for id:', id);
3558
+ this.#config.onSelectComment?.(id);
3559
+ }
3560
+ /**
3561
+ * Thiết lập tất cả comments (khôi phục từ dữ liệu)
3562
+ */
3563
+ setComments(comments) {
3564
+ this.#log('setComments called with', comments.length, 'comments');
3565
+ // Xóa comments hiện tại
3566
+ this.#log('Clearing existing comments, count:', this.#comments.size);
3567
+ this.#comments.forEach((_, id) => {
3568
+ const markerName = `comment:${id}`;
3569
+ if (this.editor.model.markers.has(markerName)) {
3570
+ this.#log('Removing marker:', markerName);
3571
+ this.editor.model.change(writer => {
3572
+ writer.removeMarker(markerName);
3573
+ });
3574
+ }
3575
+ else {
3576
+ this.#warn('Marker not found, skipping:', markerName);
3577
+ }
3578
+ });
3579
+ this.#comments.clear();
3580
+ this.#selectedId = null;
3581
+ // Thêm comments mới - status sẽ được tính toán động từ editor
3582
+ comments.forEach(comment => {
3583
+ const success = this.#createMarker(comment);
3584
+ // Lưu comment với status mặc định, sẽ được cập nhật bởi #updateCommentStatuses
3585
+ const storedComment = {
3586
+ ...comment,
3587
+ status: success ? 'normal' : 'broken',
3588
+ currentText: success ? comment.originalText : '',
3589
+ };
3590
+ this.#comments.set(comment.id, storedComment);
3591
+ });
3592
+ this.#refreshView();
3593
+ this.#fireOnChange();
3594
+ }
3595
+ /**
3596
+ * Lấy tất cả comments
3597
+ */
3598
+ get comments() {
3599
+ return Array.from(this.#comments.values());
3600
+ }
3601
+ /**
3602
+ * Thiết lập pending highlight cho selection (khi user đang nhập nội dung comment)
3603
+ */
3604
+ setPendingSelection(startPath, endPath) {
3605
+ // Xóa pending marker hiện tại MÀ KHÔNG fire callback
3606
+ this.#clearPendingMarker();
3607
+ const model = this.editor.model;
3608
+ try {
3609
+ model.change(writer => {
3610
+ const root = model.document.getRoot();
3611
+ if (!root) {
3612
+ throw new Error('Document root not found');
3613
+ }
3614
+ const startPos = writer.createPositionFromPath(root, startPath);
3615
+ const endPos = writer.createPositionFromPath(root, endPath);
3616
+ const range = writer.createRange(startPos, endPos);
3617
+ writer.addMarker(`comment:${_a.PENDING_MARKER_ID}`, {
3618
+ range,
3619
+ usingOperation: false,
3620
+ affectsData: false,
3621
+ });
3622
+ });
3623
+ this.#pendingId = _a.PENDING_MARKER_ID;
3624
+ this.#refreshView();
3625
+ return true;
3626
+ }
3627
+ catch (e) {
3628
+ this.#warn('Failed to set pending selection:', e);
3629
+ return false;
3630
+ }
3631
+ }
3632
+ /**
3633
+ * Xóa pending marker mà không fire callback (dùng nội bộ)
3634
+ */
3635
+ #clearPendingMarker() {
3636
+ if (!this.#pendingId)
3637
+ return;
3638
+ this.editor.model.change(writer => {
3639
+ writer.removeMarker(`comment:${_a.PENDING_MARKER_ID}`);
3640
+ });
3641
+ this.#pendingId = null;
3642
+ this.#refreshView();
3643
+ }
3644
+ /**
3645
+ * Xóa pending highlight và fire onCancelPending callback
3646
+ */
3647
+ clearPendingSelection() {
3648
+ if (!this.#pendingId)
3649
+ return;
3650
+ this.#clearPendingMarker();
3651
+ // Fire callback để thông báo UI
3652
+ this.#config.onCancelPending?.();
3653
+ }
3654
+ /**
3655
+ * Lấy dữ liệu selection hiện tại để tạo comment
3656
+ * Trim khoảng trắng để tránh sai vị trí khi lưu
3657
+ */
3658
+ #getSelectionData() {
3659
+ const selection = this.editor.model.document.selection;
3660
+ const range = selection.getFirstRange();
3661
+ if (!range || range.isCollapsed) {
3662
+ return null;
3663
+ }
3664
+ const text = this.#getTextFromRange(range);
3665
+ const trimmedText = text.trim();
3666
+ if (!trimmedText) {
3667
+ return null;
3668
+ }
3669
+ // Tính toán số ký tự cần trim ở đầu và cuối
3670
+ const leadingWhitespace = text.length - text.trimStart().length;
3671
+ const trailingWhitespace = text.length - text.trimEnd().length;
3672
+ // Điều chỉnh range để loại bỏ khoảng trắng
3673
+ let adjustedRange = range;
3674
+ if (leadingWhitespace > 0 || trailingWhitespace > 0) {
3675
+ adjustedRange = this.#adjustRangeForTrim(range, leadingWhitespace, trailingWhitespace);
3676
+ }
3677
+ return {
3678
+ range: adjustedRange,
3679
+ startPath: Array.from(adjustedRange.start.path),
3680
+ endPath: Array.from(adjustedRange.end.path),
3681
+ text: trimmedText,
3682
+ };
3683
+ }
3684
+ /**
3685
+ * Điều chỉnh range để loại bỏ khoảng trắng đầu/cuối
3686
+ */
3687
+ #adjustRangeForTrim(range, leadingTrim, trailingTrim) {
3688
+ const model = this.editor.model;
3689
+ return model.change(writer => {
3690
+ let startPos = range.start;
3691
+ let endPos = range.end;
3692
+ // Dịch start position forward để bỏ khoảng trắng đầu
3693
+ if (leadingTrim > 0) {
3694
+ for (let i = 0; i < leadingTrim && startPos; i++) {
3695
+ const nextPos = startPos.getShiftedBy(1);
3696
+ if (nextPos) {
3697
+ startPos = nextPos;
3698
+ }
3699
+ else {
3700
+ break;
3701
+ }
3702
+ }
3703
+ }
3704
+ // Dịch end position backward để bỏ khoảng trắng cuối
3705
+ if (trailingTrim > 0) {
3706
+ for (let i = 0; i < trailingTrim && endPos; i++) {
3707
+ const prevPos = endPos.getShiftedBy(-1);
3708
+ if (prevPos && prevPos.isAfter(startPos)) {
3709
+ endPos = prevPos;
3710
+ }
3711
+ else {
3712
+ break;
3713
+ }
3714
+ }
3715
+ }
3716
+ return writer.createRange(startPos, endPos);
3717
+ });
3718
+ }
3719
+ // ========================================================================
3720
+ // INTERNAL HELPERS
3721
+ // ========================================================================
3722
+ #createMarker(comment) {
3723
+ const model = this.editor.model;
3724
+ try {
3725
+ model.change(writer => {
3726
+ const root = model.document.getRoot();
3727
+ if (!root) {
3728
+ throw new Error('Document root not found');
3729
+ }
3730
+ const startPos = writer.createPositionFromPath(root, comment.startPath);
3731
+ const endPos = writer.createPositionFromPath(root, comment.endPath);
3732
+ const range = writer.createRange(startPos, endPos);
3733
+ // Validate: kiểm tra text tại range có khớp với originalText không
3734
+ const rangeText = this.#getTextFromRange(range);
3735
+ this.#log('Range text:', rangeText, 'range:', range);
3736
+ if (rangeText !== comment.originalText) {
3737
+ this.#warn(`Marker text mismatch for comment ${comment.id}:`, `\n Expected: "${comment.originalText}"`, `\n Got: "${rangeText}"`, `\n Trying to find text near original path...`);
3738
+ // Thử tìm text gần path gốc
3739
+ const searchRange = this.#config.searchRange ?? _a.DEFAULT_SEARCH_RANGE;
3740
+ const foundRange = this.#findTextNearPath(comment.originalText, comment.startPath, comment.endPath, searchRange);
3741
+ if (foundRange) {
3742
+ this.#log('Found text at new position, updating paths');
3743
+ // Cập nhật paths cho comment
3744
+ comment.startPath = Array.from(foundRange.start.path);
3745
+ comment.endPath = Array.from(foundRange.end.path);
3746
+ writer.addMarker(`comment:${comment.id}`, {
3747
+ range: foundRange,
3748
+ usingOperation: true,
3749
+ affectsData: false,
3750
+ });
3751
+ return;
3752
+ }
3753
+ throw new Error(`Failed to find text "${comment.originalText}" near original path`);
3754
+ }
3755
+ writer.addMarker(`comment:${comment.id}`, {
3756
+ range,
3757
+ usingOperation: true, // CKEditor tự động cập nhật vị trí
3758
+ affectsData: false,
3759
+ });
3760
+ });
3761
+ return true;
3762
+ }
3763
+ catch (e) {
3764
+ this.#warn(`Failed to create marker for comment ${comment.id}:`, e);
3765
+ return false;
3766
+ }
3767
+ }
3768
+ /**
3769
+ * Tìm text trong document trong phạm vi ±searchRange nodes từ path gốc
3770
+ */
3771
+ #findTextNearPath(searchText, startPath, endPath, searchRange) {
3772
+ if (!searchText)
3773
+ return null;
3774
+ const model = this.editor.model;
3775
+ const root = model.document.getRoot();
3776
+ if (!root)
3777
+ return null;
3778
+ // TODO: Implement text search logic here
3779
+ // 1. Lấy vị trí start và end từ các path gốc
3780
+ // 2. Tìm kiếm trong phạm vi ±searchRange nodes từ các vị trí đó
3781
+ // 3. Trả về range nếu tìm thấy text, null nếu không tìm thấy
3782
+ return null;
3783
+ }
3784
+ #getTextFromRange(range) {
3785
+ let text = '';
3786
+ for (const item of range.getItems()) {
3787
+ if (item.is('$textProxy') || item.is('$text')) {
3788
+ text += item.data;
3789
+ }
3790
+ }
3791
+ return text;
3792
+ }
3793
+ #scrollToComment(id) {
3794
+ const marker = this.editor.model.markers.get(`comment:${id}`);
3795
+ if (!marker)
3796
+ return;
3797
+ const editor = this.editor;
3798
+ // Scroll đến marker range (không set selection - không bôi đen)
3799
+ const viewRange = editor.editing.mapper.toViewRange(marker.getRange());
3800
+ const domRange = editor.editing.view.domConverter.viewRangeToDom(viewRange);
3801
+ // Scroll element into view
3802
+ const domElement = domRange.startContainer.parentElement;
3803
+ if (domElement) {
3804
+ domElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
3805
+ }
3806
+ }
3807
+ #refreshView() {
3808
+ // Force view refresh để cập nhật marker classes
3809
+ // Reconvert tất cả comment markers để cập nhật classes của chúng
3810
+ const markers = this.editor.model.markers;
3811
+ for (const marker of markers) {
3812
+ if (marker.name.startsWith('comment:')) {
3813
+ this.editor.editing.reconvertMarker(marker.name);
3814
+ }
3815
+ }
3816
+ }
3817
+ #fireOnChange() {
3818
+ this.#config.onChange?.(this.comments);
3819
+ }
3820
+ }
3821
+ _a = CkCommentPlugin;
3822
+
3219
3823
  /**
3220
3824
  * Document Builder Utilities
3221
3825
  * Các hàm tiện ích cho document builder
@@ -3322,7 +3926,6 @@ class SdDocumentBuilder {
3322
3926
  IndentBlock,
3323
3927
  // Custom Plugin
3324
3928
  HeadingPlugin,
3325
- CommentPlugin,
3326
3929
  VariablePlugin,
3327
3930
  TableCustom,
3328
3931
  PageOrientation,
@@ -3331,6 +3934,7 @@ class SdDocumentBuilder {
3331
3934
  HighlightRangePlugin,
3332
3935
  PasteHandler,
3333
3936
  BlockSpace,
3937
+ CkCommentPlugin,
3334
3938
  ],
3335
3939
  toolbar: {
3336
3940
  items: [
@@ -3359,8 +3963,6 @@ class SdDocumentBuilder {
3359
3963
  '|',
3360
3964
  'undo',
3361
3965
  'redo',
3362
- '|',
3363
- 'addCommentBtn',
3364
3966
  ],
3365
3967
  shouldNotGroupWhenFull: true,
3366
3968
  },
@@ -3495,6 +4097,21 @@ class SdDocumentBuilder {
3495
4097
  console.warn('Error setting up indent keybindings:', error);
3496
4098
  }
3497
4099
  this.#updateState();
4100
+ // Setup CkCommentPlugin callbacks
4101
+ this.#setupCkCommentPlugin();
4102
+ }
4103
+ #setupCkCommentPlugin() {
4104
+ if (!this.#editor)
4105
+ return;
4106
+ try {
4107
+ const ckCommentPlugin = this.#editor.plugins.get('CkComment');
4108
+ if (ckCommentPlugin && this.option.comment) {
4109
+ ckCommentPlugin.setConfig(this.option.comment);
4110
+ }
4111
+ }
4112
+ catch (error) {
4113
+ console.warn('CkCommentPlugin not available:', error);
4114
+ }
3498
4115
  }
3499
4116
  setContent = (html) => {
3500
4117
  this.#editor?.setData?.(html);
@@ -3698,163 +4315,20 @@ class SdDocumentBuilder {
3698
4315
  },
3699
4316
  };
3700
4317
  // ========================================================================
3701
- // 2. QUẢN LÝ COMMENT
4318
+ // 2. QUẢN LÝ COMMENT (Marker-based)
3702
4319
  // ========================================================================
3703
- comment = {
3704
- /**
3705
- * Lấy tất cả comments trong document
3706
- * @returns Danh sách tất cả comments
3707
- */
3708
- all: () => {
3709
- if (!this.#editor)
3710
- return [];
3711
- const editableElement = this.#editor.ui.view.editable.element;
3712
- if (!editableElement)
3713
- return [];
3714
- const commentMarkers = editableElement.querySelectorAll('.ck-comment-marker[data-comment-id^="comment:"]') || [];
3715
- const commentsMap = new Map();
3716
- commentMarkers.forEach(el => {
3717
- const markerId = el.getAttribute('data-comment-id');
3718
- if (markerId) {
3719
- const existing = commentsMap.get(markerId);
3720
- const text = el.textContent || '';
3721
- if (existing) {
3722
- existing.selectedText += text;
3723
- }
3724
- else {
3725
- commentsMap.set(markerId, {
3726
- markerId,
3727
- selectedText: text,
3728
- });
3729
- }
3730
- }
3731
- });
3732
- return Array.from(commentsMap.values());
3733
- },
3734
- /**
3735
- * Thêm comment vào vùng text đang được chọn
3736
- * @param comment - Dữ liệu comment
3737
- * @param data - Dữ liệu extra data
3738
- * @returns SdDocumentBuilderComment hoặc null nếu không có text được chọn
3739
- */
3740
- add: (range, comment, args) => {
3741
- // markerIdExternal khi truyền từ bên ngoài vào sẽ có thể là Date.now() hoặc uuidv4().
3742
- // Miễn là đảm bảo markerIdExternal là unique.
3743
- // Phục vụ cho case gọi API comment thành công thì mới sinh ra markerId.
3744
- const { markerIdExternal, data } = args ?? {};
3745
- if (!this.#editor)
3746
- return null;
3747
- const model = this.#editor.model;
3748
- if (!range)
3749
- return null;
3750
- // 3. Lấy text từ range
3751
- const selectedText = this.#getTextFromRange(range);
3752
- if (!selectedText.trim()) {
3753
- console.warn('Selected text is empty');
3754
- return null;
3755
- }
3756
- // 4. Tạo ID unique cho marker
3757
- const markerId = markerIdExternal ? `comment:${markerIdExternal}` : `comment:${Date.now()}`;
3758
- // 5. Tạo marker trong model
3759
- model.change(writer => {
3760
- writer.addMarker(markerId, {
3761
- range,
3762
- usingOperation: true, // Quan trọng: Cho phép undo/redo
3763
- affectsData: true, // Lưu marker vào data khi getData()
3764
- });
3765
- });
3766
- return {
3767
- markerId,
3768
- selectedText,
3769
- comment,
3770
- createdAt: new Date(),
3771
- data,
3772
- };
3773
- },
3774
- /**
3775
- * Cập nhật nội dung comment
3776
- * @param markerId - ID của marker
3777
- * @param commentData - Dữ liệu mới
3778
- * @returns Comment đã cập nhật hoặc null
3779
- */
3780
- update: (markerId, comment, data) => {
3781
- if (!this.#editor)
3782
- return null;
3783
- const marker = this.#editor.model.markers.get(markerId);
3784
- if (!marker) {
3785
- console.warn(`Marker ${markerId} not found`);
3786
- return null;
3787
- }
3788
- // Lấy text hiện tại từ marker (có thể đã thay đổi)
3789
- const currentText = this.#getTextFromRange(marker.getRange());
3790
- return {
3791
- markerId: markerId,
3792
- selectedText: currentText,
3793
- comment,
3794
- data,
3795
- };
3796
- },
3797
- /**
3798
- * Lấy chi tiết comment theo markerId
3799
- * @param markerId - ID của marker
3800
- * @returns Comment hoặc null
3801
- */
3802
- detail: (markerId) => {
3803
- if (!this.#editor)
3804
- return null;
3805
- const marker = this.#editor.model.markers.get(markerId);
3806
- if (!marker)
3807
- return null;
3808
- const currentText = this.#getTextFromRange(marker.getRange());
3809
- return {
3810
- markerId: markerId,
3811
- selectedText: currentText,
3812
- };
3813
- },
3814
- /**
3815
- * Xóa comment theo markerId
3816
- * @param markerId - ID của marker cần xóa
3817
- * @returns true nếu xóa thành công, false nếu không tìm thấy
3818
- */
3819
- remove: (markerId) => {
3820
- if (!this.#editor)
3821
- return false;
3822
- const model = this.#editor.model;
3823
- const marker = model.markers.get(markerId);
3824
- if (!marker) {
3825
- console.warn(`Marker ${markerId} not found`);
3826
- return false;
3827
- }
3828
- model.change(writer => {
3829
- writer.removeMarker(markerId);
3830
- });
3831
- return true;
3832
- },
3833
- /**
3834
- * Scroll đến vị trí comment
3835
- * @param markerId - ID của marker cần scroll tới
3836
- */
3837
- scroll: (markerId) => {
3838
- if (!this.#editor)
3839
- return;
3840
- const editor = this.#editor;
3841
- const marker = editor.model.markers.get(markerId);
3842
- if (marker) {
3843
- // 1. Set Selection vào Marker đó trước
3844
- editor.model.change(writer => {
3845
- writer.setSelection(marker.getRange());
3846
- });
3847
- // 2. Sau đó gọi scrollToTheSelection
3848
- editor.editing.view.scrollToTheSelection({
3849
- alignToTop: true,
3850
- });
3851
- editor.editing.view.focus();
3852
- }
3853
- else {
3854
- console.warn(`Marker with id ${markerId} not found.`);
3855
- }
3856
- },
3857
- };
4320
+ getCommentPluginAPI() {
4321
+ if (!this.#editor)
4322
+ return null;
4323
+ try {
4324
+ const plugin = this.#editor.plugins.get('CkComment');
4325
+ return plugin;
4326
+ }
4327
+ catch (error) {
4328
+ console.warn('CkCommentPlugin not available:', error);
4329
+ return null;
4330
+ }
4331
+ }
3858
4332
  // ========================================================================
3859
4333
  // 3. QUẢN LÝ VARIABLE
3860
4334
  // ========================================================================
@@ -4058,11 +4532,11 @@ class SdDocumentBuilder {
4058
4532
  });
4059
4533
  };
4060
4534
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
4061
- 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.15}: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;font-size:inherit;line-height:inherit;margin-bottom:4px}: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:4px;text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important;margin-bottom:4px}:host ::ng-deep .ck-content li{margin-bottom:0}:host ::ng-deep .ck-content table{margin-bottom:4px}\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"] }] });
4535
+ 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.15}: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;font-size:inherit;line-height:inherit;margin-bottom:4px}: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:4px;text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important;margin-bottom:4px}:host ::ng-deep .ck-content li{margin-bottom:0}:host ::ng-deep .ck-content table{margin-bottom:4px}\n", ":host ::ng-deep .ck-heading-highlight{background-color:#fef08a}\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", ":host ::ng-deep .ck-comment-marker{background-color:var(--comment-bg, rgba(59, 130, 246, .3));border-bottom:2px solid rgb(59,130,246);cursor:pointer;transition:all .2s ease}:host ::ng-deep .ck-comment-marker:hover{opacity:.8}:host ::ng-deep .ck-comment-marker.ck-comment-pending{background-color:var(--comment-pending-bg, rgba(245, 158, 11, .4));border-bottom-color:#f59e0b}:host ::ng-deep .ck-comment-marker.ck-comment-normal{background-color:var(--comment-bg, rgba(59, 130, 246, .2));border-bottom-color:#3b82f6}:host ::ng-deep .ck-comment-marker.ck-comment-selected{background-color:var(--comment-selected-bg, 59, 130, 246, .5);border-bottom-color:#3b82f6}:host ::ng-deep .ck-comment-marker.ck-comment-modified{background-color:var(--comment-modified-bg, rgba(249, 115, 22, .3));border-bottom-color:#f97316}:host ::ng-deep .ck-comment-marker.ck-comment-broken{background-color:var(--comment-broken-bg, rgba(239, 68, 68, .3));border-bottom-color:#ef4444;text-decoration:line-through;text-decoration-color:#ef4444}:host ::ng-deep .ck-balloon-panel .ck-button.ck-on:hover,:host ::ng-deep .ck-balloon-panel .ck-button.ck-off:hover{background:var(--ck-color-button-default-hover-background)}\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"] }] });
4062
4536
  }
4063
4537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, decorators: [{
4064
4538
  type: Component,
4065
- 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.15}: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;font-size:inherit;line-height:inherit;margin-bottom:4px}: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:4px;text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important;margin-bottom:4px}:host ::ng-deep .ck-content li{margin-bottom:0}:host ::ng-deep .ck-content table{margin-bottom:4px}\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"] }]
4539
+ 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.15}: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;font-size:inherit;line-height:inherit;margin-bottom:4px}: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:4px;text-indent:0}:host ::ng-deep .ck-content ul,:host ::ng-deep .ck-content ol{padding-left:20px!important;margin-left:0!important;margin-bottom:4px}:host ::ng-deep .ck-content li{margin-bottom:0}:host ::ng-deep .ck-content table{margin-bottom:4px}\n", ":host ::ng-deep .ck-heading-highlight{background-color:#fef08a}\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", ":host ::ng-deep .ck-comment-marker{background-color:var(--comment-bg, rgba(59, 130, 246, .3));border-bottom:2px solid rgb(59,130,246);cursor:pointer;transition:all .2s ease}:host ::ng-deep .ck-comment-marker:hover{opacity:.8}:host ::ng-deep .ck-comment-marker.ck-comment-pending{background-color:var(--comment-pending-bg, rgba(245, 158, 11, .4));border-bottom-color:#f59e0b}:host ::ng-deep .ck-comment-marker.ck-comment-normal{background-color:var(--comment-bg, rgba(59, 130, 246, .2));border-bottom-color:#3b82f6}:host ::ng-deep .ck-comment-marker.ck-comment-selected{background-color:var(--comment-selected-bg, 59, 130, 246, .5);border-bottom-color:#3b82f6}:host ::ng-deep .ck-comment-marker.ck-comment-modified{background-color:var(--comment-modified-bg, rgba(249, 115, 22, .3));border-bottom-color:#f97316}:host ::ng-deep .ck-comment-marker.ck-comment-broken{background-color:var(--comment-broken-bg, rgba(239, 68, 68, .3));border-bottom-color:#ef4444;text-decoration:line-through;text-decoration-color:#ef4444}:host ::ng-deep .ck-balloon-panel .ck-button.ck-on:hover,:host ::ng-deep .ck-balloon-panel .ck-button.ck-off:hover{background:var(--ck-color-button-default-hover-background)}\n"] }]
4066
4540
  }], propDecorators: { option: [{
4067
4541
  type: Input,
4068
4542
  args: [{ required: true }]