@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.
- package/assets/scss/core/bootstrap.scss +17 -0
- package/assets/scss/core/grid.scss +40 -0
- package/assets/scss/sd-core.scss +1 -0
- package/components/avatar/src/avatar.component.d.ts +2 -1
- package/components/button/src/button.component.d.ts +26 -27
- package/components/document-builder/index.d.ts +1 -0
- package/components/document-builder/src/document-builder.component.d.ts +3 -42
- package/components/document-builder/src/document-builder.model.d.ts +3 -14
- package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.d.ts +43 -0
- package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.model.d.ts +50 -0
- package/components/document-builder/src/plugins/index.d.ts +2 -1
- package/components/table/src/components/selector-action/action-filter.pipe.d.ts +11 -10
- package/components/table/src/models/table-option-selector.model.d.ts +11 -10
- package/fesm2022/sd-angular-core-components-avatar.mjs +15 -13
- package/fesm2022/sd-angular-core-components-avatar.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-button.mjs +63 -96
- package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-document-builder.mjs +729 -255
- 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-table.mjs +6 -6
- 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 +11 -11
- package/fesm2022/sd-angular-core-components-workflow.mjs.map +1 -1
- package/fesm2022/sd-angular-core-modules-layout.mjs +3 -3
- 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/package.json +52 -52
- package/sd-angular-core-19.0.0-beta.38.tgz +0 -0
- package/utilities/models/index.d.ts +1 -0
- package/utilities/models/src/unwrap-signal.model.d.ts +6 -0
- package/components/document-builder/src/plugins/comment/comment.plugin.d.ts +0 -4
- 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
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
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", "
|
|
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", "
|
|
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 }]
|