ckeditor5-blazor 1.9.0 → 1.10.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.
- package/dist/elements/editable.d.ts +3 -11
- package/dist/elements/editable.d.ts.map +1 -1
- package/dist/elements/editor/editor.d.ts.map +1 -1
- package/dist/elements/editor/typings.d.ts +2 -1
- package/dist/elements/editor/typings.d.ts.map +1 -1
- package/dist/elements/editor/utils/cleanup-orphan-editor-elements.d.ts +8 -0
- package/dist/elements/editor/utils/cleanup-orphan-editor-elements.d.ts.map +1 -0
- package/dist/elements/editor/utils/create-editor-in-context.d.ts +6 -1
- package/dist/elements/editor/utils/create-editor-in-context.d.ts.map +1 -1
- package/dist/elements/editor/utils/index.d.ts +1 -0
- package/dist/elements/editor/utils/index.d.ts.map +1 -1
- package/dist/elements/editor/utils/wrap-with-watchdog.d.ts +7 -16
- package/dist/elements/editor/utils/wrap-with-watchdog.d.ts.map +1 -1
- package/dist/elements/ui-part.d.ts +3 -3
- package/dist/elements/ui-part.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +459 -394
- package/dist/index.mjs.map +1 -1
- package/dist/interop/create-editable-blazor-interop.d.ts.map +1 -1
- package/dist/interop/create-editor-blazor-interop.d.ts.map +1 -1
- package/dist/shared/are-maps-equal.d.ts +11 -0
- package/dist/shared/are-maps-equal.d.ts.map +1 -0
- package/dist/shared/async-registry.d.ts +44 -16
- package/dist/shared/async-registry.d.ts.map +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/elements/editable.ts +38 -58
- package/src/elements/editor/editor.ts +122 -101
- package/src/elements/editor/typings.ts +3 -1
- package/src/elements/editor/utils/cleanup-orphan-editor-elements.test.ts +285 -0
- package/src/elements/editor/utils/cleanup-orphan-editor-elements.ts +60 -0
- package/src/elements/editor/utils/create-editor-in-context.ts +8 -2
- package/src/elements/editor/utils/index.ts +1 -0
- package/src/elements/editor/utils/wrap-with-watchdog.test.ts +34 -14
- package/src/elements/editor/utils/wrap-with-watchdog.ts +15 -25
- package/src/elements/ui-part.test.ts +1 -1
- package/src/elements/ui-part.ts +12 -11
- package/src/interop/create-editable-blazor-interop.ts +19 -16
- package/src/interop/create-editor-blazor-interop.ts +15 -18
- package/src/shared/are-maps-equal.test.ts +56 -0
- package/src/shared/are-maps-equal.ts +22 -0
- package/src/shared/async-registry.test.ts +190 -88
- package/src/shared/async-registry.ts +179 -107
- package/src/shared/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-editable-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editable-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAS9C;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;
|
|
1
|
+
{"version":3,"file":"create-editable-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editable-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAS9C;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;IA6DpF;;OAEG;;IAcH;;;OAGG;sBACqB,MAAM;IAS9B;;;OAGG;yCACwC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;EAU5E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-editor-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editor-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAW9C;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;
|
|
1
|
+
{"version":3,"file":"create-editor-blazor-interop.d.ts","sourceRoot":"","sources":["../../../src/interop/create-editor-blazor-interop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAW9C;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa;IAqElF;;OAEG;sBACqB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAS9C;;OAEG;yCACwC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAUzE;;OAEG;;IAcH;;;;OAIG;;EAWN"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compares two Map structures for equality based on their contents.
|
|
3
|
+
* The function checks if the maps have the same size, contain the exact same keys,
|
|
4
|
+
* and have strictly equal values (using shallow comparison).
|
|
5
|
+
*
|
|
6
|
+
* @param map1 - The first map to compare (can be null).
|
|
7
|
+
* @param map2 - The second map to compare.
|
|
8
|
+
* @returns Returns `true` if the maps are identical in terms of keys and values, otherwise `false`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function areMapsEqual(map1: Map<any, any> | null, map2: Map<any, any>): boolean;
|
|
11
|
+
//# sourceMappingURL=are-maps-equal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"are-maps-equal.d.ts","sourceRoot":"","sources":["../../../src/shared/are-maps-equal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,OAAO,CAYrF"}
|
|
@@ -19,6 +19,15 @@ export declare class AsyncRegistry<T extends Destructible> {
|
|
|
19
19
|
* Set of watchers that observe changes to the registry.
|
|
20
20
|
*/
|
|
21
21
|
private readonly watchers;
|
|
22
|
+
/**
|
|
23
|
+
* Batch nesting depth. When > 0, watcher notifications are deferred.
|
|
24
|
+
*/
|
|
25
|
+
private batchDepth;
|
|
26
|
+
/**
|
|
27
|
+
* Snapshot of the last state dispatched to watchers, used for change detection.
|
|
28
|
+
*/
|
|
29
|
+
private lastNotifiedItems;
|
|
30
|
+
private lastNotifiedErrors;
|
|
22
31
|
/**
|
|
23
32
|
* Executes a function on an item.
|
|
24
33
|
* If the item is not yet registered, it will wait for it to be registered.
|
|
@@ -29,6 +38,14 @@ export declare class AsyncRegistry<T extends Destructible> {
|
|
|
29
38
|
* @returns A promise that resolves with the result of the function.
|
|
30
39
|
*/
|
|
31
40
|
execute<R, E extends T = T>(id: RegistryId | null, onSuccess: (item: E) => R, onError?: (error: any) => void): Promise<Awaited<R>>;
|
|
41
|
+
/**
|
|
42
|
+
* Reactively binds a mount/unmount lifecycle to a single registry item.
|
|
43
|
+
*
|
|
44
|
+
* @param id The ID of the item to observe.
|
|
45
|
+
* @param onMount Function executed when the item mounts.
|
|
46
|
+
* @returns A function that stops observing and immediately runs any pending cleanup.
|
|
47
|
+
*/
|
|
48
|
+
mountEffect<E extends T = T>(id: RegistryId | null, onMount: (item: E) => (() => void) | void): () => void;
|
|
32
49
|
/**
|
|
33
50
|
* Registers an item.
|
|
34
51
|
*
|
|
@@ -53,14 +70,21 @@ export declare class AsyncRegistry<T extends Destructible> {
|
|
|
53
70
|
* Un-registers an item.
|
|
54
71
|
*
|
|
55
72
|
* @param id The ID of the item.
|
|
73
|
+
* @param resetPendingCallbacks If true resets pending callbacks.
|
|
56
74
|
*/
|
|
57
|
-
unregister(id: RegistryId | null): void;
|
|
75
|
+
unregister(id: RegistryId | null, resetPendingCallbacks?: boolean): void;
|
|
58
76
|
/**
|
|
59
77
|
* Gets all registered items.
|
|
60
78
|
*
|
|
61
79
|
* @returns An array of all registered items.
|
|
62
80
|
*/
|
|
63
81
|
getItems(): T[];
|
|
82
|
+
/**
|
|
83
|
+
* Returns single registered item.
|
|
84
|
+
*
|
|
85
|
+
* @returns Registered item.
|
|
86
|
+
*/
|
|
87
|
+
getItem(id: RegistryId | null): T | undefined;
|
|
64
88
|
/**
|
|
65
89
|
* Checks if an item with the given ID is registered.
|
|
66
90
|
*
|
|
@@ -73,15 +97,30 @@ export declare class AsyncRegistry<T extends Destructible> {
|
|
|
73
97
|
* If the item is not registered yet, it will wait for it to be registered.
|
|
74
98
|
*
|
|
75
99
|
* @param id The ID of the item.
|
|
76
|
-
* @param timeout Optional timeout in milliseconds.
|
|
77
100
|
* @returns A promise that resolves with the item instance.
|
|
78
101
|
*/
|
|
79
|
-
waitFor<E extends T = T>(id: RegistryId | null
|
|
102
|
+
waitFor<E extends T = T>(id: RegistryId | null): Promise<E>;
|
|
80
103
|
/**
|
|
81
104
|
* Destroys all registered items and clears the registry.
|
|
82
105
|
* This will call the `destroy` method on each item.
|
|
83
106
|
*/
|
|
84
107
|
destroyAll(): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Destroys all registered editors and removes all watchers.
|
|
110
|
+
*/
|
|
111
|
+
reset(): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Executes a callback while deferring all watcher notifications.
|
|
114
|
+
* A single notification is fired synchronously after the callback returns,
|
|
115
|
+
* but only if the registry actually changed.
|
|
116
|
+
*
|
|
117
|
+
* Batches can be nested — watchers are notified only when the outermost
|
|
118
|
+
* batch completes.
|
|
119
|
+
*
|
|
120
|
+
* @param fn The callback to execute.
|
|
121
|
+
* @returns The return value of the callback.
|
|
122
|
+
*/
|
|
123
|
+
batch<R>(fn: () => R): R;
|
|
85
124
|
/**
|
|
86
125
|
* Registers a watcher that will be called whenever the registry changes.
|
|
87
126
|
*
|
|
@@ -96,13 +135,9 @@ export declare class AsyncRegistry<T extends Destructible> {
|
|
|
96
135
|
*/
|
|
97
136
|
unwatch(watcher: RegistryWatcher<T>): void;
|
|
98
137
|
/**
|
|
99
|
-
*
|
|
138
|
+
* Immediately dispatches the current state to all watchers if it changed.
|
|
100
139
|
*/
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Notifies all watchers about changes to the registry.
|
|
104
|
-
*/
|
|
105
|
-
private notifyWatchers;
|
|
140
|
+
private flushWatchers;
|
|
106
141
|
/**
|
|
107
142
|
* Gets or creates pending callbacks for a specific ID.
|
|
108
143
|
*
|
|
@@ -110,13 +145,6 @@ export declare class AsyncRegistry<T extends Destructible> {
|
|
|
110
145
|
* @returns The pending callbacks structure.
|
|
111
146
|
*/
|
|
112
147
|
private getPendingCallbacks;
|
|
113
|
-
/**
|
|
114
|
-
* Registers an item as the default (null ID) item if it's the first one.
|
|
115
|
-
*
|
|
116
|
-
* @param id The ID of the item being registered.
|
|
117
|
-
* @param item The item instance.
|
|
118
|
-
*/
|
|
119
|
-
private registerAsDefault;
|
|
120
148
|
}
|
|
121
149
|
/**
|
|
122
150
|
* Interface for objects that can be destroyed.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async-registry.d.ts","sourceRoot":"","sources":["../../../src/shared/async-registry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"async-registry.d.ts","sourceRoot":"","sources":["../../../src/shared/async-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,QAAQ,CAAC;AACvB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,OAAO,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,QAAQ,CAAC;AACvB,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,OAAO,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,kCAAkC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ckeditor5-blazor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "CKEditor 5 integration for Blazor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"@vitest/coverage-v8": "^4.0.18",
|
|
31
31
|
"ckeditor5": "^47.6.0",
|
|
32
32
|
"ckeditor5-premium-features": "^47.6.0",
|
|
33
|
-
"happy-dom": "^20.
|
|
33
|
+
"happy-dom": "^20.8.9",
|
|
34
34
|
"typescript": "^5.9.3",
|
|
35
|
-
"vite": "^7.3.
|
|
35
|
+
"vite": "^7.3.2",
|
|
36
36
|
"vite-plugin-dts": "^4.5.4",
|
|
37
37
|
"vite-tsconfig-paths": "^6.0.5",
|
|
38
38
|
"vitest": "^4.0.18"
|
package/src/elements/editable.ts
CHANGED
|
@@ -11,27 +11,15 @@ import { queryAllEditorIds } from './editor/utils';
|
|
|
11
11
|
*/
|
|
12
12
|
export class EditableComponentElement extends HTMLElement {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Stops observing the editor registry and immediately runs any pending cleanup.
|
|
15
15
|
*/
|
|
16
|
-
private
|
|
16
|
+
private unmountEffect: VoidFunction | null = null;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Wait result for the interactive attribute.
|
|
20
20
|
*/
|
|
21
21
|
private interactiveWait?: WaitForInteractiveResult;
|
|
22
22
|
|
|
23
|
-
/**
|
|
24
|
-
* Callbacks to be invoked before the editable is destroyed.
|
|
25
|
-
*/
|
|
26
|
-
private beforeDestroyCallbacks: Array<() => void> = [];
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Registers a callback to be called before the editable is destroyed.
|
|
30
|
-
*/
|
|
31
|
-
public onBeforeDestroy(callback: () => void): void {
|
|
32
|
-
this.beforeDestroyCallbacks.push(callback);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
23
|
/**
|
|
36
24
|
* Mounts the editable component.
|
|
37
25
|
*/
|
|
@@ -62,11 +50,11 @@ export class EditableComponentElement extends HTMLElement {
|
|
|
62
50
|
throw new CKEditor5BlazorError('Editor ID or Root Name is missing.');
|
|
63
51
|
}
|
|
64
52
|
|
|
65
|
-
// If the editor is not registered yet, we will wait for it to be registered.
|
|
66
53
|
this.style.display = 'block';
|
|
67
|
-
|
|
54
|
+
|
|
55
|
+
this.unmountEffect = EditorsRegistry.the.mountEffect(editorId, (editor: MultiRootEditor) => {
|
|
68
56
|
if (!this.isConnected) {
|
|
69
|
-
return
|
|
57
|
+
return;
|
|
70
58
|
}
|
|
71
59
|
|
|
72
60
|
const { ui, editing, model } = editor;
|
|
@@ -95,7 +83,7 @@ export class EditableComponentElement extends HTMLElement {
|
|
|
95
83
|
});
|
|
96
84
|
}
|
|
97
85
|
|
|
98
|
-
return
|
|
86
|
+
return;
|
|
99
87
|
}
|
|
100
88
|
|
|
101
89
|
editor.addRoot(rootName, {
|
|
@@ -135,59 +123,51 @@ export class EditableComponentElement extends HTMLElement {
|
|
|
135
123
|
const debouncedSync = debounce(saveDebounceMs, sync);
|
|
136
124
|
|
|
137
125
|
editor.model.document.on('change:data', debouncedSync);
|
|
138
|
-
this.onBeforeDestroy(() => editor.model.document.off('change:data', debouncedSync));
|
|
139
126
|
sync();
|
|
140
127
|
|
|
141
|
-
return
|
|
128
|
+
return () => {
|
|
129
|
+
editor.model.document.off('change:data', debouncedSync);
|
|
130
|
+
|
|
131
|
+
if (editor.state !== 'destroyed' && rootName) {
|
|
132
|
+
const root = editor.model.document.getRoot(rootName);
|
|
133
|
+
|
|
134
|
+
/* v8 ignore else -- @preserve */
|
|
135
|
+
if (root && 'detachEditable' in editor) {
|
|
136
|
+
// Detaching editables seem to be buggy when something removed DOM element of the editable (e.g. Blazor re-render) before
|
|
137
|
+
// the editable is unmounted. To prevent errors in such cases, we will try to detach the editable if it exists, but ignore errors.
|
|
138
|
+
try {
|
|
139
|
+
/* v8 ignore else -- @preserve */
|
|
140
|
+
if (editor.ui.view.editables[rootName]) {
|
|
141
|
+
editor.detachEditable(root);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
// Ignore errors when detaching editable.
|
|
146
|
+
/* v8 ignore next -- @preserve */
|
|
147
|
+
console.error('Unable unmount editable from root:', err);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (root.isAttached()) {
|
|
151
|
+
editor.detachRoot(rootName, false);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
142
156
|
});
|
|
143
157
|
}
|
|
144
158
|
|
|
145
159
|
/**
|
|
146
160
|
* Destroys the editable component. Unmounts root from the editor.
|
|
147
161
|
*/
|
|
148
|
-
|
|
162
|
+
disconnectedCallback() {
|
|
149
163
|
// Disconnect the observer if present.
|
|
150
164
|
this.interactiveWait?.disconnect();
|
|
151
165
|
|
|
152
|
-
const rootName = this.getAttribute('data-cke-root-name');
|
|
153
|
-
|
|
154
166
|
// Let's hide the element during destruction to prevent flickering.
|
|
155
167
|
this.style.display = 'none';
|
|
156
168
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
this.
|
|
160
|
-
|
|
161
|
-
// Run all registered pre-destroy callbacks and clear the queue.
|
|
162
|
-
for (const callback of this.beforeDestroyCallbacks) {
|
|
163
|
-
callback();
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
this.beforeDestroyCallbacks = [];
|
|
167
|
-
|
|
168
|
-
// Unmount root from the editor if editor is still registered.
|
|
169
|
-
if (editor && editor.state !== 'destroyed' && rootName) {
|
|
170
|
-
const root = editor.model.document.getRoot(rootName);
|
|
171
|
-
|
|
172
|
-
/* v8 ignore else -- @preserve */
|
|
173
|
-
if (root && 'detachEditable' in editor) {
|
|
174
|
-
// Detaching editables seem to be buggy when something removed DOM element of the editable (e.g. Blazor re-render) before
|
|
175
|
-
// the editable is unmounted. To prevent errors in such cases, we will try to detach the editable if it exists, but ignore errors.
|
|
176
|
-
try {
|
|
177
|
-
if (editor.ui.view.editables[rootName]) {
|
|
178
|
-
editor.detachEditable(root);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
// Ignore errors when detaching editable.
|
|
183
|
-
/* v8 ignore next -- @preserve */
|
|
184
|
-
console.error('Unable unmount editable from root:', err);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (root.isAttached()) {
|
|
188
|
-
editor.detachRoot(rootName, false);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
169
|
+
// Stop observing the registry and run cleanup immediately.
|
|
170
|
+
this.unmountEffect?.();
|
|
171
|
+
this.unmountEffect = null;
|
|
192
172
|
}
|
|
193
173
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { WaitForInteractiveResult } from '../../shared';
|
|
2
2
|
import type { EditorId, EditorLanguage, EditorPreset } from './typings';
|
|
3
|
-
import type { EditorCreator } from './utils';
|
|
4
3
|
import type { Editor } from 'ckeditor5';
|
|
5
4
|
|
|
6
5
|
import {
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
createSyncEditorWithInputPlugin,
|
|
17
16
|
} from './plugins';
|
|
18
17
|
import {
|
|
18
|
+
cleanupOrphanEditorElements,
|
|
19
19
|
createEditorInContext,
|
|
20
20
|
isSingleRootEditor,
|
|
21
21
|
loadAllEditorTranslations,
|
|
@@ -155,144 +155,165 @@ export class EditorComponentElement extends HTMLElement {
|
|
|
155
155
|
const editableHeight = this.getAttribute('data-cke-editable-height') ? Number.parseInt(this.getAttribute('data-cke-editable-height')!, 10) : null;
|
|
156
156
|
const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
|
|
157
157
|
const language = JSON.parse(this.getAttribute('data-cke-language')!) as EditorLanguage;
|
|
158
|
-
const
|
|
158
|
+
const useWatchdog = this.hasAttribute('data-cke-watchdog');
|
|
159
159
|
const content = JSON.parse(this.getAttribute('data-cke-content')!) as Record<string, string>;
|
|
160
160
|
|
|
161
161
|
const {
|
|
162
162
|
customTranslations,
|
|
163
163
|
editorType,
|
|
164
164
|
licenseKey,
|
|
165
|
+
watchdogConfig,
|
|
165
166
|
config: { plugins, ...config },
|
|
166
167
|
} = preset;
|
|
167
168
|
|
|
168
|
-
|
|
169
|
-
let Constructor: EditorCreator = await loadEditorConstructor(editorType);
|
|
169
|
+
const Constructor = await loadEditorConstructor(editorType);
|
|
170
170
|
const context = await (
|
|
171
171
|
contextId
|
|
172
172
|
? ContextsRegistry.the.waitFor(contextId)
|
|
173
173
|
: null
|
|
174
174
|
);
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Builds the full editor configuration and creates the editor instance.
|
|
178
|
+
*/
|
|
179
|
+
const buildAndCreateEditor = async () => {
|
|
180
|
+
const { loadedPlugins, hasPremium } = await loadEditorPlugins(plugins);
|
|
179
181
|
|
|
180
|
-
(
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
loadedPlugins.push(
|
|
183
|
+
await createDispatchEditorRootsChangeEventPlugin({
|
|
184
|
+
saveDebounceMs,
|
|
185
|
+
editorId,
|
|
186
|
+
targetElement: this,
|
|
187
|
+
}),
|
|
188
|
+
);
|
|
183
189
|
|
|
184
|
-
|
|
190
|
+
if (isSingleRootEditor(editorType)) {
|
|
191
|
+
loadedPlugins.push(
|
|
192
|
+
await createSyncEditorWithInputPlugin(saveDebounceMs),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
185
195
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
196
|
+
// Mix custom translations with loaded translations.
|
|
197
|
+
const loadedTranslations = await loadAllEditorTranslations(language, hasPremium);
|
|
198
|
+
const mixedTranslations = [
|
|
199
|
+
...loadedTranslations,
|
|
200
|
+
normalizeCustomTranslations(customTranslations || {}),
|
|
201
|
+
]
|
|
202
|
+
.filter(translations => !isEmptyObject(translations));
|
|
203
|
+
|
|
204
|
+
// Let's query all elements, and create basic configuration.
|
|
205
|
+
let initialData: string | Record<string, string> = {
|
|
206
|
+
...content,
|
|
207
|
+
...queryEditablesSnapshotContent(editorId),
|
|
208
|
+
};
|
|
189
209
|
|
|
190
|
-
|
|
210
|
+
if (isSingleRootEditor(editorType)) {
|
|
211
|
+
initialData = initialData['main'] || '';
|
|
212
|
+
}
|
|
191
213
|
|
|
192
|
-
|
|
193
|
-
await
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
214
|
+
// Depending of the editor type, and parent lookup for nearest context or initialize it without it.
|
|
215
|
+
const editor = await (async () => {
|
|
216
|
+
let sourceElementOrData: HTMLElement | Record<string, HTMLElement> = queryEditablesElements(editorId);
|
|
217
|
+
|
|
218
|
+
// Handle special case when user specified `initialData` of several root elements, but editable components
|
|
219
|
+
// are not yet present in the DOM. In other words - editor is initialized before attaching root elements.
|
|
220
|
+
if (!sourceElementOrData['main']) {
|
|
221
|
+
const requiredRoots = (
|
|
222
|
+
isSingleRootEditor(editorType)
|
|
223
|
+
? ['main']
|
|
224
|
+
: Object.keys(initialData as Record<string, string>)
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
if (!checkIfAllRootsArePresent(sourceElementOrData, requiredRoots)) {
|
|
228
|
+
sourceElementOrData = await waitForAllRootsToBePresent(editorId, requiredRoots);
|
|
229
|
+
initialData = {
|
|
230
|
+
...content,
|
|
231
|
+
...queryEditablesSnapshotContent(editorId),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
199
235
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
236
|
+
// If single root editor, unwrap the element from the object.
|
|
237
|
+
if (isSingleRootEditor(editorType) && 'main' in sourceElementOrData) {
|
|
238
|
+
sourceElementOrData = sourceElementOrData['main'];
|
|
239
|
+
}
|
|
205
240
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
241
|
+
// Construct parsed config. First resolve DOM element references in the provided configuration.
|
|
242
|
+
let resolvedConfig = resolveEditorConfigElementReferences(config);
|
|
243
|
+
|
|
244
|
+
// Then resolve translation references in the provided configuration, using the mixed translations.
|
|
245
|
+
resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
|
|
246
|
+
|
|
247
|
+
// Construct parsed config.
|
|
248
|
+
const parsedConfig = {
|
|
249
|
+
...resolvedConfig,
|
|
250
|
+
initialData,
|
|
251
|
+
licenseKey,
|
|
252
|
+
plugins: loadedPlugins,
|
|
253
|
+
language,
|
|
254
|
+
...mixedTranslations.length && {
|
|
255
|
+
translations: mixedTranslations,
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if (!context || !(sourceElementOrData instanceof HTMLElement)) {
|
|
260
|
+
return Constructor.create(sourceElementOrData as any, parsedConfig);
|
|
261
|
+
}
|
|
219
262
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
263
|
+
const result = await createEditorInContext({
|
|
264
|
+
context,
|
|
265
|
+
element: sourceElementOrData,
|
|
266
|
+
creator: Constructor,
|
|
267
|
+
config: parsedConfig,
|
|
268
|
+
});
|
|
223
269
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
let sourceElementOrData: HTMLElement | Record<string, HTMLElement> = queryEditablesElements(editorId);
|
|
227
|
-
|
|
228
|
-
// Handle special case when user specified `initialData` of several root elements, but editable components
|
|
229
|
-
// are not yet present in the DOM. In other words - editor is initialized before attaching root elements.
|
|
230
|
-
if (!sourceElementOrData['main']) {
|
|
231
|
-
const requiredRoots = (
|
|
232
|
-
isSingleRootEditor(editorType)
|
|
233
|
-
? ['main']
|
|
234
|
-
: Object.keys(initialData as Record<string, string>)
|
|
235
|
-
);
|
|
270
|
+
return result.editor;
|
|
271
|
+
})();
|
|
236
272
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
};
|
|
243
|
-
}
|
|
273
|
+
// Assign root attributes if they are not empty. This is needed to support custom attributes on the root element of the editor.
|
|
274
|
+
if (!isEmptyObject(rootAttributes)) {
|
|
275
|
+
editor.model.change((writer) => {
|
|
276
|
+
writer.setAttributes(rootAttributes, editor.model.document.getRoot()!);
|
|
277
|
+
});
|
|
244
278
|
}
|
|
245
279
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
sourceElementOrData = sourceElementOrData['main'];
|
|
280
|
+
if (isSingleRootEditor(editorType) && editableHeight) {
|
|
281
|
+
setEditorEditableHeight(editor, editableHeight);
|
|
249
282
|
}
|
|
250
283
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// Then resolve translation references in the provided configuration, using the mixed translations.
|
|
255
|
-
resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
|
|
256
|
-
|
|
257
|
-
// Construct parsed config.
|
|
258
|
-
const parsedConfig = {
|
|
259
|
-
...resolvedConfig,
|
|
260
|
-
initialData,
|
|
261
|
-
licenseKey,
|
|
262
|
-
plugins: loadedPlugins,
|
|
263
|
-
language,
|
|
264
|
-
...mixedTranslations.length && {
|
|
265
|
-
translations: mixedTranslations,
|
|
266
|
-
},
|
|
267
|
-
};
|
|
284
|
+
return editor;
|
|
285
|
+
};
|
|
268
286
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
287
|
+
// Do not use editor specific watchdog if context is attached, as the context is by default protected.
|
|
288
|
+
if (useWatchdog && !context) {
|
|
289
|
+
const watchdog = await wrapWithWatchdog(buildAndCreateEditor, watchdogConfig);
|
|
272
290
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
291
|
+
watchdog.on('error', (_, { causesRestart }) => {
|
|
292
|
+
if (causesRestart) {
|
|
293
|
+
const prevEditor = EditorsRegistry.the.getItem(editorId);
|
|
294
|
+
|
|
295
|
+
/* v8 ignore next 3 */
|
|
296
|
+
if (prevEditor) {
|
|
297
|
+
cleanupOrphanEditorElements(prevEditor);
|
|
298
|
+
|
|
299
|
+
EditorsRegistry.the.unregister(editorId);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
278
302
|
});
|
|
279
303
|
|
|
280
|
-
|
|
281
|
-
|
|
304
|
+
watchdog.on('restart', () => {
|
|
305
|
+
const newInstance = watchdog.editor!;
|
|
282
306
|
|
|
283
|
-
|
|
284
|
-
if (!isEmptyObject(rootAttributes)) {
|
|
285
|
-
editor.model.change((writer) => {
|
|
286
|
-
writer.setAttributes(rootAttributes, editor.model.document.getRoot()!);
|
|
307
|
+
EditorsRegistry.the.register(editorId, newInstance);
|
|
287
308
|
});
|
|
288
|
-
}
|
|
289
309
|
|
|
290
|
-
|
|
291
|
-
|
|
310
|
+
await watchdog.create({});
|
|
311
|
+
|
|
312
|
+
return watchdog.editor!;
|
|
292
313
|
}
|
|
293
314
|
|
|
294
|
-
return
|
|
295
|
-
}
|
|
315
|
+
return buildAndCreateEditor();
|
|
316
|
+
}
|
|
296
317
|
}
|
|
297
318
|
|
|
298
319
|
/**
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { WatchdogConfig } from 'ckeditor5';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Represents a unique identifier for a CKEditor5 editor instance.
|
|
3
5
|
* This is typically the ID of the HTML element that the editor is attached to.
|
|
@@ -100,7 +102,7 @@ export type EditorPreset = {
|
|
|
100
102
|
/**
|
|
101
103
|
* Optional watchdog configuration for error recovery.
|
|
102
104
|
*/
|
|
103
|
-
watchdogConfig?:
|
|
105
|
+
watchdogConfig?: WatchdogConfig | null;
|
|
104
106
|
|
|
105
107
|
/**
|
|
106
108
|
* Optional custom translations for the editor.
|