ckeditor5-livewire 1.9.0 → 1.11.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/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 +6 -1
- package/dist/hooks/editor/utils/create-editor-in-context.d.ts.map +1 -1
- package/dist/hooks/editor/utils/index.d.ts +2 -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 +297 -220
- 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 +3 -3
- package/src/hooks/context/context.test.ts +3 -1
- package/src/hooks/editable.ts +73 -46
- package/src/hooks/editor/editor.test.ts +44 -9
- package/src/hooks/editor/editor.ts +159 -149
- package/src/hooks/editor/plugins/livewire-sync.ts +17 -8
- 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 +6 -2
- package/src/hooks/editor/utils/index.ts +2 -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,7 @@ import {
|
|
|
14
13
|
createSyncEditorWithInputPlugin,
|
|
15
14
|
} from './plugins';
|
|
16
15
|
import {
|
|
16
|
+
cleanupOrphanEditorElements,
|
|
17
17
|
createEditorInContext,
|
|
18
18
|
isSingleRootEditor,
|
|
19
19
|
loadAllEditorTranslations,
|
|
@@ -34,11 +34,6 @@ import {
|
|
|
34
34
|
* The Livewire hook that manages the lifecycle of CKEditor5 instances.
|
|
35
35
|
*/
|
|
36
36
|
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
37
|
/**
|
|
43
38
|
* Root attributes updater for the main editor root.
|
|
44
39
|
*/
|
|
@@ -53,27 +48,51 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
53
48
|
EditorsRegistry.the.resetErrors(editorId);
|
|
54
49
|
|
|
55
50
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
51
|
+
const editor = await this.createEditor();
|
|
52
|
+
const editorContext = unwrapEditorContext(editor);
|
|
53
|
+
const watchdog = unwrapEditorWatchdog(editor);
|
|
59
54
|
|
|
60
|
-
// Do not even try to broadcast about the registration of the editor
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
// Do not even try to broadcast about the registration of the editor if hook was immediately destroyed.
|
|
56
|
+
/* v8 ignore next 3 */
|
|
57
|
+
if (this.isBeingDestroyed()) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
65
60
|
|
|
61
|
+
// Run some stuff that have to be reinitialized every-time editor is being restarted.
|
|
62
|
+
const unmountDestroyWatcher = EditorsRegistry.the.mountEffect(editorId, (editor) => {
|
|
63
|
+
// Enforce deregistration of the editor when it's being destroyed by watchdog.
|
|
66
64
|
editor.once('destroy', () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
// Let's handle case when watchdog (or context watchdog) destroyed editor "externally"
|
|
66
|
+
// user might also manually kill the editor using `.destroy()` method.
|
|
67
|
+
// Keep pending callbacks though. Someone might register new callbacks just before calling `.destroy()`.
|
|
68
|
+
EditorsRegistry.the.unregister(editorId, false);
|
|
69
|
+
}, { priority: 'highest' });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.onBeforeDestroy(async () => {
|
|
73
|
+
// If for some reason editor not fired `destroy`, enforce deregistration.
|
|
74
|
+
EditorsRegistry.the.unregister(editorId);
|
|
75
|
+
unmountDestroyWatcher();
|
|
76
|
+
|
|
77
|
+
if (editorContext) {
|
|
78
|
+
// If context is present, make sure it's not in unmounting phase, as it'll kill the editors.
|
|
79
|
+
// If it's being destroyed, don't do anything, as the context will take care of it.
|
|
80
|
+
if (editorContext.state !== 'unavailable') {
|
|
81
|
+
await editorContext.context.remove(editorContext.editorContextId);
|
|
70
82
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
83
|
+
}
|
|
84
|
+
else if (watchdog) {
|
|
85
|
+
await watchdog.destroy();
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
await editor.destroy();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
EditorsRegistry.the.register(editorId, editor);
|
|
73
93
|
}
|
|
74
94
|
catch (error: any) {
|
|
75
95
|
console.error(`Error initializing CKEditor5 instance with ID "${editorId}":`, error);
|
|
76
|
-
this.editorPromise = null;
|
|
77
96
|
EditorsRegistry.the.error(editorId, error);
|
|
78
97
|
}
|
|
79
98
|
}
|
|
@@ -85,42 +104,13 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
85
104
|
override async destroyed() {
|
|
86
105
|
// Let's hide the element during destruction to prevent flickering.
|
|
87
106
|
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
107
|
}
|
|
118
108
|
|
|
119
109
|
/**
|
|
120
110
|
* Updates the editor content when the component is updated after commit changes.
|
|
121
111
|
*/
|
|
122
112
|
override async afterCommitSynced(): Promise<void> {
|
|
123
|
-
const editor = await this.
|
|
113
|
+
const editor = await EditorsRegistry.the.waitFor(this.canonical.editorId);
|
|
124
114
|
|
|
125
115
|
/* v8 ignore if -- @preserve */
|
|
126
116
|
if (editor) {
|
|
@@ -152,7 +142,7 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
152
142
|
editableHeight,
|
|
153
143
|
saveDebounceMs,
|
|
154
144
|
language,
|
|
155
|
-
watchdog,
|
|
145
|
+
watchdog: useWatchdog,
|
|
156
146
|
content,
|
|
157
147
|
} = this.canonical;
|
|
158
148
|
|
|
@@ -160,133 +150,153 @@ export class EditorComponentHook extends ClassHook<Snapshot> {
|
|
|
160
150
|
customTranslations,
|
|
161
151
|
editorType,
|
|
162
152
|
licenseKey,
|
|
153
|
+
watchdogConfig,
|
|
163
154
|
config: { plugins, ...config },
|
|
164
155
|
} = preset;
|
|
165
156
|
|
|
166
|
-
|
|
167
|
-
let Constructor: EditorCreator = await loadEditorConstructor(editorType);
|
|
157
|
+
const Constructor = await loadEditorConstructor(editorType);
|
|
168
158
|
const context = await (
|
|
169
159
|
contextId
|
|
170
160
|
? ContextsRegistry.the.waitFor(contextId)
|
|
171
161
|
: null
|
|
172
162
|
);
|
|
173
163
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Builds the full editor configuration and creates the editor instance.
|
|
166
|
+
*/
|
|
167
|
+
const buildAndCreateEditor = async () => {
|
|
168
|
+
const { loadedPlugins, hasPremium } = await loadEditorPlugins(plugins);
|
|
177
169
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
170
|
+
// Add integration specific plugins.
|
|
171
|
+
loadedPlugins.push(
|
|
172
|
+
await createLivewireSyncPlugin(
|
|
173
|
+
{
|
|
174
|
+
saveDebounceMs,
|
|
175
|
+
component: this,
|
|
176
|
+
},
|
|
177
|
+
),
|
|
178
|
+
);
|
|
181
179
|
|
|
182
|
-
|
|
180
|
+
if (isSingleRootEditor(editorType)) {
|
|
181
|
+
loadedPlugins.push(
|
|
182
|
+
await createSyncEditorWithInputPlugin(saveDebounceMs),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
183
185
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
// Mix custom translations with loaded translations.
|
|
187
|
+
const loadedTranslations = await loadAllEditorTranslations(language, hasPremium);
|
|
188
|
+
const mixedTranslations = [
|
|
189
|
+
...loadedTranslations,
|
|
190
|
+
normalizeCustomTranslations(customTranslations || {}),
|
|
191
|
+
]
|
|
192
|
+
.filter(translations => !isEmptyObject(translations));
|
|
193
|
+
|
|
194
|
+
// Let's query all elements, and create basic configuration.
|
|
195
|
+
let initialData: string | Record<string, string> = {
|
|
196
|
+
...content,
|
|
197
|
+
...queryEditablesSnapshotContent(editorId),
|
|
198
|
+
};
|
|
187
199
|
|
|
188
|
-
|
|
200
|
+
if (isSingleRootEditor(editorType)) {
|
|
201
|
+
initialData = initialData['main'] || '';
|
|
202
|
+
}
|
|
189
203
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
// Depending of the editor type, and parent lookup for nearest context or initialize it without it.
|
|
205
|
+
const editor = await (async () => {
|
|
206
|
+
let sourceElements: HTMLElement | Record<string, HTMLElement> = queryEditablesElements(editorId);
|
|
207
|
+
|
|
208
|
+
// Handle special case when user specified `initialData` of several root elements, but editable components
|
|
209
|
+
// are not yet present in the DOM. In other words - editor is initialized before attaching root elements.
|
|
210
|
+
if (!(sourceElements instanceof HTMLElement) && !('main' in sourceElements)) {
|
|
211
|
+
const requiredRoots = (
|
|
212
|
+
editorType === 'decoupled'
|
|
213
|
+
? ['main']
|
|
214
|
+
: Object.keys(initialData as Record<string, string>)
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
if (!checkIfAllRootsArePresent(sourceElements, requiredRoots)) {
|
|
218
|
+
sourceElements = await waitForAllRootsToBePresent(editorId, requiredRoots);
|
|
219
|
+
initialData = {
|
|
220
|
+
...content,
|
|
221
|
+
...queryEditablesSnapshotContent(editorId),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
199
225
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
226
|
+
// If single root editor, unwrap the element from the object.
|
|
227
|
+
if (isSingleRootEditor(editorType) && 'main' in sourceElements) {
|
|
228
|
+
sourceElements = sourceElements['main'];
|
|
229
|
+
}
|
|
205
230
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
// Construct parsed config. First resolve DOM element references in the provided configuration.
|
|
232
|
+
let resolvedConfig = resolveEditorConfigElementReferences(config);
|
|
233
|
+
|
|
234
|
+
// Then resolve translation references in the provided configuration, using the mixed translations.
|
|
235
|
+
resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
|
|
236
|
+
|
|
237
|
+
// Construct parsed config.
|
|
238
|
+
const parsedConfig = {
|
|
239
|
+
...resolvedConfig,
|
|
240
|
+
initialData,
|
|
241
|
+
licenseKey,
|
|
242
|
+
plugins: loadedPlugins,
|
|
243
|
+
language,
|
|
244
|
+
...mixedTranslations.length && {
|
|
245
|
+
translations: mixedTranslations,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
if (!context || !(sourceElements instanceof HTMLElement)) {
|
|
250
|
+
return Constructor.create(sourceElements as any, parsedConfig);
|
|
251
|
+
}
|
|
219
252
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
253
|
+
const result = await createEditorInContext({
|
|
254
|
+
context,
|
|
255
|
+
element: sourceElements,
|
|
256
|
+
creator: Constructor,
|
|
257
|
+
config: parsedConfig,
|
|
258
|
+
});
|
|
223
259
|
|
|
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
|
-
);
|
|
260
|
+
return result.editor;
|
|
261
|
+
})();
|
|
236
262
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
initialData = {
|
|
240
|
-
...content,
|
|
241
|
-
...queryEditablesSnapshotContent(editorId),
|
|
242
|
-
};
|
|
243
|
-
}
|
|
263
|
+
if (isSingleRootEditor(editorType) && editableHeight) {
|
|
264
|
+
setEditorEditableHeight(editor, editableHeight);
|
|
244
265
|
}
|
|
245
266
|
|
|
246
|
-
|
|
247
|
-
if (isSingleRootEditor(editorType) && 'main' in sourceElements) {
|
|
248
|
-
sourceElements = sourceElements['main'];
|
|
249
|
-
}
|
|
267
|
+
this.applyRootAttributes(editor);
|
|
250
268
|
|
|
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
|
-
};
|
|
269
|
+
return editor;
|
|
270
|
+
};
|
|
268
271
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
+
// Do not use editor specific watchdog if context is attached, as the context is by default protected.
|
|
273
|
+
if (useWatchdog && !context) {
|
|
274
|
+
const watchdog = await wrapWithWatchdog(buildAndCreateEditor, watchdogConfig);
|
|
275
|
+
|
|
276
|
+
// Cleanup editor registry before restart of the editor (restart might fail too).
|
|
277
|
+
watchdog.on('error', (_, { causesRestart }) => {
|
|
278
|
+
if (causesRestart) {
|
|
279
|
+
const prevEditor = EditorsRegistry.the.getItem(editorId);
|
|
280
|
+
|
|
281
|
+
/* v8 ignore next 3 */
|
|
282
|
+
if (prevEditor) {
|
|
283
|
+
cleanupOrphanEditorElements(prevEditor);
|
|
284
|
+
EditorsRegistry.the.unregister(editorId);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
272
288
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
creator: Constructor,
|
|
277
|
-
config: parsedConfig,
|
|
289
|
+
// Register new instance after editor restarted.
|
|
290
|
+
watchdog.on('restart', () => {
|
|
291
|
+
EditorsRegistry.the.register(editorId, watchdog.editor!);
|
|
278
292
|
});
|
|
279
293
|
|
|
280
|
-
|
|
281
|
-
})();
|
|
294
|
+
await watchdog.create({});
|
|
282
295
|
|
|
283
|
-
|
|
284
|
-
setEditorEditableHeight(editor, editableHeight);
|
|
296
|
+
return watchdog.editor!;
|
|
285
297
|
}
|
|
286
298
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return editor;
|
|
299
|
+
return buildAndCreateEditor();
|
|
290
300
|
};
|
|
291
301
|
}
|
|
292
302
|
|
|
@@ -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
|
|