ckeditor5-livewire 1.12.2 → 1.13.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ckeditor5-livewire",
3
- "version": "1.12.2",
3
+ "version": "1.13.0",
4
4
  "description": "CKEditor 5 integration for Laravel Livewire",
5
5
  "author": "Mateusz Bagiński <cziken58@gmail.com>",
6
6
  "license": "MIT",
@@ -71,6 +71,40 @@ describe('editable component', () => {
71
71
  expect(root.getAttribute('data-test')).toBe('initial');
72
72
  });
73
73
 
74
+ it('should set proper root element name on initial added root', async () => {
75
+ livewireStub.$internal.appendComponentToDOM({
76
+ name: 'ckeditor5-editable',
77
+ el: createEditableHtmlElement(),
78
+ canonical: {
79
+ ...createEditableSnapshot('foo', '<p>Initial foo component</p>'),
80
+ modelElement: '$inlineRoot',
81
+ },
82
+ });
83
+
84
+ appendMultirootEditor();
85
+
86
+ const editor = await waitForTestEditor();
87
+
88
+ expect(editor.model.document.getRoot('foo')?.name).to.be.equal('$inlineRoot');
89
+ });
90
+
91
+ it('should set proper root element name on lazy added root', async () => {
92
+ appendMultirootEditor();
93
+
94
+ const editor = await waitForTestEditor();
95
+
96
+ livewireStub.$internal.appendComponentToDOM({
97
+ name: 'ckeditor5-editable',
98
+ el: createEditableHtmlElement(),
99
+ canonical: {
100
+ ...createEditableSnapshot('foo', '<p>Initial foo component</p>'),
101
+ modelElement: '$inlineRoot',
102
+ },
103
+ });
104
+
105
+ expect(editor.model.document.getRoot('foo')?.name).to.be.equal('$inlineRoot');
106
+ });
107
+
74
108
  it('should update root attributes after commit', async () => {
75
109
  appendMultirootEditor();
76
110
 
@@ -26,7 +26,7 @@ export class EditableComponentHook extends ClassHook<Snapshot> {
26
26
  * Mounts the editable component.
27
27
  */
28
28
  override mounted() {
29
- const { editorId, rootName, content } = this.canonical;
29
+ const { editorId, rootName, content, modelElement } = this.canonical;
30
30
 
31
31
  const unmountEffect = EditorsRegistry.the.mountEffect(editorId, (editor: MultiRootEditor | DecoupledEditor) => {
32
32
  /* v8 ignore next if -- @preserve */
@@ -56,6 +56,7 @@ export class EditableComponentHook extends ClassHook<Snapshot> {
56
56
 
57
57
  editor.addRoot(rootName, {
58
58
  isUndoable: false,
59
+ modelElement: modelElement ?? '$root',
59
60
  ...content !== null && {
60
61
  initialData: content,
61
62
  },
@@ -267,6 +268,11 @@ export type Snapshot = {
267
268
  */
268
269
  rootName: string;
269
270
 
271
+ /**
272
+ * The name of the model element.
273
+ */
274
+ modelElement: string | null;
275
+
270
276
  /**
271
277
  * The initial content value for the editable.
272
278
  */
@@ -28,6 +28,7 @@ import {
28
28
  import type { Snapshot as EditorSnapshot } from './editor';
29
29
 
30
30
  import { timeout } from '../../shared/timeout';
31
+ import { EditableComponentHook } from '../editable';
31
32
  import { registerLivewireComponentHook } from '../hook';
32
33
  import { CustomEditorPluginsRegistry } from './custom-editor-plugins';
33
34
  import { EditorComponentHook } from './editor';
@@ -42,6 +43,7 @@ describe('editor component', () => {
42
43
  livewireStub = window.Livewire = new LivewireStub();
43
44
 
44
45
  registerLivewireComponentHook('ckeditor5', EditorComponentHook);
46
+ registerLivewireComponentHook('ckeditor5-editable', EditableComponentHook);
45
47
  });
46
48
 
47
49
  afterEach(async () => {
@@ -135,6 +137,20 @@ describe('editor component', () => {
135
137
 
136
138
  expect(editor.getData()).toBe('');
137
139
  });
140
+
141
+ it('should be possible to specify root element name', async () => {
142
+ livewireStub.$internal.appendComponentToDOM<EditorSnapshot>({
143
+ name: 'ckeditor5',
144
+ el: createEditorHtmlElement(),
145
+ canonical: createEditorSnapshot({
146
+ modelElement: '$inlineRoot',
147
+ }),
148
+ });
149
+
150
+ const editor = await waitForTestEditor();
151
+
152
+ expect(editor.model.document.getRoot()?.name).toBe('$inlineRoot');
153
+ });
138
154
  });
139
155
 
140
156
  describe('inline', () => {
@@ -234,10 +250,86 @@ describe('editor component', () => {
234
250
  },
235
251
  });
236
252
 
237
- await expect(waitForTestEditor()).rejects.toThrowError(
253
+ await expect(waitForTestEditor()).rejects.toThrow(
238
254
  /It looks like not all required root elements are present yet/,
239
255
  );
240
256
  });
257
+
258
+ it('should be possible to specify root element name using editable config alone', async () => {
259
+ livewireStub.$internal.appendComponentToDOM<EditorSnapshot>({
260
+ name: 'ckeditor5',
261
+ el: createEditorHtmlElement({ editorType: 'decoupled' }),
262
+ canonical: {
263
+ ...createEditorSnapshot(),
264
+ preset: createEditorPreset('decoupled'),
265
+ },
266
+ });
267
+
268
+ livewireStub.$internal.appendComponentToDOM({
269
+ name: 'ckeditor5-editable',
270
+ el: createEditableHtmlElement(),
271
+ canonical: {
272
+ ...createEditableSnapshot('main'),
273
+ modelElement: '$inlineRoot',
274
+ },
275
+ });
276
+
277
+ const editor = await waitForTestEditor<DecoupledEditor>();
278
+
279
+ await vi.waitFor(() => {
280
+ expect(editor.model.document.getRoot()?.name).toEqual('$inlineRoot');
281
+ });
282
+ });
283
+
284
+ it('should use editable root element name config if both specified', async () => {
285
+ livewireStub.$internal.appendComponentToDOM<EditorSnapshot>({
286
+ name: 'ckeditor5',
287
+ el: createEditorHtmlElement({ editorType: 'decoupled' }),
288
+ canonical: createEditorSnapshot({
289
+ preset: createEditorPreset('decoupled'),
290
+ modelElement: '$miaMia',
291
+ }),
292
+ });
293
+
294
+ livewireStub.$internal.appendComponentToDOM({
295
+ name: 'ckeditor5-editable',
296
+ el: createEditableHtmlElement(),
297
+ canonical: {
298
+ ...createEditableSnapshot('main'),
299
+ modelElement: '$inlineRoot',
300
+ },
301
+ });
302
+
303
+ const editor = await waitForTestEditor<DecoupledEditor>();
304
+
305
+ await vi.waitFor(() => {
306
+ expect(editor.model.document.getRoot()?.name).toEqual('$inlineRoot');
307
+ });
308
+ });
309
+
310
+ it('should use root editor element if editable root name is not specified', async () => {
311
+ livewireStub.$internal.appendComponentToDOM<EditorSnapshot>({
312
+ name: 'ckeditor5',
313
+ el: createEditorHtmlElement({ editorType: 'decoupled' }),
314
+ canonical: createEditorSnapshot({
315
+ preset: createEditorPreset('decoupled'),
316
+ modelElement: '$inlineRoot',
317
+ content: {},
318
+ }),
319
+ });
320
+
321
+ livewireStub.$internal.appendComponentToDOM({
322
+ name: 'ckeditor5-editable',
323
+ el: createEditableHtmlElement(),
324
+ canonical: createEditableSnapshot('main'),
325
+ });
326
+
327
+ const editor = await waitForTestEditor<DecoupledEditor>();
328
+
329
+ await vi.waitFor(() => {
330
+ expect(editor.model.document.getRoot()?.name).toEqual('$inlineRoot');
331
+ });
332
+ });
241
333
  });
242
334
 
243
335
  describe('balloon', () => {
@@ -275,6 +367,78 @@ describe('editor component', () => {
275
367
  expect(editor).toBeInstanceOf(MultiRootEditor);
276
368
  });
277
369
 
370
+ it('should create a multiroot editor with lazy added inline editables', async () => {
371
+ livewireStub.$internal.appendComponentToDOM<EditorSnapshot>({
372
+ name: 'ckeditor5',
373
+ el: createEditorHtmlElement({ editorType: 'multiroot' }),
374
+ canonical: {
375
+ ...createEditorSnapshot(),
376
+ preset: createEditorPreset('multiroot'),
377
+ content: {},
378
+ },
379
+ });
380
+
381
+ const editor = await waitForTestEditor();
382
+
383
+ await timeout(500); // Simulate some delay before adding the root.
384
+
385
+ livewireStub.$internal.appendComponentToDOM({
386
+ name: 'ckeditor5-editable',
387
+ el: createEditableHtmlElement(),
388
+ canonical: {
389
+ ...createEditableSnapshot('header', 'Head'),
390
+ modelElement: '$inlineRoot',
391
+ },
392
+ });
393
+
394
+ livewireStub.$internal.appendComponentToDOM({
395
+ name: 'ckeditor5-editable',
396
+ el: createEditableHtmlElement(),
397
+ canonical: {
398
+ ...createEditableSnapshot('footer', 'Footer'),
399
+ modelElement: '$inlineRoot',
400
+ },
401
+ });
402
+
403
+ expect(editor.model.document.getRoot('header')?.name).toEqual('$inlineRoot');
404
+ expect(editor.model.document.getRoot('footer')?.name).toEqual('$inlineRoot');
405
+ });
406
+
407
+ it('should create a multiroot editor with initially added inline editables', async () => {
408
+ livewireStub.$internal.appendComponentToDOM({
409
+ name: 'ckeditor5-editable',
410
+ el: createEditableHtmlElement(),
411
+ canonical: {
412
+ ...createEditableSnapshot('header', 'Head'),
413
+ modelElement: '$inlineRoot',
414
+ },
415
+ });
416
+
417
+ livewireStub.$internal.appendComponentToDOM({
418
+ name: 'ckeditor5-editable',
419
+ el: createEditableHtmlElement(),
420
+ canonical: {
421
+ ...createEditableSnapshot('footer', 'Footer'),
422
+ modelElement: '$inlineRoot',
423
+ },
424
+ });
425
+
426
+ livewireStub.$internal.appendComponentToDOM<EditorSnapshot>({
427
+ name: 'ckeditor5',
428
+ el: createEditorHtmlElement({ editorType: 'multiroot' }),
429
+ canonical: {
430
+ ...createEditorSnapshot(),
431
+ preset: createEditorPreset('multiroot'),
432
+ content: {},
433
+ },
434
+ });
435
+
436
+ const editor = await waitForTestEditor();
437
+
438
+ expect(editor.model.document.getRoot('header')?.name).toEqual('$inlineRoot');
439
+ expect(editor.model.document.getRoot('footer')?.name).toEqual('$inlineRoot');
440
+ });
441
+
278
442
  it('should wait and for root elements to be present in DOM if they are not (with content=null value)', async () => {
279
443
  livewireStub.$internal.appendComponentToDOM<EditorSnapshot>({
280
444
  name: 'ckeditor5',
@@ -356,6 +356,11 @@ export type Snapshot = {
356
356
  */
357
357
  editableHeight: number | null;
358
358
 
359
+ /**
360
+ * Root element name.
361
+ */
362
+ modelElement: string | null;
363
+
359
364
  /**
360
365
  * The language of the editor UI and content.
361
366
  */
@@ -34,6 +34,7 @@ export function assignEditorRootsToConfig<C extends EditorConfig>(
34
34
  ...rootKey === 'main' ? config.root : {},
35
35
  ...rootKey in editables
36
36
  ? {
37
+ modelElement: editables[rootKey]!.modelElement || '$root',
37
38
  ...editables[rootKey]!.content !== null && {
38
39
  initialData: editables[rootKey]!.content,
39
40
  },
@@ -12,5 +12,5 @@ export function getEditorRootsValues(editor: Editor) {
12
12
  return roots.reduce<Record<string, string>>((acc, rootName) => {
13
13
  acc[rootName] = editor.getData({ rootName });
14
14
  return acc;
15
- }, Object.create({}));
15
+ }, Object.create(null));
16
16
  }
@@ -18,14 +18,16 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
18
18
  .filter(({ name, canonical }) => name === 'ckeditor5-editable' && canonical['editorId'] === editorId)
19
19
  .reduce<Record<string, EditableItem>>((acc, { canonical, el }) => {
20
20
  const rootName = canonical['rootName'] as string;
21
+ const modelElement = (canonical['modelElement'] || null) as string | null;
21
22
 
22
23
  acc[rootName] = {
23
24
  element: el.querySelector<HTMLElement>('[data-cke-editable-content]'),
24
25
  content: canonical['content'],
26
+ modelElement,
25
27
  };
26
28
 
27
29
  return acc;
28
- }, Object.create({}));
30
+ }, Object.create(null));
29
31
 
30
32
  const rootHook = window.Livewire
31
33
  .all()
@@ -38,12 +40,17 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
38
40
 
39
41
  // v8 ignore next -- @preserve
40
42
  const editorContent: Record<string, string> = (rootHook.canonical.content as Record<string, string>) ?? {};
43
+ const rootEditorModelElement = (rootHook.canonical.modelElement || null) as string || null;
41
44
  const classicMainElement = document.querySelector<HTMLElement>(`#${editorId}_editor`);
42
45
 
43
- if (classicMainElement && !acc['main']) {
46
+ if ('main' in acc) {
47
+ acc['main'].modelElement ??= rootEditorModelElement;
48
+ }
49
+ else if (classicMainElement) {
44
50
  acc['main'] = {
45
51
  element: classicMainElement,
46
52
  content: editorContent['main'] ?? '',
53
+ modelElement: rootEditorModelElement,
47
54
  };
48
55
  }
49
56
 
@@ -53,6 +60,7 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
53
60
  acc[rootName] = {
54
61
  ...acc[rootName],
55
62
  content: acc[rootName].content ?? rootContent,
63
+ modelElement: acc[rootName].modelElement ?? rootEditorModelElement,
56
64
  };
57
65
  }
58
66
  else {
@@ -60,6 +68,7 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
60
68
  acc[rootName] = {
61
69
  element: null,
62
70
  content: rootContent,
71
+ modelElement: rootEditorModelElement,
63
72
  };
64
73
  }
65
74
  }
@@ -74,4 +83,5 @@ export function queryAllEditorEditables(editorId: EditorId): Record<string, Edit
74
83
  export type EditableItem = {
75
84
  element: HTMLElement | null;
76
85
  content: string | null;
86
+ modelElement: string | null;
77
87
  };