ckeditor5-blazor 1.11.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/elements/editable.d.ts.map +1 -1
- package/dist/elements/editor/editor.d.ts.map +1 -1
- package/dist/elements/editor/utils/assign-editor-roots-to-config.d.ts +17 -0
- package/dist/elements/editor/utils/assign-editor-roots-to-config.d.ts.map +1 -0
- package/dist/elements/editor/utils/index.d.ts +2 -3
- package/dist/elements/editor/utils/index.d.ts.map +1 -1
- package/dist/elements/editor/utils/query-all-editor-editables.d.ts +24 -0
- package/dist/elements/editor/utils/query-all-editor-editables.d.ts.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +395 -437
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/elements/editable.test.ts +42 -3
- package/src/elements/editable.ts +2 -0
- package/src/elements/editor/editor.test.ts +97 -13
- package/src/elements/editor/editor.ts +39 -64
- package/src/elements/editor/utils/assign-editor-roots-to-config.ts +61 -0
- package/src/elements/editor/utils/get-editor-roots-values.ts +1 -1
- package/src/elements/editor/utils/index.ts +2 -3
- package/src/elements/editor/utils/query-all-editor-editables.ts +81 -0
- package/src/elements/ui-part.test.ts +8 -9
- package/src/interop/create-editable-blazor-interop.test.ts +5 -2
- package/dist/elements/editor/utils/assign-initial-data-to-editor-config.d.ts +0 -10
- package/dist/elements/editor/utils/assign-initial-data-to-editor-config.d.ts.map +0 -1
- package/dist/elements/editor/utils/assign-source-elements-to-editor-config.d.ts +0 -12
- package/dist/elements/editor/utils/assign-source-elements-to-editor-config.d.ts.map +0 -1
- package/dist/elements/editor/utils/query-editor-editables.d.ts +0 -25
- package/dist/elements/editor/utils/query-editor-editables.d.ts.map +0 -1
- package/src/elements/editor/utils/assign-initial-data-to-editor-config.ts +0 -47
- package/src/elements/editor/utils/assign-source-elements-to-editor-config.ts +0 -59
- package/src/elements/editor/utils/query-editor-editables.ts +0 -101
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ckeditor5-blazor",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "CKEditor 5 integration for Blazor",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
],
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@vitest/coverage-v8": "^4.0.18",
|
|
31
|
-
"ckeditor5": "^48.
|
|
32
|
-
"ckeditor5-premium-features": "^48.
|
|
31
|
+
"ckeditor5": "^48.2.0",
|
|
32
|
+
"ckeditor5-premium-features": "^48.2.0",
|
|
33
33
|
"happy-dom": "^20.8.9",
|
|
34
34
|
"typescript": "^5.9.3",
|
|
35
35
|
"vite": "^7.3.2",
|
|
@@ -233,6 +233,42 @@ describe('editable component', () => {
|
|
|
233
233
|
|
|
234
234
|
expect(editor.model.document.getRoot('foo')).toBe(null);
|
|
235
235
|
});
|
|
236
|
+
|
|
237
|
+
it('should set proper root element name on initial added root', async () => {
|
|
238
|
+
renderTestEditable({
|
|
239
|
+
content: '<p>Foo</p>',
|
|
240
|
+
rootName: 'foo',
|
|
241
|
+
rootModelElementName: '$inlineRoot',
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
renderTestEditor({
|
|
245
|
+
preset: createEditorPreset('multiroot'),
|
|
246
|
+
content: {},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const editor = await waitForTestEditor<MultiRootEditor>();
|
|
250
|
+
|
|
251
|
+
expect(editor.model.document.getRoot('foo')?.name).to.be.equal('$inlineRoot');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should set proper root element name on lazy added root', async () => {
|
|
255
|
+
renderTestEditor({
|
|
256
|
+
preset: createEditorPreset('multiroot'),
|
|
257
|
+
content: {},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const editor = await waitForTestEditor<MultiRootEditor>();
|
|
261
|
+
|
|
262
|
+
renderTestEditable({
|
|
263
|
+
content: '<p>Foo</p>',
|
|
264
|
+
rootName: 'foo',
|
|
265
|
+
rootModelElementName: '$inlineRoot',
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await vi.waitFor(() => {
|
|
269
|
+
expect(editor.model.document.getRoot('foo')?.name).to.be.equal('$inlineRoot');
|
|
270
|
+
});
|
|
271
|
+
});
|
|
236
272
|
});
|
|
237
273
|
|
|
238
274
|
describe('input value synchronization', () => {
|
|
@@ -252,7 +288,8 @@ describe('editable component', () => {
|
|
|
252
288
|
renderTestEditable({
|
|
253
289
|
rootName: 'foo',
|
|
254
290
|
content: '<p>Initial foo component</p>',
|
|
255
|
-
|
|
291
|
+
withInput: false,
|
|
292
|
+
});
|
|
256
293
|
|
|
257
294
|
await vi.waitFor(() => {
|
|
258
295
|
expect(editor.getData({ rootName: 'foo' })).toBe('<p>Initial foo component</p>');
|
|
@@ -263,7 +300,8 @@ describe('editable component', () => {
|
|
|
263
300
|
const element = renderTestEditable({
|
|
264
301
|
rootName: 'foo',
|
|
265
302
|
content: '<p>Initial foo component</p>',
|
|
266
|
-
|
|
303
|
+
withInput: true,
|
|
304
|
+
});
|
|
267
305
|
|
|
268
306
|
const input = element.querySelector('input')!;
|
|
269
307
|
|
|
@@ -277,7 +315,8 @@ describe('editable component', () => {
|
|
|
277
315
|
rootName: 'foo',
|
|
278
316
|
content: '<p>Initial foo component</p>',
|
|
279
317
|
saveDebounceMs: 500,
|
|
280
|
-
|
|
318
|
+
withInput: true,
|
|
319
|
+
});
|
|
281
320
|
|
|
282
321
|
const input = element.querySelector('input')!;
|
|
283
322
|
|
package/src/elements/editable.ts
CHANGED
|
@@ -42,6 +42,7 @@ export class EditableComponentElement extends HTMLElement {
|
|
|
42
42
|
const editorId = this.getAttribute('data-cke-editor-id');
|
|
43
43
|
const rootName = this.getAttribute('data-cke-root-name');
|
|
44
44
|
const rootAttributes = JSON.parse(this.getAttribute('data-cke-root-attributes') || '{}');
|
|
45
|
+
const rootModelElement = this.getAttribute('data-cke-root-model-element-name') || '$root';
|
|
45
46
|
const content = this.getAttribute('data-cke-content');
|
|
46
47
|
const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
|
|
47
48
|
|
|
@@ -89,6 +90,7 @@ export class EditableComponentElement extends HTMLElement {
|
|
|
89
90
|
editor.addRoot(rootName, {
|
|
90
91
|
isUndoable: false,
|
|
91
92
|
modelAttributes: { ...rootAttributes },
|
|
93
|
+
modelElement: rootModelElement,
|
|
92
94
|
...content !== null && {
|
|
93
95
|
initialData: content,
|
|
94
96
|
},
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
|
-
createEditableSnapshot,
|
|
16
15
|
createEditorPreset,
|
|
17
16
|
getTestEditorInput,
|
|
18
17
|
html,
|
|
@@ -118,6 +117,16 @@ describe('editor component', () => {
|
|
|
118
117
|
expect(root.getAttribute('data-test-attr')).toBe('123');
|
|
119
118
|
expect(root.getAttribute('data-another-attr')).toBe('abc');
|
|
120
119
|
});
|
|
120
|
+
|
|
121
|
+
it('should be possible to specify root element name', async () => {
|
|
122
|
+
renderTestEditor({
|
|
123
|
+
rootModelElementName: '$inlineRoot',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const editor = await waitForTestEditor();
|
|
127
|
+
|
|
128
|
+
expect(editor.model.document.getRoot()?.name).toBe('$inlineRoot');
|
|
129
|
+
});
|
|
121
130
|
});
|
|
122
131
|
|
|
123
132
|
describe('inline', () => {
|
|
@@ -134,7 +143,7 @@ describe('editor component', () => {
|
|
|
134
143
|
describe('decoupled', () => {
|
|
135
144
|
it('should create a decoupled editor with `main` editable and default preset', async () => {
|
|
136
145
|
renderTestEditor({ preset: createEditorPreset('decoupled') });
|
|
137
|
-
renderTestEditable(
|
|
146
|
+
renderTestEditable();
|
|
138
147
|
|
|
139
148
|
const editor = await waitForTestEditor();
|
|
140
149
|
|
|
@@ -146,7 +155,7 @@ describe('editor component', () => {
|
|
|
146
155
|
const initialEditableContent = '<p>Initial editable content</p>';
|
|
147
156
|
|
|
148
157
|
renderTestEditor({ preset: createEditorPreset('decoupled') });
|
|
149
|
-
renderTestEditable(
|
|
158
|
+
renderTestEditable({ content: initialEditableContent });
|
|
150
159
|
|
|
151
160
|
const editor = await waitForTestEditor();
|
|
152
161
|
|
|
@@ -159,13 +168,64 @@ describe('editor component', () => {
|
|
|
159
168
|
|
|
160
169
|
await timeout(200);
|
|
161
170
|
|
|
162
|
-
renderTestEditable(
|
|
171
|
+
renderTestEditable({});
|
|
163
172
|
|
|
164
173
|
const editor = await waitForTestEditor();
|
|
165
174
|
|
|
166
175
|
expect(editor).to.toBeInstanceOf(DecoupledEditor);
|
|
167
176
|
expect(isEditorShown()).toBe(true);
|
|
168
177
|
});
|
|
178
|
+
|
|
179
|
+
it('should be possible to specify root element name using editable config alone', async () => {
|
|
180
|
+
renderTestEditor({
|
|
181
|
+
preset: createEditorPreset('decoupled'),
|
|
182
|
+
content: {},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
renderTestEditable({
|
|
186
|
+
rootModelElementName: '$inlineRoot',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const editor = await waitForTestEditor<DecoupledEditor>();
|
|
190
|
+
|
|
191
|
+
await vi.waitFor(() => {
|
|
192
|
+
expect(editor.model.document.getRoot()?.name).toEqual('$inlineRoot');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should use editable root element name config if both specified', async () => {
|
|
197
|
+
renderTestEditor({
|
|
198
|
+
preset: createEditorPreset('decoupled'),
|
|
199
|
+
content: {},
|
|
200
|
+
rootModelElementName: '$miamia',
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
renderTestEditable({
|
|
204
|
+
rootModelElementName: '$inlineRoot',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const editor = await waitForTestEditor<DecoupledEditor>();
|
|
208
|
+
|
|
209
|
+
await vi.waitFor(() => {
|
|
210
|
+
expect(editor.model.document.getRoot()?.name).toEqual('$inlineRoot');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should use default `$root` if editable root name is not specified', async () => {
|
|
215
|
+
renderTestEditor({
|
|
216
|
+
preset: createEditorPreset('decoupled'),
|
|
217
|
+
content: {},
|
|
218
|
+
rootModelElementName: '$miamia',
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
renderTestEditable();
|
|
222
|
+
|
|
223
|
+
const editor = await waitForTestEditor<DecoupledEditor>();
|
|
224
|
+
|
|
225
|
+
await vi.waitFor(() => {
|
|
226
|
+
expect(editor.model.document.getRoot()?.name).toEqual('$root');
|
|
227
|
+
});
|
|
228
|
+
});
|
|
169
229
|
});
|
|
170
230
|
|
|
171
231
|
describe('balloon', () => {
|
|
@@ -201,7 +261,7 @@ describe('editor component', () => {
|
|
|
201
261
|
|
|
202
262
|
await timeout(500); // Simulate some delay before adding the root.
|
|
203
263
|
|
|
204
|
-
renderTestEditable(
|
|
264
|
+
renderTestEditable({ rootName: 'header' });
|
|
205
265
|
|
|
206
266
|
const editor = await waitForTestEditor();
|
|
207
267
|
|
|
@@ -219,7 +279,10 @@ describe('editor component', () => {
|
|
|
219
279
|
|
|
220
280
|
await timeout(500); // Simulate some delay before adding the root.
|
|
221
281
|
|
|
222
|
-
renderTestEditable(
|
|
282
|
+
renderTestEditable({
|
|
283
|
+
rootName: 'header',
|
|
284
|
+
content: '',
|
|
285
|
+
});
|
|
223
286
|
|
|
224
287
|
const editor = await waitForTestEditor();
|
|
225
288
|
|
|
@@ -235,9 +298,10 @@ describe('editor component', () => {
|
|
|
235
298
|
},
|
|
236
299
|
});
|
|
237
300
|
|
|
238
|
-
renderTestEditable(
|
|
239
|
-
|
|
240
|
-
|
|
301
|
+
renderTestEditable({
|
|
302
|
+
rootName: 'header',
|
|
303
|
+
content: '<p>Editable content overrides snapshot content</p>',
|
|
304
|
+
});
|
|
241
305
|
|
|
242
306
|
const editor = await waitForTestEditor();
|
|
243
307
|
|
|
@@ -270,14 +334,34 @@ describe('editor component', () => {
|
|
|
270
334
|
|
|
271
335
|
const editor = await waitForTestEditor<MultiRootEditor>();
|
|
272
336
|
|
|
273
|
-
editor.addRoot('existingRoot', {
|
|
337
|
+
editor.addRoot('existingRoot', { initialData: '<p>Old content</p>' });
|
|
274
338
|
|
|
275
|
-
renderTestEditable(
|
|
339
|
+
renderTestEditable({
|
|
340
|
+
rootName: 'existingRoot',
|
|
341
|
+
content: '<p>New content</p>',
|
|
342
|
+
});
|
|
276
343
|
|
|
277
344
|
await vi.waitFor(() => {
|
|
278
345
|
expect(editor.getData({ rootName: 'existingRoot' })).toBe('<p>New content</p>');
|
|
279
346
|
});
|
|
280
347
|
});
|
|
348
|
+
|
|
349
|
+
it('should create a multiroot editor with inline editables', async () => {
|
|
350
|
+
renderTestEditor({
|
|
351
|
+
preset: createEditorPreset('multiroot'),
|
|
352
|
+
content: {},
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
renderTestEditable({ rootName: 'second', rootModelElementName: '$inlineRoot' });
|
|
356
|
+
renderTestEditable({ rootName: 'third', rootModelElementName: '$inlineRoot' });
|
|
357
|
+
|
|
358
|
+
const editor = await waitForTestEditor();
|
|
359
|
+
|
|
360
|
+
await vi.waitFor(() => {
|
|
361
|
+
expect(editor.model.document.getRoot('second')?.name).toEqual('$inlineRoot');
|
|
362
|
+
expect(editor.model.document.getRoot('third')?.name).toEqual('$inlineRoot');
|
|
363
|
+
});
|
|
364
|
+
});
|
|
281
365
|
});
|
|
282
366
|
});
|
|
283
367
|
|
|
@@ -537,8 +621,8 @@ describe('editor component', () => {
|
|
|
537
621
|
|
|
538
622
|
component.addEventListener('ckeditor5:change:data', changeSpy);
|
|
539
623
|
|
|
540
|
-
renderTestEditable(
|
|
541
|
-
renderTestEditable(
|
|
624
|
+
renderTestEditable({ rootName: 'header' });
|
|
625
|
+
renderTestEditable({ rootName: 'footer' });
|
|
542
626
|
|
|
543
627
|
const editor = await waitForTestEditor<MultiRootEditor>();
|
|
544
628
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { WaitForInteractiveResult } from '../../shared';
|
|
2
2
|
import type { EditorId, EditorLanguage, EditorPreset } from './typings';
|
|
3
|
+
import type { EditableItem } from './utils';
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
isEmptyObject,
|
|
@@ -14,8 +15,7 @@ import {
|
|
|
14
15
|
createSyncEditorWithInputPlugin,
|
|
15
16
|
} from './plugins';
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
-
assignSourceElementsToEditorConfig,
|
|
18
|
+
assignEditorRootsToConfig,
|
|
19
19
|
cleanupOrphanEditorElements,
|
|
20
20
|
createEditorInContext,
|
|
21
21
|
isSingleRootEditor,
|
|
@@ -24,8 +24,7 @@ import {
|
|
|
24
24
|
loadEditorPlugins,
|
|
25
25
|
normalizeCustomTranslations,
|
|
26
26
|
normalizeEditorLanguage,
|
|
27
|
-
|
|
28
|
-
queryEditablesSnapshotContent,
|
|
27
|
+
queryAllEditorEditables,
|
|
29
28
|
resolveEditorConfigElementReferences,
|
|
30
29
|
resolveEditorConfigTranslations,
|
|
31
30
|
setEditorEditableHeight,
|
|
@@ -158,7 +157,6 @@ export class EditorComponentElement extends HTMLElement {
|
|
|
158
157
|
const editableHeight = this.getAttribute('data-cke-editable-height') ? Number.parseInt(this.getAttribute('data-cke-editable-height')!, 10) : null;
|
|
159
158
|
const saveDebounceMs = Number.parseInt(this.getAttribute('data-cke-save-debounce-ms')!, 10);
|
|
160
159
|
const useWatchdog = this.hasAttribute('data-cke-watchdog');
|
|
161
|
-
const content = JSON.parse(this.getAttribute('data-cke-content')!) as Record<string, string>;
|
|
162
160
|
const language = (
|
|
163
161
|
this.getAttribute('data-cke-language')
|
|
164
162
|
? JSON.parse(this.getAttribute('data-cke-language')!)
|
|
@@ -200,59 +198,36 @@ export class EditorComponentElement extends HTMLElement {
|
|
|
200
198
|
]
|
|
201
199
|
.filter(translations => !isEmptyObject(translations));
|
|
202
200
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
// Query all editable elements along with their content in one pass.
|
|
202
|
+
// Roots present in the editor container's data-cke-content but not yet in the DOM
|
|
203
|
+
// are included with element: null so we know which roots to wait for.
|
|
204
|
+
let editables = queryAllEditorEditables(editorId);
|
|
205
|
+
const requiredRoots = Object.keys(editables);
|
|
208
206
|
|
|
209
207
|
if (isSingleRootEditor(editorType)) {
|
|
210
|
-
|
|
208
|
+
requiredRoots.push('main');
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
// Handle special case when user specified `initialData` of several root elements, but editable components
|
|
218
|
-
// are not yet present in the DOM. In other words - editor is initialized before attaching root elements.
|
|
219
|
-
if (!sourceElements['main']) {
|
|
220
|
-
const requiredRoots = (
|
|
221
|
-
isSingleRootEditor(editorType)
|
|
222
|
-
? ['main']
|
|
223
|
-
: Object.keys(initialData as Record<string, string>)
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
if (!checkIfAllRootsArePresent(sourceElements, requiredRoots)) {
|
|
227
|
-
sourceElements = await waitForAllRootsToBePresent(editorId, requiredRoots);
|
|
228
|
-
initialData = {
|
|
229
|
-
...content,
|
|
230
|
-
...queryEditablesSnapshotContent(editorId),
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// If single root editor, unwrap the element from the object.
|
|
236
|
-
if (isSingleRootEditor(editorType) && 'main' in sourceElements) {
|
|
237
|
-
sourceElements = sourceElements['main'];
|
|
238
|
-
}
|
|
211
|
+
if (!checkIfAllRootsArePresent(editables, requiredRoots)) {
|
|
212
|
+
editables = await waitForAllRootsToBePresent(editorId, requiredRoots);
|
|
213
|
+
}
|
|
239
214
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
215
|
+
// Do some postprocessing on received configuration.
|
|
216
|
+
let resolvedConfig = {
|
|
217
|
+
...config,
|
|
218
|
+
licenseKey,
|
|
219
|
+
plugins: loadedPlugins,
|
|
220
|
+
language,
|
|
221
|
+
...mixedTranslations.length && {
|
|
222
|
+
translations: mixedTranslations,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
250
225
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
resolvedConfig = assignInitialDataToEditorConfig(initialData, resolvedConfig);
|
|
226
|
+
resolvedConfig = resolveEditorConfigElementReferences(resolvedConfig);
|
|
227
|
+
resolvedConfig = resolveEditorConfigTranslations([...mixedTranslations].reverse(), language.ui, resolvedConfig);
|
|
228
|
+
resolvedConfig = assignEditorRootsToConfig(Constructor, editables, resolvedConfig);
|
|
255
229
|
|
|
230
|
+
const editor = await (async () => {
|
|
256
231
|
if (!context) {
|
|
257
232
|
return Constructor.create(resolvedConfig);
|
|
258
233
|
}
|
|
@@ -313,42 +288,42 @@ export class EditorComponentElement extends HTMLElement {
|
|
|
313
288
|
}
|
|
314
289
|
|
|
315
290
|
/**
|
|
316
|
-
* Checks if all required root elements are present in the
|
|
291
|
+
* Checks if all required root elements are present (i.e. have a non-null element) in the editables map.
|
|
317
292
|
*
|
|
318
|
-
* @param
|
|
319
|
-
* @param requiredRoots The list of required root
|
|
320
|
-
* @returns True if all required roots
|
|
293
|
+
* @param editables The editables map keyed by root name.
|
|
294
|
+
* @param requiredRoots The list of required root names.
|
|
295
|
+
* @returns True if all required roots have a DOM element attached, false otherwise.
|
|
321
296
|
*/
|
|
322
|
-
function checkIfAllRootsArePresent(
|
|
323
|
-
return requiredRoots.every(rootId =>
|
|
297
|
+
function checkIfAllRootsArePresent(editables: Record<string, EditableItem>, requiredRoots: string[]): boolean {
|
|
298
|
+
return requiredRoots.every(rootId => editables[rootId]?.element);
|
|
324
299
|
}
|
|
325
300
|
|
|
326
301
|
/**
|
|
327
302
|
* Waits for all required root elements to be present in the DOM.
|
|
328
303
|
*
|
|
329
304
|
* @param editorId The editor's ID.
|
|
330
|
-
* @param requiredRoots The list of required root
|
|
331
|
-
* @returns A promise that resolves to the
|
|
305
|
+
* @param requiredRoots The list of required root names.
|
|
306
|
+
* @returns A promise that resolves to the updated editables map once all elements are attached.
|
|
332
307
|
*/
|
|
333
308
|
async function waitForAllRootsToBePresent(
|
|
334
309
|
editorId: EditorId,
|
|
335
310
|
requiredRoots: string[],
|
|
336
|
-
): Promise<Record<string,
|
|
311
|
+
): Promise<Record<string, EditableItem>> {
|
|
337
312
|
return waitFor(
|
|
338
313
|
() => {
|
|
339
|
-
const
|
|
314
|
+
const editables = queryAllEditorEditables(editorId);
|
|
340
315
|
|
|
341
|
-
if (!checkIfAllRootsArePresent(
|
|
316
|
+
if (!checkIfAllRootsArePresent(editables, requiredRoots)) {
|
|
342
317
|
throw new Error(
|
|
343
318
|
'It looks like not all required root elements are present yet.\n'
|
|
344
319
|
+ '* If you want to wait for them, ensure they are registered before editor initialization.\n'
|
|
345
320
|
+ '* If you want lazy initialize roots, consider removing root values from the `initialData` config '
|
|
346
321
|
+ 'and assign initial data in editable components.\n'
|
|
347
|
-
+ `Missing roots: ${requiredRoots.filter(rootId => !
|
|
322
|
+
+ `Missing roots: ${requiredRoots.filter(rootId => !editables[rootId]?.element).join(', ')}.`,
|
|
348
323
|
);
|
|
349
324
|
}
|
|
350
325
|
|
|
351
|
-
return
|
|
326
|
+
return editables;
|
|
352
327
|
},
|
|
353
328
|
{ timeOutAfter: 2000, retryAfter: 100 },
|
|
354
329
|
);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { EditorRelaxedConstructor } from '../types/editor-relaxed-constructor.type';
|
|
2
|
+
import type { EditableItem } from './query-all-editor-editables';
|
|
3
|
+
import type { EditorConfig } from 'ckeditor5';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Assigns DOM elements and initial data to the editor configuration in a way that is compatible
|
|
7
|
+
* with the specific editor type.
|
|
8
|
+
*
|
|
9
|
+
* Roots with `element: null` (pending roots not yet in the DOM) contribute only `initialData`
|
|
10
|
+
* and are skipped for element assignment.
|
|
11
|
+
*
|
|
12
|
+
* @param Editor Constructor of the editor used to determine the location of element config entry.
|
|
13
|
+
* @param editables Map of editable items (element + content) keyed by root name.
|
|
14
|
+
* @param config Config of the editor.
|
|
15
|
+
* @returns The updated configuration object.
|
|
16
|
+
*/
|
|
17
|
+
export function assignEditorRootsToConfig<C extends EditorConfig>(
|
|
18
|
+
Editor: EditorRelaxedConstructor,
|
|
19
|
+
editables: Record<string, EditableItem>,
|
|
20
|
+
config: C,
|
|
21
|
+
): C {
|
|
22
|
+
const isClassicEditor = !Editor.editorName || Editor.editorName === 'ClassicEditor';
|
|
23
|
+
const allRootsKeys = new Set([
|
|
24
|
+
...Object.keys(editables),
|
|
25
|
+
...Object.keys(config.roots ?? {}),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const rootsConfig = Array.from(allRootsKeys).reduce((acc, rootKey) => ({
|
|
29
|
+
...acc,
|
|
30
|
+
[rootKey]: {
|
|
31
|
+
/* v8 ignore next 1 */
|
|
32
|
+
...config.roots?.[rootKey],
|
|
33
|
+
...rootKey === 'main' ? config.root : {},
|
|
34
|
+
|
|
35
|
+
/* v8 ignore next 12 */
|
|
36
|
+
...rootKey in editables
|
|
37
|
+
? {
|
|
38
|
+
...editables[rootKey]!.content !== null && {
|
|
39
|
+
initialData: editables[rootKey]!.content,
|
|
40
|
+
},
|
|
41
|
+
modelElement: editables[rootKey]!.modelElement || '$root',
|
|
42
|
+
...!isClassicEditor && editables[rootKey]!.element !== null && {
|
|
43
|
+
element: editables[rootKey]!.element,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: {},
|
|
47
|
+
},
|
|
48
|
+
}), { ...config.roots || {} });
|
|
49
|
+
|
|
50
|
+
const mappedConfig: C = {
|
|
51
|
+
...config,
|
|
52
|
+
roots: rootsConfig,
|
|
53
|
+
...isClassicEditor && editables['main']?.element && {
|
|
54
|
+
attachTo: editables['main'].element,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
delete mappedConfig.root;
|
|
59
|
+
|
|
60
|
+
return mappedConfig;
|
|
61
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
export * from './assign-
|
|
2
|
-
export * from './assign-source-elements-to-editor-config';
|
|
1
|
+
export * from './assign-editor-roots-to-config';
|
|
3
2
|
export * from './cleanup-orphan-editor-elements';
|
|
4
3
|
export * from './create-editor-in-context';
|
|
5
4
|
export * from './get-editor-roots-values';
|
|
@@ -9,8 +8,8 @@ export * from './load-editor-plugins';
|
|
|
9
8
|
export * from './load-editor-translations';
|
|
10
9
|
export * from './normalize-custom-translations';
|
|
11
10
|
export * from './normalize-editor-language';
|
|
11
|
+
export * from './query-all-editor-editables';
|
|
12
12
|
export * from './query-all-editor-ids';
|
|
13
|
-
export * from './query-editor-editables';
|
|
14
13
|
export * from './resolve-editor-config-elements-references';
|
|
15
14
|
export * from './resolve-editor-config-translations';
|
|
16
15
|
export * from './set-editor-editable-height';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { EditorId } from '../typings';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Queries all editable elements within a specific editor instance. It picks
|
|
5
|
+
* initial values from actually rendered elements or from the editor container's
|
|
6
|
+
* `data-cke-content` attribute, whichever is available.
|
|
7
|
+
*
|
|
8
|
+
* Roots present in the editor container's content but without a matching
|
|
9
|
+
* `cke5-editable` element yet are included with `element: null` — these are
|
|
10
|
+
* "pending" roots that haven't been attached to the DOM yet.
|
|
11
|
+
*
|
|
12
|
+
* @param editorId The ID of the editor to query.
|
|
13
|
+
* @returns An object mapping root names to their corresponding elements and content.
|
|
14
|
+
*/
|
|
15
|
+
export function queryAllEditorEditables(editorId: EditorId): Record<string, EditableItem> {
|
|
16
|
+
const acc = Array
|
|
17
|
+
.from(document.querySelectorAll<HTMLElement>(`cke5-editable[data-cke-editor-id="${editorId}"]`))
|
|
18
|
+
.reduce<Record<string, EditableItem>>((acc, element) => {
|
|
19
|
+
const rootName = element.getAttribute('data-cke-root-name')!;
|
|
20
|
+
const modelElement = element.getAttribute('data-cke-root-model-element-name') || null;
|
|
21
|
+
|
|
22
|
+
acc[rootName] = {
|
|
23
|
+
element: element.querySelector<HTMLElement>('[data-cke-editable-content]'),
|
|
24
|
+
content: element.getAttribute('data-cke-content'),
|
|
25
|
+
modelElement,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return acc;
|
|
29
|
+
}, Object.create(null));
|
|
30
|
+
|
|
31
|
+
const rootEditorElement = document.querySelector<HTMLElement>(`cke5-editor[data-cke-editor-id="${editorId}"]`);
|
|
32
|
+
|
|
33
|
+
/* v8 ignore next -- @preserve */
|
|
34
|
+
if (!rootEditorElement) {
|
|
35
|
+
return acc;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// v8 ignore next -- @preserve
|
|
39
|
+
const editorContent: Record<string, string> = JSON.parse(rootEditorElement.getAttribute('data-cke-content')!) ?? {};
|
|
40
|
+
const classicMainElement = document.querySelector<HTMLElement>(`#${editorId}_editor`);
|
|
41
|
+
const rootEditorModelElement = rootEditorElement.getAttribute('data-cke-root-model-element-name');
|
|
42
|
+
|
|
43
|
+
if (classicMainElement && !acc['main']) {
|
|
44
|
+
acc['main'] = {
|
|
45
|
+
element: classicMainElement,
|
|
46
|
+
content: editorContent['main'] || '',
|
|
47
|
+
modelElement: rootEditorModelElement,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const [rootName, rootContent] of Object.entries(editorContent)) {
|
|
52
|
+
if (acc[rootName]) {
|
|
53
|
+
// Editable element is already present — fill content from editor level if the editable didn't provide its own.
|
|
54
|
+
acc[rootName] = {
|
|
55
|
+
...acc[rootName],
|
|
56
|
+
content: acc[rootName].content ?? rootContent,
|
|
57
|
+
modelElement: acc[rootName].modelElement ?? rootEditorModelElement,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Root has server-provided content but its editable element hasn't been attached to the DOM yet.
|
|
62
|
+
acc[rootName] = {
|
|
63
|
+
element: null,
|
|
64
|
+
content: rootContent,
|
|
65
|
+
modelElement: rootEditorModelElement,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return acc;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Type representing an editable item within an editor.
|
|
75
|
+
* `element` is null when the root's DOM element hasn't appeared yet (pending root).
|
|
76
|
+
*/
|
|
77
|
+
export type EditableItem = {
|
|
78
|
+
element: HTMLElement | null;
|
|
79
|
+
content: string | null;
|
|
80
|
+
modelElement: string | null;
|
|
81
|
+
};
|