Skip to content

Commit d8cafb7

Browse files
committed
Add configuration for preserving block filler in editing view.
1 parent af673d0 commit d8cafb7

File tree

5 files changed

+142
-19
lines changed

5 files changed

+142
-19
lines changed

packages/ckeditor5-html-support/src/emptyblock.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ export default class EmptyBlock extends Plugin {
7272
* @inheritDoc
7373
*/
7474
public afterInit(): void {
75-
const { model, conversion, plugins } = this.editor;
75+
const { model, conversion, plugins, config } = this.editor;
7676
const schema = model.schema;
77+
const preserveInEditingView = config.get( 'emptyBlock.preserveInEditingView' );
7778

7879
schema.extend( '$block', { allowAttributes: [ EMPTY_BLOCK_MODEL_ATTRIBUTE ] } );
7980
schema.extend( '$container', { allowAttributes: [ EMPTY_BLOCK_MODEL_ATTRIBUTE ] } );
@@ -82,7 +83,12 @@ export default class EmptyBlock extends Plugin {
8283
schema.extend( 'tableCell', { allowAttributes: [ EMPTY_BLOCK_MODEL_ATTRIBUTE ] } );
8384
}
8485

85-
conversion.for( 'downcast' ).add( createEmptyBlockDowncastConverter() );
86+
if ( preserveInEditingView ) {
87+
conversion.for( 'downcast' ).add( createEmptyBlockDowncastConverter() );
88+
} else {
89+
conversion.for( 'dataDowncast' ).add( createEmptyBlockDowncastConverter() );
90+
}
91+
8692
conversion.for( 'upcast' ).add( createEmptyBlockUpcastConverter( schema ) );
8793

8894
if ( plugins.has( 'ClipboardPipeline' ) ) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3+
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4+
*/
5+
6+
/**
7+
* @module html-support/emptyblockconfig
8+
*/
9+
10+
/**
11+
* The configuration of the Empty Block feature.
12+
* The option is used by the {@link module:html-support/emptyblock~EmptyBlock} feature.
13+
*
14+
* ```ts
15+
* ClassicEditor
16+
* .create( {
17+
* emptyBlock: ... // Empty block feature config.
18+
* } )
19+
* .then( ... )
20+
* .catch( ... );
21+
* ```
22+
*
23+
* See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
24+
*/
25+
export interface EmptyBlockConfig {
26+
27+
/**
28+
* When set to `true`, empty blocks will be preserved in the editing view.
29+
* When `false` (default), empty blocks are only preserved in the data output.
30+
*
31+
* @default false
32+
*/
33+
preserveInEditingView?: boolean;
34+
}

packages/ckeditor5-html-support/tests/emptyblock.js

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote.js';
1414
import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard.js';
1515
import GeneralHtmlSupport from '../src/generalhtmlsupport.js';
1616
import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js';
17-
import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view.js';
1817
import { INLINE_FILLER } from '@ckeditor/ckeditor5-engine/src/view/filler.js';
1918

2019
import EmptyBlock from '../src/emptyblock.js';
@@ -327,23 +326,61 @@ describe( 'EmptyBlock', () => {
327326
} );
328327
} );
329328

