ckeditor5-livewire 1.10.0 → 1.12.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/hooks/editable.d.ts +3 -5
- package/dist/hooks/editable.d.ts.map +1 -1
- package/dist/hooks/editor/editor.d.ts +0 -4
- package/dist/hooks/editor/editor.d.ts.map +1 -1
- package/dist/hooks/editor/plugins/livewire-sync.d.ts.map +1 -1
- package/dist/hooks/editor/types/editor-relaxed-constructor.type.d.ts +6 -0
- package/dist/hooks/editor/types/editor-relaxed-constructor.type.d.ts.map +1 -0
- package/dist/hooks/editor/types/index.d.ts +2 -0
- package/dist/hooks/editor/types/index.d.ts.map +1 -0
- package/dist/hooks/editor/utils/assign-initial-data-to-editor-config.d.ts +10 -0
- package/dist/hooks/editor/utils/assign-initial-data-to-editor-config.d.ts.map +1 -0
- package/dist/hooks/editor/utils/assign-source-elements-to-editor-config.d.ts +12 -0
- package/dist/hooks/editor/utils/assign-source-elements-to-editor-config.d.ts.map +1 -0
- package/dist/hooks/editor/utils/cleanup-orphan-editor-elements.d.ts +8 -0
- package/dist/hooks/editor/utils/cleanup-orphan-editor-elements.d.ts.map +1 -0
- package/dist/hooks/editor/utils/create-editor-in-context.d.ts +7 -4
- package/dist/hooks/editor/utils/create-editor-in-context.d.ts.map +1 -1
- package/dist/hooks/editor/utils/index.d.ts +4 -0
- package/dist/hooks/editor/utils/index.d.ts.map +1 -1
- package/dist/hooks/editor/utils/is-multiroot-editor-instance.d.ts +6 -0
- package/dist/hooks/editor/utils/is-multiroot-editor-instance.d.ts.map +1 -0
- package/dist/hooks/editor/utils/wrap-with-watchdog.d.ts +7 -16
- package/dist/hooks/editor/utils/wrap-with-watchdog.d.ts.map +1 -1
- package/dist/hooks/ui-part.d.ts +2 -6
- package/dist/hooks/ui-part.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +349 -232
- package/dist/index.mjs.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 +43 -10
- 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 +5 -5
- package/src/hooks/context/context.test.ts +3 -1
- package/src/hooks/editable.ts +74 -47
- package/src/hooks/editor/editor.test.ts +44 -9
- package/src/hooks/editor/editor.ts +161 -149
- package/src/hooks/editor/plugins/livewire-sync.ts +17 -8
- package/src/hooks/editor/types/editor-relaxed-constructor.type.ts +6 -0
- package/src/hooks/editor/types/index.ts +1 -0
- package/src/hooks/editor/utils/assign-initial-data-to-editor-config.ts +48 -0
- package/src/hooks/editor/utils/assign-source-elements-to-editor-config.ts +61 -0
- package/src/hooks/editor/utils/cleanup-orphan-editor-elements.test.ts +285 -0
- package/src/hooks/editor/utils/cleanup-orphan-editor-elements.ts +60 -0
- package/src/hooks/editor/utils/create-editor-in-context.ts +8 -7
- package/src/hooks/editor/utils/index.ts +4 -0
- package/src/hooks/editor/utils/is-multiroot-editor-instance.ts +8 -0
- package/src/hooks/editor/utils/wrap-with-watchdog.test.ts +34 -14
- package/src/hooks/editor/utils/wrap-with-watchdog.ts +16 -26
- package/src/hooks/ui-part.ts +10 -16
- 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 +212 -31
- package/src/shared/async-registry.ts +178 -61
- package/src/shared/index.ts +1 -0
|
@@ -2,7 +2,6 @@ import type { Editor } from 'ckeditor5';
|
|
|
2
2
|
|
|
3
3
|
import type { RootAttributesUpdater } from '../utils';
|
|
4
4
|
import type { EditorId, EditorLanguage, EditorPreset } from './typings';
|
|
5
|
-
import type { EditorCreator } from './utils';
|
|
6
5
|
|
|
7
6
|
import { ContextsRegistry } from '../../hooks/context';
|
|
8
7
|
import { isEmptyObject, waitFor } from '../../shared';
|
|
@@ -14,6 +13,9 @@ import {
|
|
|
14
13
|
createSyncEditorWithInputPlugin,
|
|
15
14
|
} from './plugins';
|
|
16
15
|
import {
|
|
16
|
+
assignInitialDataToEditorConfig,
|
|
17
|
+
assignSourceElementsToEditorConfig,
|
|
18
|
+
cleanupOrphanEditorElements,
|
|
17
19
|
createEditorInContext,
|
|
18
20
|
isSingleRootEditor,
|
|
19
21
|
loadAllEditorTranslations,
|
|
@@ -34,11 +36,6 @@ import {
|
|
|
34
36
|
* The Livewire hook that manages the lifecycle of CKEditor5 instances.
|
|
35
37
|
*/
|
|
36
38
|
export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
37
|
-
/**
|
|
38
|
-
* The promise that resolves to the editor instance.
|
|
39
|
-
*/
|
|
40
|
-
private editorPromise: Promise<Editor> | null = null;
|
|
41
|
-
|
|
42
39
|
/**
|
|
43
40
|
* Root attributes updater for the main editor root.
|
|
44
41
|
*/
|
|
@@ -53,27 +50,51 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
53
50
|
EditorsRegistry.the.resetErrors(editorId);
|
|
54
51
|
|
|
55
52
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
53
|
+
const editor = await this.createEditor();
|
|
54
|
+
const editorContext = unwrapEditorContext(editor);
|
|
55
|
+
const watchdog = unwrapEditorWatchdog(editor);
|
|
59
56
|
|
|
60
|
-
// Do not even try to broadcast about the registration of the editor
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
// Do not even try to broadcast about the registration of the editor if hook was immediately destroyed.
|
|
58
|
+
/* v8 ignore next 3 */
|
|
59
|
+
if (this.isBeingDestroyed()) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
65
62
|
|
|
63
|
+
// Run some stuff that have to be reinitialized every-time editor is being restarted.
|
|
64
|
+
const unmountDestroyWatcher = EditorsRegistry.the.mountEffect(editorId, (editor) => {
|
|
65
|
+
// Enforce deregistration of the editor when it's being destroyed by watchdog.
|
|
66
66
|
editor.once('destroy', () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
// Let's handle case when watchdog (or context watchdog) destroyed editor "externally"
|
|
68
|
+
// user might also manually kill the editor using `.destroy()` method.
|
|
69
|
+
// Keep pending callbacks though. Someone might register new callbacks just before calling `.destroy()`.
|
|
70
|
+
EditorsRegistry.the.unregister(editorId, false);
|
|
71
|
+
}, { priority: 'highest' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.onBeforeDestroy(async () => {
|
|
75
|
+
// If for some reason editor not fired `destroy`, enforce deregistration.
|
|
76
|
+
EditorsRegistry.the.unregister(editorId);
|
|
77
|
+
unmountDestroyWatcher();
|
|
78
|
+
|
|
79
|
+
if (editorContext) {
|
|
80
|
+
// If context is present, make sure it's not in unmounting phase, as it'll kill the editors.
|
|
81
|
+
// If it's being destroyed, don't do anything, as the context will take care of it.
|
|
82
|
+
if (editorContext.state !== 'unavailable') {
|
|
83
|
+
await editorContext.context.remove(editorContext.editorContextId);
|
|
70
84
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
85
|
+
}
|
|
86
|
+
else if (watchdog) {
|
|
87
|
+
await watchdog.destroy();
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await editor.destroy();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
EditorsRegistry.the.register(editorId, editor);
|
|
73
95
|
}
|
|
74
96
|
catch (error: any) {
|
|
75
97
|
console.error(`Error initializing CKEditor5 instance with ID "${editorId}":`, error);
|
|
76
|
-
this.editorPromise = null;
|
|
77
98
|
EditorsRegistry.the.error(editorId, error);
|
|
78
99
|
}
|
|
79
100
|
}
|
|
@@ -85,42 +106,13 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
85
106
|
override async destroyed() {
|
|
86
107
|
// Let's hide the element during destruction to prevent flickering.
|
|
87
108
|
this.element.style.display = 'none';
|
|
88
|
-
|
|
89
|
-
// Let's wait for the mounted promise to resolve before proceeding with destruction.
|
|
90
|
-
try {
|
|
91
|
-
const editor = await this.editorPromise;
|
|
92
|
-
|
|
93
|
-
if (!editor) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const editorContext = unwrapEditorContext(editor);
|
|
98
|
-
const watchdog = unwrapEditorWatchdog(editor);
|
|
99
|
-
|
|
100
|
-
if (editorContext) {
|
|
101
|
-
// If context is present, make sure it's not in unmounting phase, as it'll kill the editors.
|
|
102
|
-
// If it's being destroyed, don't do anything, as the context will take care of it.
|
|
103
|
-
if (editorContext.state !== 'unavailable') {
|
|
104
|
-
await editorContext.context.remove(editorContext.editorContextId);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else if (watchdog) {
|
|
108
|
-
await watchdog.destroy();
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
await editor.destroy();
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
this.editorPromise = null;
|
|
116
|
-
}
|
|
117
109
|
}
|
|
118
110
|
|
|
119
111
|
/**
|
|
120
112
|
* Updates the editor content when the component is updated after commit changes.
|
|
121
113
|
*/
|
|
122
114
|
override async afterCommitSynced(): Promise<void> {
|
|
123
|
-
const editor = await this.
|
|
115
|
+
const editor = await EditorsRegistry.the.waitFor(this.canonical.editorId);
|
|
124
116
|
|
|
125
117
|
/* v8 ignore if -- @preserve */
|
|
126
118
|
if (editor) {
|
|
@@ -152,7 +144,7 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
152
144
|
editableHeight,
|
|
153
145
|
saveDebounceMs,
|
|
154
146
|
language,
|
|
155
|
-
watchdog,
|
|
147
|
+
watchdog: useWatchdog,
|
|
156
148
|
content,
|
|
157
149
|
} = this.canonical;
|
|
158
150
|
|
|
@@ -160,133 +152,153 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
160
152
|
customTranslations,
|
|
161
153
|
editorType,
|
|
162
154
|
licenseKey,
|
|
155
|
+
watchdogConfig,
|
|
163
156
|
config: { plugins, ...config },
|
|
164
157
|
} = preset;
|
|
165
158
|
|
|
166
|
-
|
|
167
|
-
let Constructor: EditorCreator = await loadEditorConstructor(editorType);
|
|
159
|
+
const Constructor = await loadEditorConstructor(editorType);
|
|
168
160
|
const context = await (
|
|
169
161
|
contextId
|
|
170
162
|
? ContextsRegistry.the.waitFor(contextId)
|
|
171
163
|
: null
|
|
172
164
|
);
|
|
173
165
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Builds the full editor configuration and creates the editor instance.
|
|
168
|
+
*/
|
|
169
|
+
const buildAndCreateEditor = async () => {
|
|
170
|
+
const { loadedPlugins, hasPremium } = await loadEditorPlugins(plugins);
|
|
177
171
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
172
|
+
// Add integration specific plugins.
|
|
173
|
+
loadedPlugins.push(
|
|
174
|
+
await createLivewireSyncPlugin(
|
|
175
|
+
{
|
|
176
|
+
saveDebounceMs,
|
|
177
|
+
component: this,
|
|
178
|
+
},
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
if (isSingleRootEditor(editorType)) {
|
|
183
|
+
loadedPlugins.push(
|
|
184
|
+
await createSyncEditorWithInputPlugin(saveDebounceMs),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
183
187
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
// Mix custom translations with loaded translations.
|
|
189
|
+
const loadedTranslations = await loadAllEditorTranslations(language, hasPremium);
|
|
190
|
+
const mixedTranslations = [
|
|
191
|
+
...loadedTranslations,
|
|
192
|
+
normalizeCustomTranslations(customTranslations || {}),
|
|
193
|
+
]
|
|
194
|
+
.filter(translations => !isEmptyObject(translations));
|
|
195
|
+
|
|
196
|
+
// Let's query all elements, and create basic configuration.
|
|
197
|
+
let initialData: string | Record<string, string> = {
|
|
198
|
+
...content,
|
|
199
|
+
...queryEditablesSnapshotContent(editorId),
|
|
200
|
+
};
|
|
187
201
|
|
|
188
|
-
|
|
202
|
+
if (isSingleRootEditor(editorType)) {
|
|
203
|
+
initialData = initialData['main'] || '';
|
|
204
|
+
}
|
|
189
205
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
206
|
+
// Depending of the editor type, and parent lookup for nearest context or initialize it without it.
|
|
207
|
+
const editor = await (async () => {
|
|
208
|
+
let sourceElements: HTMLElement | Record<string, HTMLElement> = queryEditablesElements(editorId);
|
|
209
|
+
|
|
210
|
+
// Handle special case when user specified `initialData` of several root elements, but editable components
|
|
211
|
+
// are not yet present in the DOM. In other words - editor is initialized before attaching root elements.
|
|
212
|
+
if (!(sourceElements instanceof HTMLElement) && !('main' in sourceElements)) {
|
|
213
|
+
const requiredRoots = (
|
|
214
|
+
editorType === 'decoupled'
|
|
215
|
+
? ['main']
|
|
216
|
+
: Object.keys(initialData as Record<string, string>)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
if (!checkIfAllRootsArePresent(sourceElements, requiredRoots)) {
|
|
220
|
+
sourceElements = await waitForAllRootsToBePresent(editorId, requiredRoots);
|
|
221
|
+
initialData = {
|
|
222
|
+
...content,
|
|
223
|
+
...queryEditablesSnapshotContent(editorId),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
199
227
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
228
|
+
// If single root editor, unwrap the element from the object.
|
|
229
|
+
if (isSingleRootEditor(editorType) && 'main' in sourceElements) {
|
|
230
|
+
sourceElements = sourceElements['main'];
|
|
231
|
+
}
|
|
205
232
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
233
|
+
let resolvedConfig = { ...config };
|
|
234
|
+
|
|
235
|
+
// Do some postprocessing on received configuration.
|
|
236
|
+
resolvedConfig = resolveEditorConfigElementReferences(resolvedConfig);
|
|
237
|
+
resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
|
|
238
|
+
resolvedConfig = assignSourceElementsToEditorConfig(Constructor, sourceElements, resolvedConfig);
|
|
239
|
+
resolvedConfig = assignInitialDataToEditorConfig(initialData, resolvedConfig);
|
|
240
|
+
|
|
241
|
+
// Construct parsed config.
|
|
242
|
+
const parsedConfig = {
|
|
243
|
+
...resolvedConfig,
|
|
244
|
+
licenseKey,
|
|
245
|
+
plugins: loadedPlugins,
|
|
246
|
+
language,
|
|
247
|
+
...mixedTranslations.length && {
|
|
248
|
+
translations: mixedTranslations,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (!context || !(sourceElements instanceof HTMLElement)) {
|
|
253
|
+
return Constructor.create(parsedConfig);
|
|
254
|
+
}
|
|
219
255
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
256
|
+
const result = await createEditorInContext({
|
|
257
|
+
context,
|
|
258
|
+
creator: Constructor,
|
|
259
|
+
config: parsedConfig,
|
|
260
|
+
});
|
|
223
261
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
let sourceElements: 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 (!(sourceElements instanceof HTMLElement) && !('main' in sourceElements)) {
|
|
231
|
-
const requiredRoots = (
|
|
232
|
-
editorType === 'decoupled'
|
|
233
|
-
? ['main']
|
|
234
|
-
: Object.keys(initialData as Record<string, string>)
|
|
235
|
-
);
|
|
262
|
+
return result.editor;
|
|
263
|
+
})();
|
|
236
264
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
initialData = {
|
|
240
|
-
...content,
|
|
241
|
-
...queryEditablesSnapshotContent(editorId),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
265
|
+
if (isSingleRootEditor(editorType) && editableHeight) {
|
|
266
|
+
setEditorEditableHeight(editor, editableHeight);
|
|
244
267
|
}
|
|
245
268
|
|
|
246
|
-
|
|
247
|
-
if (isSingleRootEditor(editorType) && 'main' in sourceElements) {
|
|
248
|
-
sourceElements = sourceElements['main'];
|
|
249
|
-
}
|
|
269
|
+
this.applyRootAttributes(editor);
|
|
250
270
|
|
|
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
|
-
};
|
|
271
|
+
return editor;
|
|
272
|
+
};
|
|
268
273
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
274
|
+
// Do not use editor specific watchdog if context is attached, as the context is by default protected.
|
|
275
|
+
if (useWatchdog && !context) {
|
|
276
|
+
const watchdog = await wrapWithWatchdog(buildAndCreateEditor, watchdogConfig);
|
|
277
|
+
|
|
278
|
+
// Cleanup editor registry before restart of the editor (restart might fail too).
|
|
279
|
+
watchdog.on('error', (_, { causesRestart }) => {
|
|
280
|
+
if (causesRestart) {
|
|
281
|
+
const prevEditor = EditorsRegistry.the.getItem(editorId);
|
|
282
|
+
|
|
283
|
+
/* v8 ignore next 3 */
|
|
284
|
+
if (prevEditor) {
|
|
285
|
+
cleanupOrphanEditorElements(prevEditor);
|
|
286
|
+
EditorsRegistry.the.unregister(editorId);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
272
290
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
creator: Constructor,
|
|
277
|
-
config: parsedConfig,
|
|
291
|
+
// Register new instance after editor restarted.
|
|
292
|
+
watchdog.on('restart', () => {
|
|
293
|
+
EditorsRegistry.the.register(editorId, watchdog.editor!);
|
|
278
294
|
});
|
|
279
295
|
|
|
280
|
-
|
|
281
|
-
})();
|
|
296
|
+
await watchdog.create({});
|
|
282
297
|
|
|
283
|
-
|
|
284
|
-
setEditorEditableHeight(editor, editableHeight);
|
|
298
|
+
return watchdog.editor!;
|
|
285
299
|
}
|
|
286
300
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return editor;
|
|
301
|
+
return buildAndCreateEditor();
|
|
290
302
|
};
|
|
291
303
|
}
|
|
292
304
|
|
|
@@ -108,7 +108,9 @@ export async function createLivewireSyncPlugin(
|
|
|
108
108
|
* Setups the content sync from Livewire to the editor when Livewire emits an event.
|
|
109
109
|
*/
|
|
110
110
|
private setupSetEditorContentHandler() {
|
|
111
|
-
|
|
111
|
+
const { editor } = this;
|
|
112
|
+
|
|
113
|
+
const handler = ({ editorId, content }: SetContentPayload) => {
|
|
112
114
|
if (editorId !== component.canonical.editorId) {
|
|
113
115
|
return;
|
|
114
116
|
}
|
|
@@ -116,9 +118,16 @@ export async function createLivewireSyncPlugin(
|
|
|
116
118
|
const currentValues = this.getEditorRootsValues();
|
|
117
119
|
|
|
118
120
|
if (!shallowEqual(currentValues, content)) {
|
|
119
|
-
|
|
121
|
+
editor.setData(content);
|
|
120
122
|
}
|
|
121
|
-
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const clean: any = Livewire.on('set-editor-content', handler);
|
|
126
|
+
|
|
127
|
+
/* v8 ignore next if -- @preserve */
|
|
128
|
+
if (typeof clean === 'function') {
|
|
129
|
+
editor.once('destroy', clean);
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
/**
|
|
@@ -150,21 +159,21 @@ export async function createLivewireSyncPlugin(
|
|
|
150
159
|
};
|
|
151
160
|
|
|
152
161
|
const debouncedSync = debounce(saveDebounceMs, syncContentChange);
|
|
153
|
-
|
|
162
|
+
|
|
163
|
+
// Apply small debounce to avoid race conditions during re-mount of editables during watchdog restart.
|
|
164
|
+
// CKEditor tends to reset roots map and re-assign value in the same tick which may confuse two way binding.
|
|
165
|
+
model.document.on('change:data', debounce(10, () => {
|
|
154
166
|
if (ui.focusTracker.isFocused) {
|
|
155
167
|
debouncedSync();
|
|
156
168
|
}
|
|
157
169
|
else {
|
|
158
170
|
syncContentChange();
|
|
159
171
|
}
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
model.document.on('change:data', onChangeData);
|
|
172
|
+
}));
|
|
163
173
|
|
|
164
174
|
editor.once('ready', syncContentChange);
|
|
165
175
|
editor.once('destroy', () => {
|
|
166
176
|
isDestroyed = true;
|
|
167
|
-
model.document.off('change:data', onChangeData);
|
|
168
177
|
});
|
|
169
178
|
}
|
|
170
179
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './editor-relaxed-constructor.type';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { EditorConfig } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Assigns initial data to specified editor config.
|
|
5
|
+
*
|
|
6
|
+
* @param dataOrMap Initial data to be assigned to config.
|
|
7
|
+
* @param config Config of the editor.
|
|
8
|
+
* @returns The updated configuration object.
|
|
9
|
+
*/
|
|
10
|
+
export function assignInitialDataToEditorConfig<C extends EditorConfig>(
|
|
11
|
+
dataOrMap: string | Record<string, string>,
|
|
12
|
+
config: C,
|
|
13
|
+
): C {
|
|
14
|
+
const dataMap = toDataMap(dataOrMap);
|
|
15
|
+
const allRootsKeys = new Set([
|
|
16
|
+
...Object.keys(dataMap),
|
|
17
|
+
...Object.keys(config.roots ?? {}),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const rootsConfig = Array.from(allRootsKeys).reduce((acc, rootKey) => ({
|
|
21
|
+
...acc,
|
|
22
|
+
[rootKey]: {
|
|
23
|
+
...config.roots?.[rootKey],
|
|
24
|
+
...rootKey === 'main' ? config.root : {},
|
|
25
|
+
|
|
26
|
+
/* v8 ignore start -- @preserve */
|
|
27
|
+
...rootKey in dataMap
|
|
28
|
+
? {
|
|
29
|
+
initialData: dataMap[rootKey],
|
|
30
|
+
}
|
|
31
|
+
: {},
|
|
32
|
+
/* v8 ignore stop -- @preserve */
|
|
33
|
+
},
|
|
34
|
+
}), Object.create(config.roots || {}));
|
|
35
|
+
|
|
36
|
+
const mappedConfig: C = {
|
|
37
|
+
...config,
|
|
38
|
+
roots: rootsConfig,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
delete mappedConfig.root;
|
|
42
|
+
|
|
43
|
+
return mappedConfig;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toDataMap(element: string | Record<string, string>): Record<string, string> {
|
|
47
|
+
return typeof element === 'string' ? { main: element } : { ...element };
|
|
48
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { EditorConfig } from 'ckeditor5';
|
|
2
|
+
|
|
3
|
+
import type { EditorRelaxedConstructor } from '../types/editor-relaxed-constructor.type';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Assigns a DOM element to the editor configuration in a way that is compatible with the specific editor type.
|
|
7
|
+
*
|
|
8
|
+
* @param Editor Constructor of the editor used to determine the location of element config entry.
|
|
9
|
+
* @param elementOrMap Element to be assigned to config.
|
|
10
|
+
* @param config Config of the editor.
|
|
11
|
+
* @returns The updated configuration object.
|
|
12
|
+
*/
|
|
13
|
+
export function assignSourceElementsToEditorConfig<C extends EditorConfig>(
|
|
14
|
+
Editor: EditorRelaxedConstructor,
|
|
15
|
+
elementOrMap: HTMLElement | Record<string, HTMLElement>,
|
|
16
|
+
config: C,
|
|
17
|
+
): C {
|
|
18
|
+
const elementsMap = toElementsMap(elementOrMap);
|
|
19
|
+
|
|
20
|
+
if (!Editor.editorName || Editor.editorName === 'ClassicEditor') {
|
|
21
|
+
return {
|
|
22
|
+
...config,
|
|
23
|
+
attachTo: elementsMap['main'],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const allRootsKeys = new Set([
|
|
28
|
+
...Object.keys(elementsMap),
|
|
29
|
+
...Object.keys(config.roots ?? {}),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const rootsConfig = Array.from(allRootsKeys).reduce((acc, rootKey) => ({
|
|
33
|
+
...acc,
|
|
34
|
+
[rootKey]: {
|
|
35
|
+
/* v8 ignore next */
|
|
36
|
+
...config.roots?.[rootKey],
|
|
37
|
+
...rootKey === 'main' ? config.root : {},
|
|
38
|
+
|
|
39
|
+
/* v8 ignore start -- @preserve */
|
|
40
|
+
...rootKey in elementsMap
|
|
41
|
+
? {
|
|
42
|
+
element: elementsMap[rootKey],
|
|
43
|
+
}
|
|
44
|
+
: {},
|
|
45
|
+
/* v8 ignore stop -- @preserve */
|
|
46
|
+
},
|
|
47
|
+
}), Object.create(config.roots || {}));
|
|
48
|
+
|
|
49
|
+
const mappedConfig: C = {
|
|
50
|
+
...config,
|
|
51
|
+
roots: rootsConfig,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
delete mappedConfig.root;
|
|
55
|
+
|
|
56
|
+
return mappedConfig;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toElementsMap(element: HTMLElement | Record<string, HTMLElement>): Record<string, HTMLElement> {
|
|
60
|
+
return element instanceof HTMLElement ? { main: element } : { ...element };
|
|
61
|
+
}
|