@sd-angular/core 19.0.0-beta.55 → 19.0.0-beta.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/scss/core/form.scss +4 -1
- package/components/button/src/button.component.d.ts +5 -1
- package/components/document-builder/src/document-builder.component.d.ts +3 -14
- package/components/document-builder/src/document-builder.model.d.ts +4 -1
- package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.model.d.ts +1 -0
- package/components/document-builder/src/plugins/variable/variable.plugin.d.ts +39 -0
- package/components/section/src/section.component.d.ts +1 -1
- package/components/side-drawer/src/side-drawer.component.d.ts +11 -23
- package/components/table/src/components/desktop-cell/desktop-cell.component.d.ts +12 -2
- package/components/table/src/components/desktop-cell-view/desktop-cell-view.component.d.ts +12 -2
- package/components/table/src/components/selector-action/selector-action.component.d.ts +5 -3
- package/components/table/src/models/table-column.model.d.ts +1 -0
- package/components/table/src/models/table-option-config.model.d.ts +5 -0
- package/fesm2022/sd-angular-core-components-badge.mjs +11 -11
- package/fesm2022/sd-angular-core-components-badge.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-button.mjs +8 -2
- package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-document-builder.mjs +458 -186
- package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-import-excel.mjs +1 -1
- package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-preview.mjs +1 -1
- package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-section.mjs +3 -3
- package/fesm2022/sd-angular-core-components-section.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-side-drawer.mjs +70 -89
- package/fesm2022/sd-angular-core-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-table.mjs +88 -26
- package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-upload-file.mjs +1 -1
- package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-workflow.mjs +23 -23
- package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +5 -2
- package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-chip.mjs +5 -2
- package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date-range.mjs +4 -3
- package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-date.mjs +4 -3
- package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-datetime.mjs +4 -3
- package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input-number.mjs +3 -2
- package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-input.mjs +6 -4
- package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-select.mjs +3 -2
- package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-textarea.mjs +3 -2
- package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs +5 -5
- package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-confirm.mjs +1 -1
- package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-notify.mjs.map +1 -1
- package/forms/chip/src/chip.component.d.ts +3 -2
- package/forms/chip-calendar/src/chip-calendar.component.d.ts +3 -2
- package/forms/date/src/date.component.d.ts +3 -2
- package/forms/date-range/src/date-range.component.d.ts +3 -2
- package/forms/datetime/src/datetime.component.d.ts +3 -2
- package/forms/input/src/input.component.d.ts +3 -2
- package/forms/input-number/src/input-number.component.d.ts +3 -2
- package/forms/select/src/select.component.d.ts +3 -2
- package/forms/textarea/src/textarea.component.d.ts +3 -2
- package/modules/layout/pipes/high-light-search.pipe.d.ts +1 -1
- package/package.json +55 -55
- package/sd-angular-core-19.0.0-beta.57.tgz +0 -0
- package/services/notify/index.d.ts +1 -0
- package/services/notify/src/notify.model.d.ts +1 -1
- package/services/notify/src/notify.service.d.ts +5 -5
- package/sd-angular-core-19.0.0-beta.55.tgz +0 -0
|
@@ -364,7 +364,10 @@ function createHeadingHandler(editor, modelName, headingDefaults) {
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
class VariablePlugin extends Plugin {
|
|
367
|
-
//
|
|
367
|
+
// Tên plugin đăng ký với CKEditor — bắt buộc để tìm kiếm bằng string và ổn định trong build minified
|
|
368
|
+
static get pluginName() {
|
|
369
|
+
return 'VariablePlugin';
|
|
370
|
+
}
|
|
368
371
|
static get requires() {
|
|
369
372
|
return [Widget];
|
|
370
373
|
}
|
|
@@ -379,9 +382,11 @@ class VariablePlugin extends Plugin {
|
|
|
379
382
|
allowWhere: '$text',
|
|
380
383
|
isInline: true,
|
|
381
384
|
isObject: true,
|
|
382
|
-
allowAttributes: ['id', 'uuid', 'value', 'display'],
|
|
385
|
+
allowAttributes: ['id', 'uuid', 'value', 'display', 'bindingValue'],
|
|
383
386
|
});
|
|
384
387
|
// 2. Model -> HTML
|
|
388
|
+
// model là string 'variable' → chỉ chạy khi element được TẠO MỚI (không reconvert khi attribute đổi)
|
|
389
|
+
// Luôn render trạng thái UNBOUND tại đây — binding state được xử lý riêng bởi converter bên dưới
|
|
385
390
|
conversion.for('downcast').elementToElement({
|
|
386
391
|
model: 'variable',
|
|
387
392
|
view: (modelItem, { writer: viewWriter }) => {
|
|
@@ -395,18 +400,40 @@ class VariablePlugin extends Plugin {
|
|
|
395
400
|
'data-uuid': uuid,
|
|
396
401
|
'data-value': value,
|
|
397
402
|
'data-display': display,
|
|
403
|
+
'data-binding': 'false', // Luôn bắt đầu unbound — binding chỉ set qua bindValue()
|
|
398
404
|
});
|
|
399
405
|
const innerText = viewWriter.createText(`{{${display}}}`);
|
|
400
406
|
viewWriter.insert(viewWriter.createPositionAt(widgetElement, 0), innerText);
|
|
401
407
|
return toWidget(widgetElement, viewWriter);
|
|
402
408
|
},
|
|
403
409
|
});
|
|
410
|
+
// 2b. One-way attribute converter cho bindingValue (chỉ editing view, không ảnh hưởng getData())
|
|
411
|
+
// Chỉ chạy khi bindValue() / clearValue() gọi model.change → setAttribute/removeAttribute
|
|
412
|
+
// Binding được persist vào HTML (getData() và setData() có binding state)
|
|
413
|
+
// conversion.for('downcast') ảnh hưởng cả editing view và data view (getData)
|
|
414
|
+
conversion.for('downcast').add(dispatcher => {
|
|
415
|
+
dispatcher.on('attribute:bindingValue:variable', (evt, data, conversionApi) => {
|
|
416
|
+
if (!conversionApi.consumable.consume(data.item, evt.name))
|
|
417
|
+
return;
|
|
418
|
+
const viewWriter = conversionApi.writer;
|
|
419
|
+
const viewElement = conversionApi.mapper.toViewElement(data.item);
|
|
420
|
+
if (!viewElement)
|
|
421
|
+
return;
|
|
422
|
+
const bindingValue = data.attributeNewValue;
|
|
423
|
+
const isBound = !!bindingValue;
|
|
424
|
+
const display = data.item.getAttribute('display');
|
|
425
|
+
// Cập nhật data-binding attribute trên container span
|
|
426
|
+
viewWriter.setAttribute('data-binding', isBound ? 'true' : 'false', viewElement);
|
|
427
|
+
// Xóa text node cũ và chèn text mới (bound value hoặc {{display}})
|
|
428
|
+
viewWriter.remove(viewWriter.createRangeIn(viewElement));
|
|
429
|
+
viewWriter.insert(viewWriter.createPositionAt(viewElement, 0), viewWriter.createText(isBound ? bindingValue : `{{${display}}}`));
|
|
430
|
+
});
|
|
431
|
+
});
|
|
404
432
|
// 3. HTML -> Model
|
|
405
433
|
conversion.for('upcast').elementToElement({
|
|
406
|
-
// NOTE:
|
|
407
|
-
//
|
|
408
|
-
// - Nếu
|
|
409
|
-
// - Nếu chưa bổ sung thì sẽ sinh ra thẻ <span> bọc ngoài kèm [atribute] chưa lọc
|
|
434
|
+
// NOTE: Chỉ khai báo các attribute CẦN THIẾT để nhận biết variable widget.
|
|
435
|
+
// - data-binding KHÔNG được đưa vào required attributes (HTML cũ không có sẽ không được nhận biết).
|
|
436
|
+
// - Nếu data-binding="true" trong HTML → đọc text content làm bindingValue để restore trạng thái bound.
|
|
410
437
|
view: {
|
|
411
438
|
name: 'span',
|
|
412
439
|
classes: 'variable-widget ck-widget',
|
|
@@ -419,11 +446,23 @@ class VariablePlugin extends Plugin {
|
|
|
419
446
|
},
|
|
420
447
|
},
|
|
421
448
|
model: (viewElement, { writer: modelWriter }) => {
|
|
449
|
+
const isBound = viewElement.getAttribute('data-binding') === 'true';
|
|
450
|
+
let bindingValue;
|
|
451
|
+
if (isBound) {
|
|
452
|
+
// Đọc text content làm bindingValue (bound value được lưu trực tiếp vào innerHTML)
|
|
453
|
+
for (const child of viewElement.getChildren()) {
|
|
454
|
+
if (child.is('$text')) {
|
|
455
|
+
bindingValue = child.data;
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
422
460
|
return modelWriter.createElement('variable', {
|
|
423
461
|
id: viewElement.getAttribute('data-id'),
|
|
424
462
|
uuid: viewElement.getAttribute('data-uuid'),
|
|
425
463
|
value: viewElement.getAttribute('data-value'),
|
|
426
464
|
display: viewElement.getAttribute('data-display'),
|
|
465
|
+
...(bindingValue ? { bindingValue } : {}),
|
|
427
466
|
});
|
|
428
467
|
},
|
|
429
468
|
});
|
|
@@ -443,7 +482,8 @@ class VariablePlugin extends Plugin {
|
|
|
443
482
|
const getOption = config.get('getOption');
|
|
444
483
|
const option = getOption?.();
|
|
445
484
|
if (option?.onDropVariable) {
|
|
446
|
-
|
|
485
|
+
// Bug 4 Fix (Q2-A): Xóa tham số dropIndex — không còn tryền giá trị hardcode 0
|
|
486
|
+
const result = await SdResolveMaybeAsync(option.onDropVariable(variable));
|
|
447
487
|
// * Hỗ trợ dữ liệu có sẵn sẽ chỉ cần nhận vào boolean có cho phép thả hay không?
|
|
448
488
|
if (typeof result === 'boolean') {
|
|
449
489
|
if (!result) {
|
|
@@ -460,11 +500,13 @@ class VariablePlugin extends Plugin {
|
|
|
460
500
|
}
|
|
461
501
|
}
|
|
462
502
|
}
|
|
503
|
+
let insertedUuid = '';
|
|
463
504
|
editor.model.change(writer => {
|
|
464
505
|
// 4.1. Chèn biến
|
|
506
|
+
insertedUuid = v4();
|
|
465
507
|
const variableElem = writer.createElement('variable', {
|
|
466
508
|
id: variable.id,
|
|
467
|
-
uuid:
|
|
509
|
+
uuid: insertedUuid,
|
|
468
510
|
value: variable.value,
|
|
469
511
|
display: variable.display,
|
|
470
512
|
});
|
|
@@ -472,6 +514,9 @@ class VariablePlugin extends Plugin {
|
|
|
472
514
|
// 4.2. Đặt con trỏ ra sau biến
|
|
473
515
|
writer.setSelection(variableElem, 'after');
|
|
474
516
|
});
|
|
517
|
+
// onAfterDropVariable: fires SAU model.change() → variable đã có trong model
|
|
518
|
+
// Consumer có thể gọi variable.all() tại đây và thấy biến mới nhất
|
|
519
|
+
option?.onAfterDropVariable?.({ ...variable, uuid: insertedUuid });
|
|
475
520
|
}
|
|
476
521
|
catch (e) {
|
|
477
522
|
// Đặt con trỏ ngay tại vị trí lỗi
|
|
@@ -493,20 +538,66 @@ class VariablePlugin extends Plugin {
|
|
|
493
538
|
});
|
|
494
539
|
}
|
|
495
540
|
});
|
|
496
|
-
//
|
|
541
|
+
// -------------------------------------------------------------------------
|
|
542
|
+
// 5 & 6. Navigation (Arrow keys + Mouse) + Cursor spacing sau variable
|
|
543
|
+
// -------------------------------------------------------------------------
|
|
544
|
+
// Bug 1 Fix: isNavigating cần được reset sau mỗi lần change:range xử lý xong
|
|
545
|
+
// để tránh logic chèn \u00A0 chạy lặp khi có selection change programmatic.
|
|
497
546
|
let isNavigating = false;
|
|
498
|
-
|
|
547
|
+
// Opt 1: Gộp arrow-key detection + Backspace/Delete handler vào 1 listener keydown
|
|
548
|
+
// Bug 3 Fix: Dùng this.listenTo() thay .on() để CKEditor tự cleanup khi plugin destroy
|
|
549
|
+
this.listenTo(editingView.document, 'keydown', (evt, data) => {
|
|
550
|
+
const keyCode = data.keyCode;
|
|
551
|
+
// --- Arrow key navigation detection (priority: high) ---
|
|
499
552
|
// Mã phím mũi tên: 37 (Left), 38 (Up), 39 (Right), 40 (Down)
|
|
500
|
-
const isArrowKey =
|
|
553
|
+
const isArrowKey = keyCode >= 37 && keyCode <= 40;
|
|
501
554
|
if (isArrowKey) {
|
|
502
555
|
isNavigating = true;
|
|
556
|
+
return; // không xử lý thêm cho arrow key ở đây
|
|
503
557
|
}
|
|
504
|
-
|
|
558
|
+
// Reset flag nếu nhấn phím khác (không phải arrow, không phải Backspace/Delete)
|
|
559
|
+
const btnBackspace = keyCode === 8;
|
|
560
|
+
const btnDelete = keyCode === 46;
|
|
561
|
+
if (!btnBackspace && !btnDelete) {
|
|
505
562
|
isNavigating = false;
|
|
563
|
+
return;
|
|
506
564
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
565
|
+
// --- Opt 1: Backspace / Delete handler (đã gộp vào cùng listener) ---
|
|
566
|
+
// priority: highest — chạy trước mọi handler khác để bắt xóa variable 2 bước
|
|
567
|
+
const selection = editor.model.document.selection;
|
|
568
|
+
const model = editor.model;
|
|
569
|
+
// CASE 1: Nếu con trỏ đang nhấp nháy (Collapsed)
|
|
570
|
+
if (selection.isCollapsed) {
|
|
571
|
+
const position = selection.getFirstPosition();
|
|
572
|
+
// Với Backspace ta kiểm tra nodeBefore, với Delete ta kiểm tra nodeAfter
|
|
573
|
+
const targetNode = btnBackspace ? position?.nodeBefore : position?.nodeAfter;
|
|
574
|
+
if (targetNode && targetNode.is('element', 'variable')) {
|
|
575
|
+
data.preventDefault();
|
|
576
|
+
evt.stop();
|
|
577
|
+
model.change(writer => {
|
|
578
|
+
// Chọn bao quanh Variable đó (lần bấm tiếp theo sẽ xóa)
|
|
579
|
+
writer.setSelection(targetNode, 'on');
|
|
580
|
+
});
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
// CASE 2: Nếu đang có một vùng chọn (đã được highlight từ lần bấm trước)
|
|
585
|
+
else {
|
|
586
|
+
const selectedElement = selection.getSelectedElement();
|
|
587
|
+
// Nếu phần tử đang được chọn chính là variable → xóa hẳn
|
|
588
|
+
if (selectedElement && selectedElement.is('element', 'variable')) {
|
|
589
|
+
data.preventDefault();
|
|
590
|
+
evt.stop();
|
|
591
|
+
model.change(writer => {
|
|
592
|
+
writer.remove(selectedElement);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
// priority: highest — Backspace/Delete phải chạy trước CKEditor default để bắt 2-step deletion
|
|
598
|
+
{ priority: 'highest' });
|
|
599
|
+
// Bug 3 Fix: mousedown cũng dùng this.listenTo() để tránh memory leak
|
|
600
|
+
this.listenTo(editingView.document, 'mousedown', () => {
|
|
510
601
|
isNavigating = true;
|
|
511
602
|
});
|
|
512
603
|
this.listenTo(editor.model.document.selection, 'change:range', () => {
|
|
@@ -514,6 +605,9 @@ class VariablePlugin extends Plugin {
|
|
|
514
605
|
if (!isNavigating) {
|
|
515
606
|
return;
|
|
516
607
|
}
|
|
608
|
+
// Bug 1 Fix: Reset ngay sau khi vào handler để tránh chạy lặp
|
|
609
|
+
// khi có thêm selection change programmatic (VD: writer.insertText bên dưới tự trigger lại)
|
|
610
|
+
isNavigating = false;
|
|
517
611
|
const model = editor.model;
|
|
518
612
|
const selection = model.document.selection;
|
|
519
613
|
if (!selection.isCollapsed)
|
|
@@ -526,105 +620,64 @@ class VariablePlugin extends Plugin {
|
|
|
526
620
|
if (nodeBefore && nodeBefore.is('element', 'variable')) {
|
|
527
621
|
// Lấy node ngay sau variable để kiểm tra
|
|
528
622
|
const nextNode = nodeBefore.nextSibling;
|
|
529
|
-
// Logic: Nếu phía sau KHÔNG CÓ GÌ hoặc KHÔNG PHẢI LÀ TEXT
|
|
623
|
+
// Logic: Nếu phía sau KHÔNG CÓ GÌ hoặc KHÔNG PHẢI LÀ TEXT → chèn \u00A0 để có thể gõ tiếp
|
|
530
624
|
if (!nextNode || !nextNode.is('$text')) {
|
|
531
625
|
model.change(writer => {
|
|
532
|
-
// Chèn thêm con trỏ variable
|
|
533
626
|
writer.insertText('\u00A0', nodeBefore, 'after');
|
|
534
627
|
// Lấy vị trí ngay sau variable (lúc này đang là đầu của text node mới)
|
|
535
628
|
const posAfterVariable = writer.createPositionAfter(nodeBefore);
|
|
536
629
|
// Dịch chuyển vị trí đó sang phải 1 đơn vị (bỏ qua ký tự vừa thêm)
|
|
537
630
|
const targetPos = posAfterVariable.getShiftedBy(1);
|
|
538
|
-
// 3. Đặt con trỏ vào vị trí đã tính toán
|
|
539
631
|
writer.setSelection(targetPos);
|
|
540
632
|
});
|
|
541
633
|
}
|
|
542
634
|
}
|
|
543
635
|
});
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const btnDelete = data.keyCode === 46;
|
|
549
|
-
if (btnBackspace || btnDelete) {
|
|
550
|
-
const selection = editor.model.document.selection;
|
|
551
|
-
const model = editor.model;
|
|
552
|
-
// CASE 1: Nếu con trỏ đang nhấp nháy (Collapsed)
|
|
553
|
-
if (selection.isCollapsed) {
|
|
554
|
-
const position = selection.getFirstPosition();
|
|
555
|
-
// Với Backspace ta kiểm tra nodeBefore, với Delete ta kiểm tra nodeAfter
|
|
556
|
-
const targetNode = data.keyCode === 8 ? position?.nodeBefore : position?.nodeAfter;
|
|
557
|
-
if (targetNode && targetNode.is('element', 'variable')) {
|
|
558
|
-
data.preventDefault();
|
|
559
|
-
evt.stop();
|
|
560
|
-
model.change(writer => {
|
|
561
|
-
// Chọn bao quanh Variable đó
|
|
562
|
-
writer.setSelection(targetNode, 'on');
|
|
563
|
-
});
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// CASE 2: Nếu đang có một vùng chọn (đã được highlight từ lần bấm trước)
|
|
568
|
-
else {
|
|
569
|
-
const selectedElement = selection.getSelectedElement();
|
|
570
|
-
// Nếu phần tử đang được chọn chính là variable
|
|
571
|
-
if (selectedElement && selectedElement.is('element', 'variable')) {
|
|
572
|
-
// Cho phép hành động mặc định diễn ra (CKEditor sẽ tự xóa phần tử đang được chọn)
|
|
573
|
-
// Hoặc chủ động xóa để chắc chắn:
|
|
574
|
-
data.preventDefault();
|
|
575
|
-
evt.stop();
|
|
576
|
-
model.change(writer => {
|
|
577
|
-
writer.remove(selectedElement);
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}, { priority: 'highest' });
|
|
583
|
-
// 8. Xử lý sự kiện Copy (Clipboard Output)
|
|
584
|
-
// Chỉ set thêm text/plain fallback, không thay đổi HTML content
|
|
636
|
+
// -------------------------------------------------------------------------
|
|
637
|
+
// 8. Xử lý sự kiện Copy / Cut (Clipboard Output)
|
|
638
|
+
// -------------------------------------------------------------------------
|
|
639
|
+
// priority: low — chạy sau CKEditor để bổ sung text/plain fallback, không can thiệp HTML
|
|
585
640
|
this.listenTo(editor.editing.view.document, 'clipboardOutput', (evt, data) => {
|
|
586
641
|
const isCopyOrCut = data.method === 'copy' || data.method === 'cut';
|
|
587
642
|
if (!isCopyOrCut)
|
|
588
643
|
return;
|
|
589
|
-
// Set thêm plain text fallback cho external apps
|
|
590
644
|
const dataTransfer = data.dataTransfer;
|
|
591
645
|
const content = data.content;
|
|
592
|
-
//
|
|
646
|
+
// Opt 3: Chỉ visit element nodes, bỏ qua các node không liên quan
|
|
593
647
|
let plainText = '';
|
|
594
648
|
const viewRange = editor.editing.view.createRangeIn(content);
|
|
595
649
|
for (const item of viewRange.getItems()) {
|
|
596
|
-
if (item.is('$text')
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if (display)
|
|
604
|
-
plainText += `{{${display}}}`;
|
|
605
|
-
}
|
|
650
|
+
if (item.is('$text')) {
|
|
651
|
+
plainText += item.data;
|
|
652
|
+
}
|
|
653
|
+
else if (item.is('element', 'span') && item.hasClass('variable-widget')) {
|
|
654
|
+
const display = item.getAttribute('data-display');
|
|
655
|
+
if (display)
|
|
656
|
+
plainText += `{{${display}}}`;
|
|
606
657
|
}
|
|
607
658
|
}
|
|
608
659
|
if (plainText) {
|
|
609
660
|
dataTransfer.setData('text/plain', plainText);
|
|
610
661
|
}
|
|
611
|
-
// HTML content giữ nguyên
|
|
662
|
+
// HTML content giữ nguyên — CKEditor sẽ tự xử lý upcast khi paste lại
|
|
612
663
|
}, { priority: 'low' });
|
|
664
|
+
// -------------------------------------------------------------------------
|
|
613
665
|
// 9. Xử lý sự kiện Paste (Clipboard Input)
|
|
666
|
+
// -------------------------------------------------------------------------
|
|
614
667
|
// Nếu paste từ external source (chỉ có text, không có HTML variable)
|
|
615
668
|
// thì chuyển {{text}} thành variable widget
|
|
616
|
-
this.listenTo(editor.editing.view.document, 'clipboardInput', (evt, data) => {
|
|
669
|
+
this.listenTo(editor.editing.view.document, 'clipboardInput', async (evt, data) => {
|
|
617
670
|
const dataTransfer = data.dataTransfer;
|
|
618
671
|
// Nếu có HTML chứa variable-widget thì để CKEditor xử lý (upcast converter)
|
|
619
672
|
const html = dataTransfer.getData('text/html');
|
|
620
673
|
if (html && html.includes('variable-widget')) {
|
|
621
|
-
return;
|
|
674
|
+
return;
|
|
622
675
|
}
|
|
623
676
|
// Chỉ xử lý nếu chỉ có plain text với pattern {{text}}
|
|
624
|
-
|
|
677
|
+
const text = dataTransfer.getData('text/plain');
|
|
625
678
|
if (!text)
|
|
626
679
|
return;
|
|
627
|
-
// Kiểm tra có chứa pattern {{text}} không
|
|
680
|
+
// Kiểm tra có chứa pattern {{text}} không
|
|
628
681
|
const variablePattern = /\{\{([^}]+)\}\}/g;
|
|
629
682
|
if (!variablePattern.test(text)) {
|
|
630
683
|
return;
|
|
@@ -632,53 +685,85 @@ class VariablePlugin extends Plugin {
|
|
|
632
685
|
// Reset lastIndex sau khi test
|
|
633
686
|
variablePattern.lastIndex = 0;
|
|
634
687
|
evt.stop();
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
688
|
+
// Bug 2 Fix (Q1-A): Lấy option để gọi onPasteVariable callback nếu có
|
|
689
|
+
const config = editor.config;
|
|
690
|
+
const getOption = config.get('getOption');
|
|
691
|
+
const option = getOption?.();
|
|
692
|
+
// Tách text thành các phần: normal text và variables
|
|
693
|
+
let lastIndex = 0;
|
|
694
|
+
let match;
|
|
695
|
+
const fragments = [];
|
|
696
|
+
while ((match = variablePattern.exec(text)) !== null) {
|
|
697
|
+
// Thêm text trước variable
|
|
698
|
+
if (match.index > lastIndex) {
|
|
699
|
+
fragments.push({ type: 'text', content: text.slice(lastIndex, match.index) });
|
|
700
|
+
}
|
|
701
|
+
fragments.push({ type: 'variable', content: match[0], display: match[1] });
|
|
702
|
+
lastIndex = match.index + match[0].length;
|
|
703
|
+
}
|
|
704
|
+
// Thêm text còn lại sau variable cuối cùng
|
|
705
|
+
if (lastIndex < text.length) {
|
|
706
|
+
fragments.push({ type: 'text', content: text.slice(lastIndex) });
|
|
707
|
+
}
|
|
708
|
+
// Bug 2 Fix: Resolve tất cả variable fragments trước khi thực hiện model.change
|
|
709
|
+
// Tránh async operation bên trong model.change (CKEditor không hỗ trợ async writer)
|
|
710
|
+
// Xây dựng lookup map từ variables đang có trong document (theo display name)
|
|
711
|
+
// Dùng để fallback khi không có onPasteVariable — giữ lại đầy đủ id/value của biến gốc
|
|
712
|
+
// khi copy-paste nội bộ bị mất HTML (chỉ còn plain text {{display}})
|
|
713
|
+
const existingVariableMap = this.#buildDisplayMap();
|
|
714
|
+
const resolvedFragments = [];
|
|
715
|
+
for (const fragment of fragments) {
|
|
716
|
+
if (fragment.type === 'variable' && fragment.display) {
|
|
717
|
+
let resolved = null;
|
|
718
|
+
if (option?.onPasteVariable) {
|
|
719
|
+
try {
|
|
720
|
+
resolved = await SdResolveMaybeAsync(option.onPasteVariable(fragment.display));
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
console.warn(`[VariablePlugin] onPasteVariable("${fragment.display}") thất bại:`, e);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// Fallback 1: Tra cứu variable đang có trong document theo display name
|
|
727
|
+
// → Giữ lại đầy đủ id/value khi copy-paste nội bộ bị mất HTML
|
|
728
|
+
if (!resolved) {
|
|
729
|
+
resolved = existingVariableMap.get(fragment.display) ?? null;
|
|
651
730
|
}
|
|
652
|
-
//
|
|
653
|
-
|
|
654
|
-
|
|
731
|
+
// Fallback 2: Không tìm thấy → tạo variable sentinel với id = ''
|
|
732
|
+
// Consumer có thể filter qua variable.all() và nhận biết bằng id === ''
|
|
733
|
+
resolvedFragments.push({
|
|
655
734
|
type: 'variable',
|
|
656
|
-
content:
|
|
657
|
-
|
|
735
|
+
content: fragment.content,
|
|
736
|
+
variable: resolved ?? {
|
|
737
|
+
id: '',
|
|
738
|
+
uuid: v4(),
|
|
739
|
+
value: fragment.display,
|
|
740
|
+
display: fragment.display,
|
|
741
|
+
},
|
|
658
742
|
});
|
|
659
|
-
lastIndex = match.index + match[0].length;
|
|
660
743
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
fragments.push({
|
|
664
|
-
type: 'text',
|
|
665
|
-
content: text.slice(lastIndex),
|
|
666
|
-
});
|
|
744
|
+
else {
|
|
745
|
+
resolvedFragments.push({ type: 'text', content: fragment.content });
|
|
667
746
|
}
|
|
668
|
-
|
|
747
|
+
}
|
|
748
|
+
// Chèn từng fragment vào document
|
|
749
|
+
editor.model.change(writer => {
|
|
750
|
+
const selection = editor.model.document.selection;
|
|
751
|
+
const position = selection.getFirstPosition();
|
|
752
|
+
if (!position)
|
|
753
|
+
return;
|
|
669
754
|
let currentPosition = position;
|
|
670
|
-
for (const fragment of
|
|
755
|
+
for (const fragment of resolvedFragments) {
|
|
671
756
|
if (fragment.type === 'text' && fragment.content) {
|
|
672
757
|
const textNode = writer.createText(fragment.content);
|
|
673
758
|
writer.insert(textNode, currentPosition);
|
|
674
759
|
currentPosition = writer.createPositionAfter(textNode);
|
|
675
760
|
}
|
|
676
|
-
else if (fragment.type === 'variable' && fragment.
|
|
761
|
+
else if (fragment.type === 'variable' && fragment.variable) {
|
|
677
762
|
const variableElem = writer.createElement('variable', {
|
|
678
|
-
id:
|
|
679
|
-
uuid: v4(),
|
|
680
|
-
value: fragment.
|
|
681
|
-
display: fragment.display,
|
|
763
|
+
id: fragment.variable.id,
|
|
764
|
+
uuid: fragment.variable.uuid ?? v4(),
|
|
765
|
+
value: fragment.variable.value,
|
|
766
|
+
display: fragment.variable.display,
|
|
682
767
|
});
|
|
683
768
|
writer.insert(variableElem, currentPosition);
|
|
684
769
|
currentPosition = writer.createPositionAfter(variableElem);
|
|
@@ -698,6 +783,204 @@ class VariablePlugin extends Plugin {
|
|
|
698
783
|
typeof obj.value === 'string' &&
|
|
699
784
|
typeof obj.display === 'string');
|
|
700
785
|
};
|
|
786
|
+
/**
|
|
787
|
+
* Quét tất cả variable elements trong document hiện tại, trả về Map<display, SdDocumentBuilderVariable>.
|
|
788
|
+
* Dùng làm fallback khi paste {{display}} mà không có onPasteVariable callback:
|
|
789
|
+
* nếu document đã có variable cùng display → tái sử dụng id/value của biến gốc.
|
|
790
|
+
* Nếu có nhiều variable cùng display → lấy cái đầu tiên tìm được.
|
|
791
|
+
*/
|
|
792
|
+
#buildDisplayMap = () => {
|
|
793
|
+
const map = new Map();
|
|
794
|
+
const root = this.editor.model.document.getRoot();
|
|
795
|
+
if (!root)
|
|
796
|
+
return map;
|
|
797
|
+
const range = this.editor.model.createRangeIn(root);
|
|
798
|
+
for (const item of range.getItems()) {
|
|
799
|
+
if (item.is('element', 'variable')) {
|
|
800
|
+
const display = item.getAttribute('display');
|
|
801
|
+
// Chỉ lưu lần đầu tiên gặp display này
|
|
802
|
+
if (display && !map.has(display)) {
|
|
803
|
+
map.set(display, {
|
|
804
|
+
id: item.getAttribute('id'),
|
|
805
|
+
uuid: item.getAttribute('uuid'),
|
|
806
|
+
value: item.getAttribute('value'),
|
|
807
|
+
display,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return map;
|
|
813
|
+
};
|
|
814
|
+
// =========================================================================
|
|
815
|
+
// PUBLIC API — Variable management
|
|
816
|
+
// =========================================================================
|
|
817
|
+
/**
|
|
818
|
+
* Lấy tất cả variables trong document.
|
|
819
|
+
* @returns Danh sách tất cả variables (bao gồm bindingValue nếu đã binding)
|
|
820
|
+
*/
|
|
821
|
+
all() {
|
|
822
|
+
const model = this.editor.model;
|
|
823
|
+
const root = model.document.getRoot();
|
|
824
|
+
if (!root)
|
|
825
|
+
return [];
|
|
826
|
+
const variables = [];
|
|
827
|
+
try {
|
|
828
|
+
const range = model.createRangeIn(root);
|
|
829
|
+
for (const item of range.getItems()) {
|
|
830
|
+
if (item.is('element', 'variable')) {
|
|
831
|
+
variables.push({
|
|
832
|
+
id: item.getAttribute('id'),
|
|
833
|
+
uuid: item.getAttribute('uuid'),
|
|
834
|
+
value: item.getAttribute('value'),
|
|
835
|
+
display: item.getAttribute('display'),
|
|
836
|
+
bindingValue: item.getAttribute('bindingValue'),
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
catch (e) {
|
|
842
|
+
console.error(e);
|
|
843
|
+
return [];
|
|
844
|
+
}
|
|
845
|
+
return variables;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Scroll tới vị trí của variable theo uuid.
|
|
849
|
+
* @param uuid - uuid của variable (FE tự sinh sau mỗi lần drop)
|
|
850
|
+
*/
|
|
851
|
+
scroll(uuid) {
|
|
852
|
+
const model = this.editor.model;
|
|
853
|
+
const root = model.document.getRoot();
|
|
854
|
+
if (!root)
|
|
855
|
+
return;
|
|
856
|
+
let targetElement = null;
|
|
857
|
+
const range = model.createRangeIn(root);
|
|
858
|
+
for (const item of range.getItems()) {
|
|
859
|
+
if (item.is('element', 'variable') && item.getAttribute('uuid') === uuid) {
|
|
860
|
+
targetElement = item;
|
|
861
|
+
break;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (targetElement) {
|
|
865
|
+
const viewElement = this.editor.editing.mapper.toViewElement(targetElement);
|
|
866
|
+
if (viewElement) {
|
|
867
|
+
const domElement = this.editor.editing.view.domConverter.viewToDom(viewElement);
|
|
868
|
+
if (domElement) {
|
|
869
|
+
domElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
870
|
+
model.change(writer => {
|
|
871
|
+
writer.setSelection(targetElement, 'on');
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
console.warn(`Variable với uuid "${uuid}" không tìm thấy trong tài liệu.`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Gán giá trị cho TẤT CẢ variable có cùng id trong document.
|
|
882
|
+
* Nếu value rỗng → tự động gọi clearValue(id).
|
|
883
|
+
* @param id - id của variable definition
|
|
884
|
+
* @param value - giá trị binding cần hiển thị
|
|
885
|
+
* @returns số instance đã được cập nhật
|
|
886
|
+
*/
|
|
887
|
+
bindValue(id, value) {
|
|
888
|
+
if (!value)
|
|
889
|
+
return this.clearValue(id);
|
|
890
|
+
const model = this.editor.model;
|
|
891
|
+
const root = model.document.getRoot();
|
|
892
|
+
if (!root)
|
|
893
|
+
return 0;
|
|
894
|
+
let count = 0;
|
|
895
|
+
model.change(writer => {
|
|
896
|
+
const range = model.createRangeIn(root);
|
|
897
|
+
for (const item of range.getItems()) {
|
|
898
|
+
if (item.is('element', 'variable') && item.getAttribute('id') === id) {
|
|
899
|
+
writer.setAttribute('bindingValue', value, item);
|
|
900
|
+
count++;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
});
|
|
904
|
+
return count;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Xóa binding value của TẤT CẢ variable có cùng id.
|
|
908
|
+
* @param id - id của variable definition
|
|
909
|
+
* @returns số instance đã được cập nhật
|
|
910
|
+
*/
|
|
911
|
+
clearValue(id) {
|
|
912
|
+
const model = this.editor.model;
|
|
913
|
+
const root = model.document.getRoot();
|
|
914
|
+
if (!root)
|
|
915
|
+
return 0;
|
|
916
|
+
let count = 0;
|
|
917
|
+
model.change(writer => {
|
|
918
|
+
const range = model.createRangeIn(root);
|
|
919
|
+
for (const item of range.getItems()) {
|
|
920
|
+
if (item.is('element', 'variable') && item.getAttribute('id') === id && item.hasAttribute('bindingValue')) {
|
|
921
|
+
writer.removeAttribute('bindingValue', item);
|
|
922
|
+
count++;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
return count;
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Batch bind nhiều variables theo map { id → value }.
|
|
930
|
+
* Toàn bộ thực hiện trong 1 model.change() → 1 undo step duy nhất.
|
|
931
|
+
* @param map - { [id]: boundValue }
|
|
932
|
+
*/
|
|
933
|
+
bindValues(map) {
|
|
934
|
+
if (!Object.keys(map).length)
|
|
935
|
+
return;
|
|
936
|
+
const model = this.editor.model;
|
|
937
|
+
const root = model.document.getRoot();
|
|
938
|
+
if (!root)
|
|
939
|
+
return;
|
|
940
|
+
model.change(writer => {
|
|
941
|
+
const range = model.createRangeIn(root);
|
|
942
|
+
for (const item of range.getItems()) {
|
|
943
|
+
if (!item.is('element', 'variable'))
|
|
944
|
+
continue;
|
|
945
|
+
const id = item.getAttribute('id');
|
|
946
|
+
if (!(id in map))
|
|
947
|
+
continue;
|
|
948
|
+
const value = map[id];
|
|
949
|
+
if (value) {
|
|
950
|
+
writer.setAttribute('bindingValue', value, item);
|
|
951
|
+
}
|
|
952
|
+
else if (item.hasAttribute('bindingValue')) {
|
|
953
|
+
writer.removeAttribute('bindingValue', item);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Batch clear binding của nhiều variables.
|
|
960
|
+
* @param ids - danh sách id cần clear; nếu không truyền/rỗng → clear TẤT CẢ
|
|
961
|
+
*/
|
|
962
|
+
clearValues(ids) {
|
|
963
|
+
const model = this.editor.model;
|
|
964
|
+
const root = model.document.getRoot();
|
|
965
|
+
if (!root)
|
|
966
|
+
return;
|
|
967
|
+
const idSet = ids?.length ? new Set(ids) : null;
|
|
968
|
+
model.change(writer => {
|
|
969
|
+
const range = model.createRangeIn(root);
|
|
970
|
+
for (const item of range.getItems()) {
|
|
971
|
+
if (!item.is('element', 'variable') || !item.hasAttribute('bindingValue'))
|
|
972
|
+
continue;
|
|
973
|
+
const id = item.getAttribute('id');
|
|
974
|
+
if (!idSet || idSet.has(id)) {
|
|
975
|
+
writer.removeAttribute('bindingValue', item);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
/** Xóa toàn bộ binding values trong document. Shorthand của clearValues(). */
|
|
981
|
+
clearAllValues() {
|
|
982
|
+
this.clearValues();
|
|
983
|
+
}
|
|
701
984
|
}
|
|
702
985
|
|
|
703
986
|
class TableCustom extends Plugin {
|
|
@@ -3201,9 +3484,14 @@ class CkCommentPlugin extends Plugin {
|
|
|
3201
3484
|
tooltip: true,
|
|
3202
3485
|
isEnabled: false,
|
|
3203
3486
|
});
|
|
3204
|
-
// Enable khi có selection
|
|
3487
|
+
// Enable khi có selection, không phải chỉ khoảng trắng, và allowCreating = true
|
|
3205
3488
|
const selection = editor.model.document.selection;
|
|
3206
3489
|
this.listenTo(selection, 'change', () => {
|
|
3490
|
+
// Disabled ngay khi allowCreating = false
|
|
3491
|
+
if (!(this.#config.allowCreating ?? true)) {
|
|
3492
|
+
view.isEnabled = false;
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3207
3495
|
const isCollapsed = selection.isCollapsed;
|
|
3208
3496
|
const range = selection.getFirstRange();
|
|
3209
3497
|
// Kiểm tra xem selection có content không phải khoảng trắng không
|
|
@@ -3419,6 +3707,11 @@ class CkCommentPlugin extends Plugin {
|
|
|
3419
3707
|
this.#log('Skipping clear pending - isCreatingPending flag is set');
|
|
3420
3708
|
}
|
|
3421
3709
|
if (!selection.isCollapsed) {
|
|
3710
|
+
// Chỉ hiện balloon khi allowCreating = true (mặc định)
|
|
3711
|
+
if (!(this.#config.allowCreating ?? true)) {
|
|
3712
|
+
this.#hideBalloon();
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3422
3715
|
const range = selection.getFirstRange();
|
|
3423
3716
|
if (range) {
|
|
3424
3717
|
// Chỉ hiện balloon khi selection có content không phải khoảng trắng và không vượt quá max length
|
|
@@ -3740,6 +4033,15 @@ class CkCommentPlugin extends Plugin {
|
|
|
3740
4033
|
if (!range || range.isCollapsed) {
|
|
3741
4034
|
return null;
|
|
3742
4035
|
}
|
|
4036
|
+
// Validate: start/end không được nằm bên trong isObject element (path.length > 2)
|
|
4037
|
+
// Trường hợp xảy ra khi user drag-select qua bound variable widget
|
|
4038
|
+
if (range.start.path.length > 2 || range.end.path.length > 2) {
|
|
4039
|
+
this.#warn('Selection contains invalid path (inside isObject element) - aborting comment creation', {
|
|
4040
|
+
startPath: Array.from(range.start.path),
|
|
4041
|
+
endPath: Array.from(range.end.path),
|
|
4042
|
+
});
|
|
4043
|
+
return null;
|
|
4044
|
+
}
|
|
3743
4045
|
const text = this.#getTextFromRange(range);
|
|
3744
4046
|
const trimmedText = text.trim();
|
|
3745
4047
|
if (!trimmedText) {
|
|
@@ -3765,6 +4067,11 @@ class CkCommentPlugin extends Plugin {
|
|
|
3765
4067
|
if (leadingWhitespace > 0 || trailingWhitespace > 0) {
|
|
3766
4068
|
adjustedRange = this.#adjustRangeForTrim(range, leadingWhitespace, trailingWhitespace);
|
|
3767
4069
|
}
|
|
4070
|
+
// Validate lại sau khi trim
|
|
4071
|
+
if (adjustedRange.start.path.length > 2 || adjustedRange.end.path.length > 2) {
|
|
4072
|
+
this.#warn('Adjusted range has invalid path depth - aborting');
|
|
4073
|
+
return null;
|
|
4074
|
+
}
|
|
3768
4075
|
return {
|
|
3769
4076
|
range: adjustedRange,
|
|
3770
4077
|
startPath: Array.from(adjustedRange.start.path),
|
|
@@ -3773,10 +4080,32 @@ class CkCommentPlugin extends Plugin {
|
|
|
3773
4080
|
};
|
|
3774
4081
|
}
|
|
3775
4082
|
/**
|
|
3776
|
-
* Điều chỉnh range để loại bỏ khoảng trắng đầu/cuối
|
|
4083
|
+
* Điều chỉnh range để loại bỏ khoảng trắng đầu/cuối.
|
|
4084
|
+
* Đảm bảo các vị trí sau khi dịch chuyển không rơi vào bên trong isObject element
|
|
4085
|
+
* (ví dụ: variable widget đã bound) để tránh lỗi document-selection-wrong-position.
|
|
3777
4086
|
*/
|
|
3778
4087
|
#adjustRangeForTrim(range, leadingTrim, trailingTrim) {
|
|
3779
4088
|
const model = this.editor.model;
|
|
4089
|
+
/** Kiểm tra pos có hợp lệ (không nằm bên trong isObject element) */
|
|
4090
|
+
const isValidPosition = (pos) => {
|
|
4091
|
+
try {
|
|
4092
|
+
// path.length > 2 nghĩa là position nằm sâu hơn paragraph → bên trong element
|
|
4093
|
+
if (pos.path && pos.path.length > 2)
|
|
4094
|
+
return false;
|
|
4095
|
+
// Kiểm tra node tại vị trí đó
|
|
4096
|
+
const nodeAfter = pos.nodeAfter;
|
|
4097
|
+
const nodeBefore = pos.nodeBefore;
|
|
4098
|
+
// Không cho phép position bắt đầu/kết thúc bên trong isObject
|
|
4099
|
+
if (nodeAfter && model.schema.isObject(nodeAfter))
|
|
4100
|
+
return true; // trước object = ok
|
|
4101
|
+
if (nodeBefore && model.schema.isObject(nodeBefore))
|
|
4102
|
+
return true; // sau object = ok
|
|
4103
|
+
return model.schema.checkChild(pos, '$text') || true;
|
|
4104
|
+
}
|
|
4105
|
+
catch {
|
|
4106
|
+
return false;
|
|
4107
|
+
}
|
|
4108
|
+
};
|
|
3780
4109
|
return model.change(writer => {
|
|
3781
4110
|
let startPos = range.start;
|
|
3782
4111
|
let endPos = range.end;
|
|
@@ -3784,7 +4113,7 @@ class CkCommentPlugin extends Plugin {
|
|
|
3784
4113
|
if (leadingTrim > 0) {
|
|
3785
4114
|
for (let i = 0; i < leadingTrim && startPos; i++) {
|
|
3786
4115
|
const nextPos = startPos.getShiftedBy(1);
|
|
3787
|
-
if (nextPos) {
|
|
4116
|
+
if (nextPos && isValidPosition(nextPos)) {
|
|
3788
4117
|
startPos = nextPos;
|
|
3789
4118
|
}
|
|
3790
4119
|
else {
|
|
@@ -3796,7 +4125,7 @@ class CkCommentPlugin extends Plugin {
|
|
|
3796
4125
|
if (trailingTrim > 0) {
|
|
3797
4126
|
for (let i = 0; i < trailingTrim && endPos; i++) {
|
|
3798
4127
|
const prevPos = endPos.getShiftedBy(-1);
|
|
3799
|
-
if (prevPos && prevPos.isAfter(startPos)) {
|
|
4128
|
+
if (prevPos && prevPos.isAfter(startPos) && isValidPosition(prevPos)) {
|
|
3800
4129
|
endPos = prevPos;
|
|
3801
4130
|
}
|
|
3802
4131
|
else {
|
|
@@ -4456,75 +4785,18 @@ class SdDocumentBuilder {
|
|
|
4456
4785
|
// ========================================================================
|
|
4457
4786
|
// 3. QUẢN LÝ VARIABLE
|
|
4458
4787
|
// ========================================================================
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
const variables = [];
|
|
4472
|
-
try {
|
|
4473
|
-
const range = model.createRangeIn(root);
|
|
4474
|
-
for (const item of range.getItems()) {
|
|
4475
|
-
// Sử dụng item.is('element', 'variable') là chính xác
|
|
4476
|
-
if (item.is('element', 'variable')) {
|
|
4477
|
-
variables.push({
|
|
4478
|
-
id: item.getAttribute('id'),
|
|
4479
|
-
uuid: item.getAttribute('uuid'),
|
|
4480
|
-
value: item.getAttribute('value'),
|
|
4481
|
-
display: item.getAttribute('display'),
|
|
4482
|
-
});
|
|
4483
|
-
}
|
|
4484
|
-
}
|
|
4485
|
-
}
|
|
4486
|
-
catch (e) {
|
|
4487
|
-
console.error(e);
|
|
4488
|
-
return [];
|
|
4489
|
-
}
|
|
4490
|
-
return variables;
|
|
4491
|
-
},
|
|
4492
|
-
/**
|
|
4493
|
-
* Scroll tới vị trí của variable
|
|
4494
|
-
* @param uuid - uuid của variable FE sẽ tự sinh sau mỗi lần drop vào editor
|
|
4495
|
-
*/
|
|
4496
|
-
scroll: (uuid) => {
|
|
4497
|
-
if (!this.#editor)
|
|
4498
|
-
return;
|
|
4499
|
-
const model = this.#editor.model;
|
|
4500
|
-
const root = model.document.getRoot();
|
|
4501
|
-
if (!root)
|
|
4502
|
-
return;
|
|
4503
|
-
let targetElement = null;
|
|
4504
|
-
const range = model.createRangeIn(root);
|
|
4505
|
-
for (const item of range.getItems()) {
|
|
4506
|
-
if (item.is('element', 'variable') && item.getAttribute('uuid') === uuid) {
|
|
4507
|
-
targetElement = item;
|
|
4508
|
-
break;
|
|
4509
|
-
}
|
|
4510
|
-
}
|
|
4511
|
-
if (targetElement) {
|
|
4512
|
-
const viewElement = this.#editor.editing.mapper.toViewElement(targetElement);
|
|
4513
|
-
if (viewElement) {
|
|
4514
|
-
const domElement = this.#editor.editing.view.domConverter.viewToDom(viewElement);
|
|
4515
|
-
if (domElement) {
|
|
4516
|
-
domElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
4517
|
-
model.change(writer => {
|
|
4518
|
-
writer.setSelection(targetElement, 'on');
|
|
4519
|
-
});
|
|
4520
|
-
}
|
|
4521
|
-
}
|
|
4522
|
-
}
|
|
4523
|
-
else {
|
|
4524
|
-
console.warn(`Variable với id ${uuid} không tìm thấy trong tài liệu.`);
|
|
4525
|
-
}
|
|
4526
|
-
},
|
|
4527
|
-
};
|
|
4788
|
+
getVariablePluginAPI() {
|
|
4789
|
+
if (!this.#editor)
|
|
4790
|
+
return null;
|
|
4791
|
+
try {
|
|
4792
|
+
// Dùng class reference (không dùng string) → an toàn trong build minified, TypeScript-typed
|
|
4793
|
+
return this.#editor.plugins.get(VariablePlugin);
|
|
4794
|
+
}
|
|
4795
|
+
catch (error) {
|
|
4796
|
+
console.warn('VariablePlugin not available:', error);
|
|
4797
|
+
return null;
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4528
4800
|
// ========================================================================
|
|
4529
4801
|
// 4. HÀM EXPORT DOCX (FULL HEADER/FOOTER + PAGE NUMBER)
|
|
4530
4802
|
// ========================================================================
|
|
@@ -4656,11 +4928,11 @@ class SdDocumentBuilder {
|
|
|
4656
4928
|
});
|
|
4657
4929
|
};
|
|
4658
4930
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4659
|
-
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-
|
|
4931
|
+
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{display:inline-block;-webkit-user-select:none;user-select:none}:host ::ng-deep .variable-widget:not([data-binding=true]){background-color:#e3f2fd;color:#1976d2;border:1px solid #90caf9!important;border-radius:4px;padding:2px 6px;font-size:0;margin:0 2px;vertical-align:middle;font-weight:600;cursor:default}:host ::ng-deep .variable-widget:not([data-binding=true]):before{content:attr(data-display);font-size:10px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}:host ::ng-deep .variable-widget:not([data-binding=true]):hover{background-color:#bbdefb;box-shadow:0 1px 2px #0000001a}:host ::ng-deep .variable-widget:not([data-binding=true]).ck-widget_selected{outline:2px solid #2196f3;background-color:#bbdefb}:host ::ng-deep .variable-widget[data-binding=true]{background-color:#4caf501f;color:#2e7d32;border-bottom:2px solid #4caf50!important;border-radius:0;padding:0 1px;font-size:inherit;cursor:default}:host ::ng-deep .variable-widget[data-binding=true]:before{content:none}:host ::ng-deep .variable-widget[data-binding=true]:hover{background-color:#4caf5038}:host ::ng-deep .variable-widget[data-binding=true].ck-widget_selected{outline:2px solid #4caf50;background-color:#4caf5047}: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"] }] });
|
|
4660
4932
|
}
|
|
4661
4933
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.17", ngImport: i0, type: SdDocumentBuilder, decorators: [{
|
|
4662
4934
|
type: Component,
|
|
4663
|
-
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-
|
|
4935
|
+
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{display:inline-block;-webkit-user-select:none;user-select:none}:host ::ng-deep .variable-widget:not([data-binding=true]){background-color:#e3f2fd;color:#1976d2;border:1px solid #90caf9!important;border-radius:4px;padding:2px 6px;font-size:0;margin:0 2px;vertical-align:middle;font-weight:600;cursor:default}:host ::ng-deep .variable-widget:not([data-binding=true]):before{content:attr(data-display);font-size:10px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}:host ::ng-deep .variable-widget:not([data-binding=true]):hover{background-color:#bbdefb;box-shadow:0 1px 2px #0000001a}:host ::ng-deep .variable-widget:not([data-binding=true]).ck-widget_selected{outline:2px solid #2196f3;background-color:#bbdefb}:host ::ng-deep .variable-widget[data-binding=true]{background-color:#4caf501f;color:#2e7d32;border-bottom:2px solid #4caf50!important;border-radius:0;padding:0 1px;font-size:inherit;cursor:default}:host ::ng-deep .variable-widget[data-binding=true]:before{content:none}:host ::ng-deep .variable-widget[data-binding=true]:hover{background-color:#4caf5038}:host ::ng-deep .variable-widget[data-binding=true].ck-widget_selected{outline:2px solid #4caf50;background-color:#4caf5047}: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"] }]
|
|
4664
4936
|
}], propDecorators: { option: [{
|
|
4665
4937
|
type: Input,
|
|
4666
4938
|
args: [{ required: true }]
|