@ckeditor/ckeditor5-watchdog 46.0.3 → 46.1.0-alpha.0

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.
@@ -0,0 +1,204 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+ /**
6
+ * @module watchdog/actionsrecorderconfig
7
+ */
8
+ import type { ActionsRecorder } from './actionsrecorder.js';
9
+ /**
10
+ * The configuration of the ActionsRecorder plugin.
11
+ *
12
+ * ```ts
13
+ * ClassicEditor
14
+ * .create( editorElement, {
15
+ * actionsRecorder: ... // ActionsRecorder feature options.
16
+ * } )
17
+ * .then( ... )
18
+ * .catch( ... );
19
+ * ```
20
+ *
21
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor configuration options}.
22
+ */
23
+ export interface ActionsRecorderConfig {
24
+ /**
25
+ * The maximum number of action entries to keep in memory.
26
+ * When this limit is reached, older entries will be removed.
27
+ *
28
+ * This behavior can be modified by providing {@link #onMaxEntries `onMaxEntries`} callback.
29
+ *
30
+ * ```ts
31
+ * ClassicEditor
32
+ * .create( editorElement, {
33
+ * plugins: [ ActionsRecorder, ... ],
34
+ * actionsRecorder: {
35
+ * maxEntries: 1000
36
+ * }
37
+ * } )
38
+ * .then( ... )
39
+ * .catch( ... );
40
+ * ```
41
+ *
42
+ * @default 1000
43
+ */
44
+ maxEntries?: number;
45
+ /**
46
+ * Filter function that determines whether a record should be added to the list.
47
+ * This is called before the action executes and before the entry is stored.
48
+ * It allows to reduce memory usage by filtering out unnecessary records.
49
+ *
50
+ * If this function returns `false`, the record will not be stored in the entries array.
51
+ *
52
+ * ```ts
53
+ * ClassicEditor
54
+ * .create( editorElement, {
55
+ * plugins: [ ActionsRecorder, ... ],
56
+ * actionsRecorder: {
57
+ * onFilter( entry, prevEntries ) {
58
+ * // Only record command executions.
59
+ * return entry.action.startsWith( 'commands.' );
60
+ * }
61
+ * }
62
+ * } )
63
+ * .then( ... )
64
+ * .catch( ... );
65
+ * ```
66
+ */
67
+ onFilter?: ActionsRecorderFilterCallback;
68
+ /**
69
+ * Callback function called on caught error.
70
+ *
71
+ * ```ts
72
+ * ClassicEditor
73
+ * .create( editorElement, {
74
+ * plugins: [ ActionsRecorder, ... ],
75
+ * actionsRecorder: {
76
+ * onError( error, entries ) {
77
+ * console.error( 'ActionsRecorder - Error detected:', error );
78
+ * console.warn( 'Actions recorded before error:', entries );
79
+ *
80
+ * this.flushEntries();
81
+ * }
82
+ * }
83
+ * } )
84
+ * .then( ... )
85
+ * .catch( ... );
86
+ * ```
87
+ */
88
+ onError?: ActionsRecorderErrorCallback;
89
+ /**
90
+ * Callback function called when recorded entries count reaches {@link #maxEntries `maxEntries`}.
91
+ *
92
+ * ```ts
93
+ * ClassicEditor
94
+ * .create( editorElement, {
95
+ * plugins: [ ActionsRecorder, ... ],
96
+ * actionsRecorder: {
97
+ * onMaxEntries() {
98
+ * const entries = this.getEntries();
99
+ *
100
+ * this.flushEntries();
101
+ *
102
+ * console.log( 'ActionsRecorder - Batch of entries:', entries );
103
+ * }
104
+ * }
105
+ * } )
106
+ * .then( ... )
107
+ * .catch( ... );
108
+ * ```
109
+ *
110
+ * By default, when this callback is not provided, the list of entries is shifted so it does not include more than
111
+ * {@link #maxEntries `maxEntries`}.
112
+ */
113
+ onMaxEntries?: ActionsRecorderMaxEntriesCallback;
114
+ }
115
+ /**
116
+ * Callback function type for the {@link ~ActionsRecorderConfig#onFilter `onFilter`} option.
117
+ * Called before the action executes to determine if it should be recorded.
118
+ *
119
+ * @param entry The action entry to be filtered.
120
+ * @param prevEntries The array of previous action entries.
121
+ */
122
+ export type ActionsRecorderFilterCallback = (this: ActionsRecorder, entry: ActionsRecorderEntry, prevEntries: Array<ActionsRecorderEntry>) => boolean;
123
+ /**
124
+ * Callback function type for the {@link ~ActionsRecorderConfig#onError `onError`} option.
125
+ *
126
+ * @param error The error that occurred.
127
+ * @param entries The log of actions before the error was encountered.
128
+ */
129
+ export type ActionsRecorderErrorCallback = (this: ActionsRecorder, error: any, entries: Array<ActionsRecorderEntry>) => void;
130
+ /**
131
+ * Callback function type for the {@link ~ActionsRecorderConfig#onMaxEntries `onMaxEntries`} option.
132
+ */
133
+ export type ActionsRecorderMaxEntriesCallback = (this: ActionsRecorder) => void;
134
+ /**
135
+ * Represents the state snapshot of the editor at a specific point in time.
136
+ */
137
+ export interface ActionsRecorderEntryEditorSnapshot {
138
+ /**
139
+ * The document version. See {@link module:engine/model/document~ModelDocument#version}.
140
+ */
141
+ documentVersion: number;
142
+ /**
143
+ * Whether the editor is in the read-only mode. See {@link module:core/editor/editor~Editor#isReadOnly}.
144
+ */
145
+ editorReadOnly: boolean;
146
+ /**
147
+ * True if document is focused. See {@link module:engine/view/document~ViewDocument#isFocused}.
148
+ */
149
+ editorFocused: boolean;
150
+ /**
151
+ * The current model selection. See {@link module:engine/model/document~ModelDocument#selection}.
152
+ */
153
+ modelSelection: any;
154
+ }
155
+ /**
156
+ * Represents a recorded action entry with context and state information.
157
+ */
158
+ export interface ActionsRecorderEntry {
159
+ /**
160
+ * Entry timestamp in ISO date time format.
161
+ */
162
+ timeStamp: string;
163
+ /**
164
+ * For nested actions this is a reference to parent action (nesting of try-catch blocks).
165
+ *
166
+ * For example when user clicks a button in a toolbar it could generate such nested tree:
167
+ * * `component.bold:execute`
168
+ * * `commands.bold:execute`
169
+ * * `model.applyOperation`
170
+ */
171
+ parentEntry?: ActionsRecorderEntry;
172
+ /**
173
+ * The name of the action.
174
+ *
175
+ * For example:
176
+ * * `component.bold:execute` Main action for toolbar button `bold` was executed.
177
+ * * `commands.bold:execute` The `bold` command was executed.
178
+ * * `model.applyOperation` The low-level operation was applied to the model.
179
+ * * `observers:paste` The `paste` DOM event was dispatched in the editing root.
180
+ * * `model.insertContent` The `model#insertContent()` was called.
181
+ * * `model-selection:change:range` The model selection range was changed.
182
+ */
183
+ action: string;
184
+ /**
185
+ * The editor state before the action was executed.
186
+ */
187
+ before: ActionsRecorderEntryEditorSnapshot;
188
+ /**
189
+ * The editor state after the action was executed.
190
+ */
191
+ after?: ActionsRecorderEntryEditorSnapshot;
192
+ /**
193
+ * Params provided for the executed action. They depend on the actual action.
194
+ */
195
+ params?: Array<any>;
196
+ /**
197
+ * The result returned by the executed action. It depends on the actual action.
198
+ */
199
+ result?: any;
200
+ /**
201
+ * The error if the action throws one.
202
+ */
203
+ error?: any;
204
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
+ */
5
+ export {};
@@ -3,6 +3,7 @@
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
4
  */
5
5
  import type { EditorData } from './editorwatchdog.js';
6
+ import type { ActionsRecorderConfig } from './actionsrecorderconfig.js';
6
7
  declare module '@ckeditor/ckeditor5-core' {
7
8
  interface EditorConfig {
8
9
  /**
@@ -11,5 +12,9 @@ declare module '@ckeditor/ckeditor5-core' {
11
12
  * @internal
12
13
  */
13
14
  _watchdogInitialData?: EditorData;
15
+ /**
16
+ * The configuration for the actions recorder plugin.
17
+ */
18
+ actionsRecorder?: ActionsRecorderConfig;
14
19
  }
15
20
  }
@@ -440,24 +440,31 @@ class EditorWatchdogInitPlugin {
440
440
  // `as any` to avoid linking from external private repo.
441
441
  const parsedCommentThreads = JSON.parse(this._data.commentThreads);
442
442
  const parsedSuggestions = JSON.parse(this._data.suggestions);
443
- parsedCommentThreads.forEach(commentThreadData => {
444
- const channelId = this.editor.config.get('collaboration.channelId');
443
+ if (this.editor.plugins.has('CommentsRepository')) {
445
444
  const commentsRepository = this.editor.plugins.get('CommentsRepository');
446
- if (commentsRepository.hasCommentThread(commentThreadData.threadId)) {
447
- const commentThread = commentsRepository.getCommentThread(commentThreadData.threadId);
448
- commentThread.remove();
445
+ // First, remove the existing comments that were created by integration plugins during initialization.
446
+ // These comments may be outdated, and new instances will be created in the next step based on the saved data.
447
+ for (const commentThread of commentsRepository.getCommentThreads()) {
448
+ // Use the internal API since it removes the comment thread directly and does not trigger events
449
+ // that could cause side effects, such as removing markers.
450
+ commentsRepository._removeCommentThread({ threadId: commentThread.id });
449
451
  }
450
- commentsRepository.addCommentThread({ channelId, ...commentThreadData });
451
- });
452
- parsedSuggestions.forEach(suggestionData => {
452
+ parsedCommentThreads.forEach(commentThreadData => {
453
+ const channelId = this.editor.config.get('collaboration.channelId');
454
+ const commentsRepository = this.editor.plugins.get('CommentsRepository');
455
+ commentsRepository.addCommentThread({ channelId, ...commentThreadData });
456
+ });
457
+ }
458
+ if (this.editor.plugins.has('TrackChangesEditing')) {
453
459
  const trackChangesEditing = this.editor.plugins.get('TrackChangesEditing');
454
- if (trackChangesEditing.hasSuggestion(suggestionData.id)) {
455
- const suggestion = trackChangesEditing.getSuggestion(suggestionData.id);
456
- suggestion.attributes = suggestionData.attributes;
460
+ // First, remove the existing suggestions that were created by integration plugins during initialization.
461
+ // These suggestions may be outdated, and new instances will be created in the next step based on the saved data.
462
+ for (const suggestion of trackChangesEditing.getSuggestions()) {
463
+ trackChangesEditing._removeSuggestion(suggestion);
457
464
  }
458
- else {
465
+ parsedSuggestions.forEach(suggestionData => {
459
466
  trackChangesEditing.addSuggestionData(suggestionData);
460
- }
461
- });
467
+ });
468
+ }
462
469
  }
463
470
  }
package/src/index.d.ts CHANGED
@@ -8,5 +8,7 @@
8
8
  export { ContextWatchdog, type ContextWatchdogRestartEvent, type ContextWatchdogItemErrorEvent, type ContextWatchdogItemErrorEventData, type ContextWatchdogItemRestartEvent, type ContextWatchdogItemRestartEventData, type ContextWatchdogItemConfiguration } from './contextwatchdog.js';
9
9
  export { EditorWatchdog, type EditorWatchdogCreatorFunction, type EditorWatchdogRestartEvent } from './editorwatchdog.js';
10
10
  export { Watchdog, type WatchdogConfig } from './watchdog.js';
11
+ export { ActionsRecorder } from './actionsrecorder.js';
12
+ export type { ActionsRecorderConfig, ActionsRecorderEntry, ActionsRecorderEntryEditorSnapshot, ActionsRecorderErrorCallback, ActionsRecorderFilterCallback, ActionsRecorderMaxEntriesCallback } from './actionsrecorderconfig.js';
11
13
  export type { WatchdogEventMap, WatchdogEventArgs, WatchdogEventCallback, WatchdogErrorEvent, WatchdogErrorEventData, WatchdogStateChangeEvent, WatchdogState } from './watchdog.js';
12
14
  import './augmentation.js';
package/src/index.js CHANGED
@@ -8,4 +8,5 @@
8
8
  export { ContextWatchdog } from './contextwatchdog.js';
9
9
  export { EditorWatchdog } from './editorwatchdog.js';
10
10
  export { Watchdog } from './watchdog.js';
11
+ export { ActionsRecorder } from './actionsrecorder.js';
11
12
  import './augmentation.js';