@ckeditor/ckeditor5-watchdog 36.0.0 → 37.0.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.
@@ -2,326 +2,216 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
- /**
7
- * @module watchdog/editorwatchdog
8
- */
9
-
10
- /* globals console */
11
-
12
- import { throttle, cloneDeepWith, isElement } from 'lodash-es';
13
5
  import areConnectedThroughProperties from './utils/areconnectedthroughproperties';
14
6
  import Watchdog from './watchdog';
15
-
7
+ import { throttle, cloneDeepWith, isElement } from 'lodash-es';
16
8
  /**
17
9
  * A watchdog for CKEditor 5 editors.
18
10
  *
19
11
  * See the {@glink features/watchdog Watchdog feature guide} to learn the rationale behind it and
20
12
  * how to use it.
21
- *
22
- * @extends {module:watchdog/watchdog~Watchdog}
23
13
  */
24
14
  export default class EditorWatchdog extends Watchdog {
25
- /**
26
- * @param {*} Editor The editor class.
27
- * @param {module:watchdog/watchdog~WatchdogConfig} [watchdogConfig] The watchdog plugin configuration.
28
- */
29
- constructor( Editor, watchdogConfig = {} ) {
30
- super( watchdogConfig );
31
-
32
- /**
33
- * The current editor instance.
34
- *
35
- * @private
36
- * @type {module:core/editor/editor~Editor}
37
- */
38
- this._editor = null;
39
-
40
- /**
41
- * Throttled save method. The `save()` method is called the specified `saveInterval` after `throttledSave()` is called,
42
- * unless a new action happens in the meantime.
43
- *
44
- * @private
45
- * @type {Function}
46
- */
47
- this._throttledSave = throttle(
48
- this._save.bind( this ),
49
- typeof watchdogConfig.saveInterval === 'number' ? watchdogConfig.saveInterval : 5000
50
- );
51
-
52
- /**
53
- * The latest saved editor data represented as a root name -> root data object.
54
- *
55
- * @private
56
- * @member {Object.<String,String>} #_data
57
- */
58
-
59
- /**
60
- * The last document version.
61
- *
62
- * @private
63
- * @member {Number} #_lastDocumentVersion
64
- */
65
-
66
- /**
67
- * The editor source element or data.
68
- *
69
- * @private
70
- * @member {HTMLElement|String|Object.<String|String>} #_elementOrData
71
- */
72
-
73
- /**
74
- * The editor configuration.
75
- *
76
- * @private
77
- * @member {Object|undefined} #_config
78
- */
79
-
80
- // Set default creator and destructor functions:
81
- this._creator = ( ( elementOrData, config ) => Editor.create( elementOrData, config ) );
82
- this._destructor = editor => editor.destroy();
83
- }
84
-
85
- /**
86
- * The current editor instance.
87
- *
88
- * @readonly
89
- * @type {module:core/editor/editor~Editor}
90
- */
91
- get editor() {
92
- return this._editor;
93
- }
94
-
95
- /**
96
- * @inheritDoc
97
- */
98
- get _item() {
99
- return this._editor;
100
- }
101
-
102
- /**
103
- * Sets the function that is responsible for the editor creation.
104
- * It expects a function that should return a promise.
105
- *
106
- * watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) );
107
- *
108
- * @method #setCreator
109
- * @param {Function} creator
110
- */
111
-
112
- /**
113
- * Sets the function that is responsible for the editor destruction.
114
- * Overrides the default destruction function, which destroys only the editor instance.
115
- * It expects a function that should return a promise or `undefined`.
116
- *
117
- * watchdog.setDestructor( editor => {
118
- * // Do something before the editor is destroyed.
119
- *
120
- * return editor
121
- * .destroy()
122
- * .then( () => {
123
- * // Do something after the editor is destroyed.
124
- * } );
125
- * } );
126
- *
127
- * @method #setDestructor
128
- * @param {Function} destructor
129
- */
130
-
131
- /**
132
- * Restarts the editor instance. This method is called whenever an editor error occurs. It fires the `restart` event and changes
133
- * the state to `initializing`.
134
- *
135
- * @protected
136
- * @fires restart
137
- * @returns {Promise}
138
- */
139
- _restart() {
140
- return Promise.resolve()
141
- .then( () => {
142
- this.state = 'initializing';
143
- this._fire( 'stateChange' );
144
-
145
- return this._destroy();
146
- } )
147
- .catch( err => {
148
- console.error( 'An error happened during the editor destroying.', err );
149
- } )
150
- .then( () => {
151
- if ( typeof this._elementOrData === 'string' ) {
152
- return this.create( this._data, this._config, this._config.context );
153
- } else {
154
- const updatedConfig = Object.assign( {}, this._config, {
155
- initialData: this._data
156
- } );
157
-
158
- return this.create( this._elementOrData, updatedConfig, updatedConfig.context );
159
- }
160
- } )
161
- .then( () => {
162
- this._fire( 'restart' );
163
- } );
164
- }
165
-
166
- /**
167
- * Creates the editor instance and keeps it running, using the defined creator and destructor.
168
- *
169
- * @param {HTMLElement|String|Object.<String|String>} [elementOrData] The editor source element or the editor data.
170
- * @param {module:core/editor/editorconfig~EditorConfig} [config] The editor configuration.
171
- * @param {Object} [context] A context for the editor.
172
- *
173
- * @returns {Promise}
174
- */
175
- create( elementOrData = this._elementOrData, config = this._config, context ) {
176
- return Promise.resolve()
177
- .then( () => {
178
- super._startErrorHandling();
179
-
180
- this._elementOrData = elementOrData;
181
-
182
- // Clone configuration because it might be shared within multiple watchdog instances. Otherwise,
183
- // when an error occurs in one of these editors, the watchdog will restart all of them.
184
- this._config = this._cloneEditorConfiguration( config ) || {};
185
-
186
- this._config.context = context;
187
-
188
- return this._creator( elementOrData, this._config );
189
- } )
190
- .then( editor => {
191
- this._editor = editor;
192
-
193
- editor.model.document.on( 'change:data', this._throttledSave );
194
-
195
- this._lastDocumentVersion = editor.model.document.version;
196
- this._data = this._getData();
197
-
198
- this.state = 'ready';
199
- this._fire( 'stateChange' );
200
- } );
201
- }
202
-
203
- /**
204
- * Destroys the watchdog and the current editor instance. It fires the callback
205
- * registered in {@link #setDestructor `setDestructor()`} and uses it to destroy the editor instance.
206
- * It also sets the state to `destroyed`.
207
- *
208
- * @returns {Promise}
209
- */
210
- destroy() {
211
- return Promise.resolve()
212
- .then( () => {
213
- this.state = 'destroyed';
214
- this._fire( 'stateChange' );
215
-
216
- super.destroy();
217
-
218
- return this._destroy();
219
- } );
220
- }
221
-
222
- /**
223
- * @private
224
- * @returns {Promise}
225
- */
226
- _destroy() {
227
- return Promise.resolve()
228
- .then( () => {
229
- this._stopErrorHandling();
230
-
231
- // Save data if there is a remaining editor data change.
232
- this._throttledSave.flush();
233
-
234
- const editor = this._editor;
235
-
236
- this._editor = null;
237
-
238
- // Remove the `change:data` listener before destroying the editor.
239
- // Incorrectly written plugins may trigger firing `change:data` events during the editor destruction phase
240
- // causing the watchdog to call `editor.getData()` when some parts of editor are already destroyed.
241
- editor.model.document.off( 'change:data', this._throttledSave );
242
-
243
- return this._destructor( editor );
244
- } );
245
- }
246
-
247
- /**
248
- * Saves the editor data, so it can be restored after the crash even if the data cannot be fetched at
249
- * the moment of the crash.
250
- *
251
- * @private
252
- */
253
- _save() {
254
- const version = this._editor.model.document.version;
255
-
256
- try {
257
- this._data = this._getData();
258
- this._lastDocumentVersion = version;
259
- } catch ( err ) {
260
- console.error(
261
- err,
262
- 'An error happened during restoring editor data. ' +
263
- 'Editor will be restored from the previously saved data.'
264
- );
265
- }
266
- }
267
-
268
- /**
269
- * @protected
270
- * @param {Set} props
271
- */
272
- _setExcludedProperties( props ) {
273
- this._excludedProps = props;
274
- }
275
-
276
- /**
277
- * Returns the editor data.
278
- *
279
- * @private
280
- * @returns {Object<String,String>}
281
- */
282
- _getData() {
283
- const data = {};
284
-
285
- for ( const rootName of this._editor.model.document.getRootNames() ) {
286
- data[ rootName ] = this._editor.data.get( { rootName } );
287
- }
288
-
289
- return data;
290
- }
291
-
292
- /**
293
- * Traverses the error context and the current editor to find out whether these structures are connected
294
- * to each other via properties.
295
- *
296
- * @protected
297
- * @param {module:utils/ckeditorerror~CKEditorError} error
298
- */
299
- _isErrorComingFromThisItem( error ) {
300
- return areConnectedThroughProperties( this._editor, error.context, this._excludedProps );
301
- }
302
-
303
- /**
304
- * Clones the editor configuration.
305
- *
306
- * @private
307
- * @param {Object} config
308
- */
309
- _cloneEditorConfiguration( config ) {
310
- return cloneDeepWith( config, ( value, key ) => {
311
- // Leave DOM references.
312
- if ( isElement( value ) ) {
313
- return value;
314
- }
315
-
316
- if ( key === 'context' ) {
317
- return value;
318
- }
319
- } );
320
- }
321
-
322
- /**
323
- * Fired after the watchdog restarts the error in case of a crash.
324
- *
325
- * @event restart
326
- */
15
+ /**
16
+ * @param Editor The editor class.
17
+ * @param watchdogConfig The watchdog plugin configuration.
18
+ */
19
+ constructor(Editor, watchdogConfig = {}) {
20
+ super(watchdogConfig);
21
+ /**
22
+ * The current editor instance.
23
+ */
24
+ this._editor = null;
25
+ // this._editorClass = Editor;
26
+ this._throttledSave = throttle(this._save.bind(this), typeof watchdogConfig.saveInterval === 'number' ? watchdogConfig.saveInterval : 5000);
27
+ // Set default creator and destructor functions:
28
+ if (Editor) {
29
+ this._creator = ((elementOrData, config) => Editor.create(elementOrData, config));
30
+ }
31
+ this._destructor = editor => editor.destroy();
32
+ }
33
+ /**
34
+ * The current editor instance.
35
+ */
36
+ get editor() {
37
+ return this._editor;
38
+ }
39
+ /**
40
+ * @internal
41
+ */
42
+ get _item() {
43
+ return this._editor;
44
+ }
45
+ /**
46
+ * Sets the function that is responsible for the editor creation.
47
+ * It expects a function that should return a promise.
48
+ *
49
+ * ```ts
50
+ * watchdog.setCreator( ( element, config ) => ClassicEditor.create( element, config ) );
51
+ * ```
52
+ */
53
+ setCreator(creator) {
54
+ this._creator = creator;
55
+ }
56
+ /**
57
+ * Sets the function that is responsible for the editor destruction.
58
+ * Overrides the default destruction function, which destroys only the editor instance.
59
+ * It expects a function that should return a promise or `undefined`.
60
+ *
61
+ * ```ts
62
+ * watchdog.setDestructor( editor => {
63
+ * // Do something before the editor is destroyed.
64
+ *
65
+ * return editor
66
+ * .destroy()
67
+ * .then( () => {
68
+ * // Do something after the editor is destroyed.
69
+ * } );
70
+ * } );
71
+ * ```
72
+ */
73
+ setDestructor(destructor) {
74
+ this._destructor = destructor;
75
+ }
76
+ /**
77
+ * Restarts the editor instance. This method is called whenever an editor error occurs. It fires the `restart` event and changes
78
+ * the state to `initializing`.
79
+ *
80
+ * @fires restart
81
+ */
82
+ _restart() {
83
+ return Promise.resolve()
84
+ .then(() => {
85
+ this.state = 'initializing';
86
+ this._fire('stateChange');
87
+ return this._destroy();
88
+ })
89
+ .catch(err => {
90
+ console.error('An error happened during the editor destroying.', err);
91
+ })
92
+ .then(() => {
93
+ if (typeof this._elementOrData === 'string') {
94
+ return this.create(this._data, this._config, this._config.context);
95
+ }
96
+ else {
97
+ const updatedConfig = Object.assign({}, this._config, {
98
+ initialData: this._data
99
+ });
100
+ return this.create(this._elementOrData, updatedConfig, updatedConfig.context);
101
+ }
102
+ })
103
+ .then(() => {
104
+ this._fire('restart');
105
+ });
106
+ }
107
+ /**
108
+ * Creates the editor instance and keeps it running, using the defined creator and destructor.
109
+ *
110
+ * @param elementOrData The editor source element or the editor data.
111
+ * @param config The editor configuration.
112
+ * @param context A context for the editor.
113
+ */
114
+ create(elementOrData = this._elementOrData, config = this._config, context) {
115
+ return Promise.resolve()
116
+ .then(() => {
117
+ super._startErrorHandling();
118
+ this._elementOrData = elementOrData;
119
+ // Clone configuration because it might be shared within multiple watchdog instances. Otherwise,
120
+ // when an error occurs in one of these editors, the watchdog will restart all of them.
121
+ this._config = this._cloneEditorConfiguration(config) || {};
122
+ this._config.context = context;
123
+ return this._creator(elementOrData, this._config);
124
+ })
125
+ .then(editor => {
126
+ this._editor = editor;
127
+ editor.model.document.on('change:data', this._throttledSave);
128
+ this._lastDocumentVersion = editor.model.document.version;
129
+ this._data = this._getData();
130
+ this.state = 'ready';
131
+ this._fire('stateChange');
132
+ });
133
+ }
134
+ /**
135
+ * Destroys the watchdog and the current editor instance. It fires the callback
136
+ * registered in {@link #setDestructor `setDestructor()`} and uses it to destroy the editor instance.
137
+ * It also sets the state to `destroyed`.
138
+ */
139
+ destroy() {
140
+ return Promise.resolve()
141
+ .then(() => {
142
+ this.state = 'destroyed';
143
+ this._fire('stateChange');
144
+ super.destroy();
145
+ return this._destroy();
146
+ });
147
+ }
148
+ _destroy() {
149
+ return Promise.resolve()
150
+ .then(() => {
151
+ this._stopErrorHandling();
152
+ // Save data if there is a remaining editor data change.
153
+ this._throttledSave.flush();
154
+ const editor = this._editor;
155
+ this._editor = null;
156
+ // Remove the `change:data` listener before destroying the editor.
157
+ // Incorrectly written plugins may trigger firing `change:data` events during the editor destruction phase
158
+ // causing the watchdog to call `editor.getData()` when some parts of editor are already destroyed.
159
+ editor.model.document.off('change:data', this._throttledSave);
160
+ return this._destructor(editor);
161
+ });
162
+ }
163
+ /**
164
+ * Saves the editor data, so it can be restored after the crash even if the data cannot be fetched at
165
+ * the moment of the crash.
166
+ */
167
+ _save() {
168
+ const version = this._editor.model.document.version;
169
+ try {
170
+ this._data = this._getData();
171
+ this._lastDocumentVersion = version;
172
+ }
173
+ catch (err) {
174
+ console.error(err, 'An error happened during restoring editor data. ' +
175
+ 'Editor will be restored from the previously saved data.');
176
+ }
177
+ }
178
+ /**
179
+ * @internal
180
+ */
181
+ _setExcludedProperties(props) {
182
+ this._excludedProps = props;
183
+ }
184
+ /**
185
+ * Returns the editor data.
186
+ */
187
+ _getData() {
188
+ const data = {};
189
+ for (const rootName of this._editor.model.document.getRootNames()) {
190
+ data[rootName] = this._editor.data.get({ rootName });
191
+ }
192
+ return data;
193
+ }
194
+ /**
195
+ * Traverses the error context and the current editor to find out whether these structures are connected
196
+ * to each other via properties.
197
+ *
198
+ * @internal
199
+ */
200
+ _isErrorComingFromThisItem(error) {
201
+ return areConnectedThroughProperties(this._editor, error.context, this._excludedProps);
202
+ }
203
+ /**
204
+ * Clones the editor configuration.
205
+ */
206
+ _cloneEditorConfiguration(config) {
207
+ return cloneDeepWith(config, (value, key) => {
208
+ // Leave DOM references.
209
+ if (isElement(value)) {
210
+ return value;
211
+ }
212
+ if (key === 'context') {
213
+ return value;
214
+ }
215
+ });
216
+ }
327
217
  }
package/src/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module watchdog
7
+ */
8
+ export { default as ContextWatchdog } from './contextwatchdog';
9
+ export { default as EditorWatchdog } from './editorwatchdog';
10
+ export { default as Watchdog } from './watchdog';
package/src/index.js CHANGED
@@ -2,11 +2,9 @@
2
2
  * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
3
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
4
  */
5
-
6
5
  /**
7
6
  * @module watchdog
8
7
  */
9
-
10
8
  export { default as ContextWatchdog } from './contextwatchdog';
11
9
  export { default as EditorWatchdog } from './editorwatchdog';
12
10
  export { default as Watchdog } from './watchdog';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * Traverses both structures to find out whether there is a reference that is shared between both structures.
7
+ */
8
+ export default function areConnectedThroughProperties(target1: unknown, target2: unknown, excludedNodes?: Set<unknown>): boolean;