@sd-angular/core 19.0.0-beta.43 → 19.0.0-beta.44
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/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.d.ts +1 -0
- package/components/document-builder/src/plugins/ck-comment/ck-comment.plugin.model.d.ts +6 -0
- package/components/mini-editor/src/mini-editor.model.d.ts +2 -0
- package/fesm2022/sd-angular-core-components-document-builder.mjs +146 -22
- package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-mini-editor.mjs +2 -2
- package/fesm2022/sd-angular-core-components-mini-editor.mjs.map +1 -1
- package/forms/select/src/select.component.d.ts +1 -1
- package/package.json +46 -46
- package/sd-angular-core-19.0.0-beta.44.tgz +0 -0
- package/sd-angular-core-19.0.0-beta.43.tgz +0 -0
|
@@ -6,6 +6,7 @@ export declare class CkCommentPlugin extends Plugin {
|
|
|
6
6
|
static get requires(): (typeof ContextualBalloon)[];
|
|
7
7
|
static readonly PENDING_MARKER_ID = "__pending_comment__";
|
|
8
8
|
static readonly DEFAULT_SEARCH_RANGE = 5;
|
|
9
|
+
static readonly DEFAULT_MAX_TEXT_LENGTH = 1000;
|
|
9
10
|
static readonly DEFAULT_COLORS: CkCommentColors;
|
|
10
11
|
init(): void;
|
|
11
12
|
/**
|
|
@@ -35,9 +35,15 @@ export interface CkCommentConfig {
|
|
|
35
35
|
onRemoveComment?: (id: string | number) => void;
|
|
36
36
|
onChange?: (comments: CkComment[]) => void;
|
|
37
37
|
onCancelPending?: () => void;
|
|
38
|
+
onError?: (error: {
|
|
39
|
+
code: string;
|
|
40
|
+
message: string;
|
|
41
|
+
data?: any;
|
|
42
|
+
}) => void;
|
|
38
43
|
searchRange?: number;
|
|
39
44
|
debug?: boolean;
|
|
40
45
|
colors?: CkCommentColors;
|
|
46
|
+
maxTextLength?: number;
|
|
41
47
|
}
|
|
42
48
|
/**
|
|
43
49
|
* Data returned when user selects text for comment
|
|
@@ -24,6 +24,8 @@ export interface SdMiniEditorOption {
|
|
|
24
24
|
placeholder?: string;
|
|
25
25
|
/** Chiều cao editor (mặc định: auto) */
|
|
26
26
|
height?: string;
|
|
27
|
+
/** Chiều cao tối đa của editor (ví dụ: '300px') */
|
|
28
|
+
maxHeight?: string;
|
|
27
29
|
/** Bật/tắt mention plugin */
|
|
28
30
|
enableMention?: boolean;
|
|
29
31
|
/** Cấu hình mention */
|
|
@@ -3135,19 +3135,21 @@ class CkCommentPlugin extends Plugin {
|
|
|
3135
3135
|
#selectedId = null;
|
|
3136
3136
|
#pendingId = null; // ID cho pending highlight
|
|
3137
3137
|
#isCreatingPending = false; // Flag để prevent clearing pending khi đang tạo
|
|
3138
|
+
#isProcessingClick = false; // Flag để prevent duplicate click events
|
|
3138
3139
|
#balloon;
|
|
3139
3140
|
#config = {};
|
|
3140
3141
|
// Hằng số ID cho pending marker
|
|
3141
3142
|
static PENDING_MARKER_ID = '__pending_comment__';
|
|
3142
3143
|
// Số node tìm kiếm mặc định khi path không chính xác
|
|
3143
3144
|
static DEFAULT_SEARCH_RANGE = 5;
|
|
3145
|
+
// Độ dài text tối đa để tạo marker
|
|
3146
|
+
static DEFAULT_MAX_TEXT_LENGTH = 1000;
|
|
3144
3147
|
// Màu sắc mặc định cho markers
|
|
3145
3148
|
static DEFAULT_COLORS = {
|
|
3146
3149
|
marker: 'rgba(59, 130, 246, 0.2)',
|
|
3147
3150
|
markerSelected: 'rgba(59, 130, 246, 0.5)',
|
|
3148
3151
|
markerPending: 'rgba(245, 158, 11, 0.4)',
|
|
3149
3152
|
markerModified: 'rgba(255, 193, 7, 0.4)',
|
|
3150
|
-
markerBroken: 'rgba(220, 53, 69, 0.3)',
|
|
3151
3153
|
};
|
|
3152
3154
|
/**
|
|
3153
3155
|
* Debug log - chỉ log khi debug config là true
|
|
@@ -3208,7 +3210,13 @@ class CkCommentPlugin extends Plugin {
|
|
|
3208
3210
|
let hasValidContent = false;
|
|
3209
3211
|
if (range && !isCollapsed) {
|
|
3210
3212
|
const text = this.#getTextFromRange(range);
|
|
3211
|
-
|
|
3213
|
+
const trimmedText = text.trim();
|
|
3214
|
+
const maxTextLength = this.#config.maxTextLength ?? _a.DEFAULT_MAX_TEXT_LENGTH;
|
|
3215
|
+
// Kiểm tra: có content, không phải chỉ khoảng trắng, và không vượt quá max length
|
|
3216
|
+
hasValidContent = trimmedText.length > 0 && trimmedText.length <= maxTextLength;
|
|
3217
|
+
if (trimmedText.length > maxTextLength) {
|
|
3218
|
+
this.#log(`Độ dài text vượt quá giới hạn: ${trimmedText.length} > ${maxTextLength}`);
|
|
3219
|
+
}
|
|
3212
3220
|
}
|
|
3213
3221
|
view.isEnabled = hasValidContent;
|
|
3214
3222
|
});
|
|
@@ -3261,22 +3269,32 @@ class CkCommentPlugin extends Plugin {
|
|
|
3261
3269
|
};
|
|
3262
3270
|
}
|
|
3263
3271
|
const comment = self.#comments.get(commentId);
|
|
3264
|
-
// Build CSS variables based on status
|
|
3265
|
-
|
|
3272
|
+
// Build CSS variables based on status - ALWAYS set the correct variable for the status
|
|
3273
|
+
let cssVars = [];
|
|
3266
3274
|
if (comment) {
|
|
3275
|
+
// Add status class
|
|
3267
3276
|
classes.push(`ck-comment-${comment.status}`);
|
|
3268
|
-
|
|
3269
|
-
classes.push('ck-comment-selected');
|
|
3270
|
-
cssVars.push(`--comment-selected-bg: ${colors.markerSelected}`);
|
|
3271
|
-
}
|
|
3272
|
-
// Add status-specific colors
|
|
3277
|
+
// Set CSS variable based on status
|
|
3273
3278
|
if (comment.status === 'modified') {
|
|
3274
|
-
cssVars
|
|
3279
|
+
cssVars = [`--comment-modified-bg: ${colors.markerModified}`];
|
|
3275
3280
|
}
|
|
3276
3281
|
else if (comment.status === 'broken') {
|
|
3277
|
-
cssVars
|
|
3282
|
+
cssVars = [`--comment-broken-bg: ${colors.markerBroken}`];
|
|
3283
|
+
}
|
|
3284
|
+
else {
|
|
3285
|
+
// normal status
|
|
3286
|
+
cssVars = [`--comment-bg: ${colors.marker}`];
|
|
3287
|
+
}
|
|
3288
|
+
// Add selected state if needed
|
|
3289
|
+
if (commentId === self.#selectedId) {
|
|
3290
|
+
classes.push('ck-comment-selected');
|
|
3291
|
+
cssVars.push(`--comment-selected-bg: ${colors.markerSelected}`);
|
|
3278
3292
|
}
|
|
3279
3293
|
}
|
|
3294
|
+
else {
|
|
3295
|
+
// No comment found - use default
|
|
3296
|
+
cssVars = [`--comment-bg: ${colors.marker}`];
|
|
3297
|
+
}
|
|
3280
3298
|
return {
|
|
3281
3299
|
classes: classes,
|
|
3282
3300
|
attributes: {
|
|
@@ -3292,7 +3310,7 @@ class CkCommentPlugin extends Plugin {
|
|
|
3292
3310
|
// ========================================================================
|
|
3293
3311
|
#setupMarkerClickHandler() {
|
|
3294
3312
|
const viewDocument = this.editor.editing.view.document;
|
|
3295
|
-
// Lắng nghe cả click và mousedown
|
|
3313
|
+
// Lắng nghe cả click và mousedown trên CKEditor view
|
|
3296
3314
|
viewDocument.on('mousedown', (evt, data) => {
|
|
3297
3315
|
this.#log('Mousedown event triggered, data:', data);
|
|
3298
3316
|
this.#handleMarkerClick(evt, data);
|
|
@@ -3301,8 +3319,58 @@ class CkCommentPlugin extends Plugin {
|
|
|
3301
3319
|
this.#log('Click event triggered, data:', data);
|
|
3302
3320
|
this.#handleMarkerClick(evt, data);
|
|
3303
3321
|
});
|
|
3322
|
+
// Thêm DOM event listener như fallback để đảm bảo bắt được click
|
|
3323
|
+
// Sử dụng editor's editable DOM element
|
|
3324
|
+
const editableElement = this.editor.ui.getEditableElement();
|
|
3325
|
+
if (editableElement) {
|
|
3326
|
+
editableElement.addEventListener('click', (domEvent) => {
|
|
3327
|
+
this.#log('DOM click event triggered');
|
|
3328
|
+
this.#handleDomMarkerClick(domEvent, editableElement);
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
/**
|
|
3333
|
+
* Handle DOM click event (fallback)
|
|
3334
|
+
*/
|
|
3335
|
+
#handleDomMarkerClick(domEvent, rootElement) {
|
|
3336
|
+
// Prevent duplicate if already processing
|
|
3337
|
+
if (this.#isProcessingClick) {
|
|
3338
|
+
this.#log('DOM click skipped - already processing');
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
let targetElement = domEvent.target;
|
|
3342
|
+
// Traverse up to find marker element
|
|
3343
|
+
while (targetElement && targetElement !== rootElement) {
|
|
3344
|
+
if (targetElement.classList?.contains('ck-comment-marker')) {
|
|
3345
|
+
const commentId = targetElement.getAttribute('data-comment-id');
|
|
3346
|
+
this.#log('DOM click found marker with commentId:', commentId);
|
|
3347
|
+
if (commentId) {
|
|
3348
|
+
this.#isProcessingClick = true;
|
|
3349
|
+
this.selectComment(commentId, false);
|
|
3350
|
+
domEvent.stopPropagation();
|
|
3351
|
+
domEvent.preventDefault();
|
|
3352
|
+
// Reset flag after a short delay
|
|
3353
|
+
setTimeout(() => {
|
|
3354
|
+
this.#isProcessingClick = false;
|
|
3355
|
+
}, 50);
|
|
3356
|
+
}
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
targetElement = targetElement.parentElement;
|
|
3360
|
+
}
|
|
3361
|
+
// Click outside markers - clear selection
|
|
3362
|
+
if (this.#selectedId) {
|
|
3363
|
+
this.#log('DOM click outside markers, clearing selection');
|
|
3364
|
+
this.#selectedId = null;
|
|
3365
|
+
this.#refreshView();
|
|
3366
|
+
}
|
|
3304
3367
|
}
|
|
3305
3368
|
#handleMarkerClick(evt, data) {
|
|
3369
|
+
// Prevent duplicate if already processing
|
|
3370
|
+
if (this.#isProcessingClick) {
|
|
3371
|
+
this.#log('View click skipped - already processing');
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3306
3374
|
const viewElement = data.target;
|
|
3307
3375
|
let element = viewElement;
|
|
3308
3376
|
this.#log('Target element:', element, 'hasClass:', typeof element?.hasClass);
|
|
@@ -3313,8 +3381,13 @@ class CkCommentPlugin extends Plugin {
|
|
|
3313
3381
|
if (hasMarkerClass) {
|
|
3314
3382
|
const commentId = element.getAttribute('data-comment-id');
|
|
3315
3383
|
this.#log('Found marker with commentId:', commentId);
|
|
3384
|
+
this.#isProcessingClick = true;
|
|
3316
3385
|
this.selectComment(commentId, false);
|
|
3317
3386
|
evt.stop();
|
|
3387
|
+
// Reset flag after a short delay
|
|
3388
|
+
setTimeout(() => {
|
|
3389
|
+
this.#isProcessingClick = false;
|
|
3390
|
+
}, 50);
|
|
3318
3391
|
return;
|
|
3319
3392
|
}
|
|
3320
3393
|
element = element.parent;
|
|
@@ -3348,9 +3421,11 @@ class CkCommentPlugin extends Plugin {
|
|
|
3348
3421
|
if (!selection.isCollapsed) {
|
|
3349
3422
|
const range = selection.getFirstRange();
|
|
3350
3423
|
if (range) {
|
|
3351
|
-
// Chỉ hiện balloon khi selection có content không phải khoảng trắng
|
|
3424
|
+
// Chỉ hiện balloon khi selection có content không phải khoảng trắng và không vượt quá max length
|
|
3352
3425
|
const text = this.#getTextFromRange(range);
|
|
3353
|
-
|
|
3426
|
+
const trimmedText = text.trim();
|
|
3427
|
+
const maxTextLength = this.#config.maxTextLength ?? _a.DEFAULT_MAX_TEXT_LENGTH;
|
|
3428
|
+
if (trimmedText.length > 0 && trimmedText.length <= maxTextLength) {
|
|
3354
3429
|
this.#showBalloon(range);
|
|
3355
3430
|
}
|
|
3356
3431
|
else {
|
|
@@ -3459,6 +3534,7 @@ class CkCommentPlugin extends Plugin {
|
|
|
3459
3534
|
JSON.stringify(comment.endPath) !== JSON.stringify(newEndPath);
|
|
3460
3535
|
const textChanged = currentText !== comment.currentText;
|
|
3461
3536
|
if (pathChanged || textChanged) {
|
|
3537
|
+
const oldStatus = comment.status;
|
|
3462
3538
|
hasChanges = true;
|
|
3463
3539
|
comment.startPath = newStartPath;
|
|
3464
3540
|
comment.endPath = newEndPath;
|
|
@@ -3473,13 +3549,16 @@ class CkCommentPlugin extends Plugin {
|
|
|
3473
3549
|
else {
|
|
3474
3550
|
comment.status = 'modified';
|
|
3475
3551
|
}
|
|
3552
|
+
this.#log(`Comment ${id} status changed: ${oldStatus} -> ${comment.status}`, `\n originalText: "${comment.originalText}"`, `\n currentText: "${currentText}"`, `\n textChanged: ${textChanged}`);
|
|
3476
3553
|
}
|
|
3477
3554
|
}
|
|
3478
3555
|
else {
|
|
3479
3556
|
// Không tìm thấy marker - bị hỏng
|
|
3480
3557
|
if (comment.status !== 'broken') {
|
|
3558
|
+
const oldStatus = comment.status;
|
|
3481
3559
|
hasChanges = true;
|
|
3482
3560
|
comment.status = 'broken';
|
|
3561
|
+
this.#log(`Comment ${id} marker not found, status changed: ${oldStatus} -> broken`);
|
|
3483
3562
|
}
|
|
3484
3563
|
}
|
|
3485
3564
|
});
|
|
@@ -3666,6 +3745,18 @@ class CkCommentPlugin extends Plugin {
|
|
|
3666
3745
|
if (!trimmedText) {
|
|
3667
3746
|
return null;
|
|
3668
3747
|
}
|
|
3748
|
+
// Kiểm tra độ dài text tối đa
|
|
3749
|
+
const maxTextLength = this.#config.maxTextLength ?? _a.DEFAULT_MAX_TEXT_LENGTH;
|
|
3750
|
+
if (trimmedText.length > maxTextLength) {
|
|
3751
|
+
this.#warn(`Text too long: ${trimmedText.length} > ${maxTextLength}`);
|
|
3752
|
+
// Fire error callback
|
|
3753
|
+
this.#config.onError?.({
|
|
3754
|
+
code: 'TEXT_TOO_LONG',
|
|
3755
|
+
message: `Văn bản quá dài (${trimmedText.length} ký tự). Tối đa ${maxTextLength} ký tự.`,
|
|
3756
|
+
data: { textLength: trimmedText.length, maxLength: maxTextLength },
|
|
3757
|
+
});
|
|
3758
|
+
return null;
|
|
3759
|
+
}
|
|
3669
3760
|
// Tính toán số ký tự cần trim ở đầu và cuối
|
|
3670
3761
|
const leadingWhitespace = text.length - text.trimStart().length;
|
|
3671
3762
|
const trailingWhitespace = text.length - text.trimEnd().length;
|
|
@@ -3792,16 +3883,49 @@ class CkCommentPlugin extends Plugin {
|
|
|
3792
3883
|
}
|
|
3793
3884
|
#scrollToComment(id) {
|
|
3794
3885
|
const marker = this.editor.model.markers.get(`comment:${id}`);
|
|
3795
|
-
if (!marker)
|
|
3886
|
+
if (!marker) {
|
|
3887
|
+
this.#warn('Marker not found for scroll:', id);
|
|
3796
3888
|
return;
|
|
3889
|
+
}
|
|
3797
3890
|
const editor = this.editor;
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3891
|
+
const STICKY_OFFSET = 100; // Offset cho sticky toolbar
|
|
3892
|
+
try {
|
|
3893
|
+
// Get the model range from marker
|
|
3894
|
+
const modelRange = marker.getRange();
|
|
3895
|
+
// Convert model range to view range
|
|
3896
|
+
const viewRange = editor.editing.mapper.toViewRange(modelRange);
|
|
3897
|
+
// Convert view range to DOM range
|
|
3898
|
+
const domRange = editor.editing.view.domConverter.viewRangeToDom(viewRange);
|
|
3899
|
+
this.#log('DOM range start:', domRange.startContainer, domRange.startOffset);
|
|
3900
|
+
// Get the start position of the range
|
|
3901
|
+
let targetElement = domRange.startContainer;
|
|
3902
|
+
// If it's a text node, get its parent
|
|
3903
|
+
if (targetElement.nodeType === Node.TEXT_NODE) {
|
|
3904
|
+
targetElement = targetElement.parentElement;
|
|
3905
|
+
}
|
|
3906
|
+
this.#log('Target element:', targetElement);
|
|
3907
|
+
// Find .builder-container
|
|
3908
|
+
let scrollContainer = targetElement;
|
|
3909
|
+
while (scrollContainer && !scrollContainer.classList?.contains('builder-container')) {
|
|
3910
|
+
scrollContainer = scrollContainer.parentElement;
|
|
3911
|
+
}
|
|
3912
|
+
this.#log('Found scroll container:', scrollContainer);
|
|
3913
|
+
if (!scrollContainer) {
|
|
3914
|
+
this.#warn('No builder-container found');
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3917
|
+
// Get the target element's position relative to the container
|
|
3918
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
3919
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
3920
|
+
const relativeTop = targetRect.top - containerRect.top + scrollContainer.scrollTop - STICKY_OFFSET;
|
|
3921
|
+
this.#log('Container top:', containerRect.top);
|
|
3922
|
+
this.#log('Target top:', targetRect.top);
|
|
3923
|
+
this.#log('Current scroll:', scrollContainer.scrollTop);
|
|
3924
|
+
this.#log('Relative top to scroll:', relativeTop);
|
|
3925
|
+
scrollContainer.scrollTo({ top: relativeTop, behavior: 'smooth' });
|
|
3926
|
+
}
|
|
3927
|
+
catch (e) {
|
|
3928
|
+
this.#warn('Error scrolling to comment:', e);
|
|
3805
3929
|
}
|
|
3806
3930
|
}
|
|
3807
3931
|
#refreshView() {
|