330-
describe( 'editing pipeline', () => {
331-
it( 'should preserve empty paragraph in editing view', () => {
332-
setModelData( model,
333-
'<paragraph htmlEmptyBlock="true"></paragraph>' +
334-
'<paragraph></paragraph>'
329+
describe( 'config.emptyBlock.preserveInEditingView', () => {
330+
it( 'should preserve empty blocks in editing view when enabled', async () => {
331+
await editor.destroy();
332+
333+
editor = await ClassicTestEditor.create( element, {
334+
plugins: [ Paragraph, TableEditing, EmptyBlock, Heading, ListEditing, BlockQuote, Clipboard ],
335+
emptyBlock: {
336+
preserveInEditingView: true
337+
}
338+
} );
339+
340+
editor.setData( '<p></p><p>foo</p>' );
341+
342+
const domRoot = editor.editing.view.getDomRoot();
343+
344+
expect( domRoot.innerHTML ).to.equal(
345+
'<p>' + INLINE_FILLER + '</p>' +
346+
'<p>foo</p>'
335347
);
348+
} );
336349

337-
expect( getViewData( view ) ).to.equal(
338-
'<p>[]</p>' +
339-
'<p></p>'
350+
it( 'should not preserve empty blocks in editing view when disabled', async () => {
351+
await editor.destroy();
352+
353+
editor = await ClassicTestEditor.create( element, {
354+
plugins: [ Paragraph, TableEditing, EmptyBlock, Heading, ListEditing, BlockQuote, Clipboard ],
355+
emptyBlock: {
356+
preserveInEditingView: false
357+
}
358+
} );
359+
360+
editor.setData( '<p></p><p>foo</p>' );
361+
362+
const domRoot = editor.editing.view.getDomRoot();
363+
364+
expect( domRoot.innerHTML ).to.equal(
365+
'<p><br data-cke-filler="true"></p>' +
366+
'<p>foo</p>'
340367
);
368+
} );
369+
370+
it( 'should not preserve empty blocks in editing view by default', async () => {
371+
await editor.destroy();
372+
373+
editor = await ClassicTestEditor.create( element, {
374+
plugins: [ Paragraph, TableEditing, EmptyBlock, Heading, ListEditing, BlockQuote, Clipboard ]
375+
} );
376+
377+
editor.setData( '<p></p><p>foo</p>' );
341378

342379
const domRoot = editor.editing.view.getDomRoot();
343380

344381
expect( domRoot.innerHTML ).to.equal(
345-
'<p>' + INLINE_FILLER + '</p>' +
346-
'<p><br data-cke-filler="true"></p>'
382+
'<p><br data-cke-filler="true"></p>' +
383+
'<p>foo</p>'
347384
);
348385
} );
349386
} );

packages/ckeditor5-html-support/tests/manual/emptyblock.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,20 @@
2323
background: #f0f0f0;
2424
white-space: pre-wrap;
2525
}
26+
27+
.controls {
28+
margin-bottom: 20px;
29+
}
2630
</style>
2731
</head>
2832

33+
<div class="controls">
34+
<label>
35+
<input type="checkbox" id="preserve-empty-blocks" checked>
36+
Preserve empty blocks in editing view
37+
</label>
38+
</div>
39+
2940
<div class="editors-container">
3041
<div class="editor-wrapper">
3142
<h2>Editor 1 (with EmptyBlocks)</h2>
@@ -34,6 +45,11 @@ <h2>Editor 1 (with EmptyBlocks)</h2>
3445
<p></p>
3546
<p>&nbsp;</p>
3647
<p>Some text</p>
48+
<ul>
49+
<li></li>
50+
<li></li>
51+
<li></li>
52+
</ul>
3753
<table>
3854
<tr>
3955
<td>Table cell 1</td>
@@ -59,6 +75,11 @@ <h2>Editor 2 (without EmptyBlocks)</h2>
5975
<p></p>
6076
<p>&nbsp;</p>
6177
<p>Another text</p>
78+
<ul>
79+
<li></li>
80+
<li></li>
81+
<li></li>
82+
</ul>
6283
<table>
6384
<tr>
6485
<td>Table cell 1</td>

packages/ckeditor5-html-support/tests/manual/emptyblock.js

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,24 @@ const config = {
2020
toolbar: [ 'sourceEditing', '|', 'insertTable', 'bulletedList', 'numberedList', 'bold', 'italic', 'heading' ]
2121
};
2222

23-
ClassicEditor
24-
.create( document.getElementById( 'editor1' ), config )
25-
.then( instance => {
26-
window.editor1 = instance;
27-
CKEditorInspector.attach( { 'With EmptyBlock plugin': instance } );
28-
} );
23+
const preserveEmptyBlocksCheckbox = document.getElementById( 'preserve-empty-blocks' );
24+
25+
function createEditor1( preserveInEditingView ) {
26+
return ClassicEditor
27+
.create( document.getElementById( 'editor1' ), {
28+
...config,
29+
emptyBlock: {
30+
preserveInEditingView
31+
}
32+
} )
33+
.then( instance => {
34+
window.editor1 = instance;
35+
CKEditorInspector.attach( { 'With EmptyBlock plugin': instance } );
36+
} );
37+
}
38+
39+
// Initial editor creation
40+
createEditor1( preserveEmptyBlocksCheckbox.checked );
2941

3042
ClassicEditor
3143
.create( document.getElementById( 'editor2' ), {
@@ -45,3 +57,16 @@ function handleClipboardEvent( evt ) {
4557

4658
document.addEventListener( 'copy', handleClipboardEvent );
4759
document.addEventListener( 'cut', handleClipboardEvent );
60+
61+
preserveEmptyBlocksCheckbox.addEventListener( 'change', async () => {
62+
const editorElement = document.getElementById( 'editor1' );
63+
const editorData = window.editor1.getData();
64+
65+
await window.editor1.destroy();
66+
67+
// Restore any content that was in the editor
68+
editorElement.innerHTML = editorData;
69+
70+
// Create new editor instance with updated configuration
71+
await createEditor1( preserveEmptyBlocksCheckbox.checked );
72+
} );

0 commit comments

Comments
 (0)