diff --git a/packages/ckeditor5-code-block/docs/features/code-blocks.md b/packages/ckeditor5-code-block/docs/features/code-blocks.md
index 292384cb0ac..3c6226f7ab5 100644
--- a/packages/ckeditor5-code-block/docs/features/code-blocks.md
+++ b/packages/ckeditor5-code-block/docs/features/code-blocks.md
@@ -16,7 +16,7 @@ Code blocks is a perfect feature to present programming- or software-related iss
## Demo
-Use the code block toolbar button {@icon @ckeditor/ckeditor5-code-block/theme/icons/codeblock.svg Insert code block} and the type dropdown to insert a desired code block. Alternatively, start the line with `` ``` `` to format it as a code block thanks to the {@link features/autoformat autoformatting feature}. To add a paragraph underneath a code block, just use the double caret function (press Enter twice).
+Use the code block toolbar button {@icon @ckeditor/ckeditor5-code-block/theme/icons/codeblock.svg Insert code block} and the type dropdown to insert a desired code block. Alternatively, start the line with `` ``` `` to format it as a code block thanks to the {@link features/autoformat autoformatting feature}. To add a paragraph underneath a code block, just press Enter three times.
{@snippet features/code-block}
@@ -100,7 +100,7 @@ There could be situations when there is no obvious way to set the caret before o
{@img assets/img/code-blocks-typing-before.gif 770 The animation shows typing before the code blocks in CKEditor 5 rich text editor.}
-* To type **after the code block**: Put the selection at the end of the last line of the code block and press Enter twice. A new paragraph that you can type in will be created after the code block.
+* To type **after the code block**: Put the selection at the end of the last line of the code block and press Enter three times. A new paragraph that you can type in will be created after the code block.
{@img assets/img/code-blocks-typing-after.gif 770 The animation shows typing after the code blocks in CKEditor 5 rich text editor.}
diff --git a/packages/ckeditor5-code-block/src/codeblockediting.js b/packages/ckeditor5-code-block/src/codeblockediting.js
index 39e80f3edc0..a64cc28a4c1 100644
--- a/packages/ckeditor5-code-block/src/codeblockediting.js
+++ b/packages/ckeditor5-code-block/src/codeblockediting.js
@@ -219,7 +219,7 @@ export default class CodeBlockEditing extends Plugin {
// Customize the response to the Enter and Shift+Enter
// key press when the selection is in the code block. Upon enter key press we can either
- // leave the block if it's "two enters" in a row or create a new code block line, preserving
+ // leave the block if it's "two or three enters" in a row or create a new code block line, preserving
// previous line's indentation.
this.listenTo( editor.editing.view.document, 'enter', ( evt, data ) => {
const positionParent = editor.model.document.selection.getLastPosition().parent;
@@ -301,7 +301,7 @@ function leaveBlockStartOnEnter( editor, isSoftEnter ) {
return false;
}
- if ( !nodeAfter || !nodeAfter.is( 'element', 'softBreak' ) ) {
+ if ( !isSoftBreakNode( nodeAfter ) ) {
return false;
}
@@ -352,42 +352,61 @@ function leaveBlockEndOnEnter( editor, isSoftEnter ) {
let emptyLineRangeToRemoveOnEnter;
- if ( isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtEnd || !nodeBefore ) {
+ if ( isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtEnd || !nodeBefore || !nodeBefore.previousSibling ) {
return false;
}
- // When the position is directly preceded by a soft break
+ // When the position is directly preceded by two soft breaks
//
- // foo[]
+ // foo[]
//
// it creates the following range that will be cleaned up before leaving:
//
- // foo[]
+ // foo[]
//
- if ( nodeBefore.is( 'element', 'softBreak' ) ) {
- emptyLineRangeToRemoveOnEnter = model.createRangeOn( nodeBefore );
+ if ( isSoftBreakNode( nodeBefore ) && isSoftBreakNode( nodeBefore.previousSibling ) ) {
+ emptyLineRangeToRemoveOnEnter = model.createRange(
+ model.createPositionBefore( nodeBefore.previousSibling ), model.createPositionAfter( nodeBefore )
+ );
}
- // When there's some text before the position made purely of white–space characters
+ // When there's some text before the position that is
+ // preceded by two soft breaks and made purely of white–space characters
+ //
+ // foo []
//
- // foo []
+ // it creates the following range to clean up before leaving:
//
- // but NOT when it's the first one of the kind
+ // foo[ ]
//
- // []
+ else if (
+ isEmptyishTextNode( nodeBefore ) &&
+ isSoftBreakNode( nodeBefore.previousSibling ) &&
+ isSoftBreakNode( nodeBefore.previousSibling.previousSibling )
+ ) {
+ emptyLineRangeToRemoveOnEnter = model.createRange(
+ model.createPositionBefore( nodeBefore.previousSibling.previousSibling ), model.createPositionAfter( nodeBefore )
+ );
+ }
+
+ // When there's some text before the position that is made purely of white–space characters
+ // and is preceded by some other text made purely of white–space characters
+ //
+ // foo []
//
// it creates the following range to clean up before leaving:
//
- // foo[ ]
+ // foo[ ]
//
else if (
- nodeBefore.is( '$text' ) &&
- !nodeBefore.data.match( /\S/ ) &&
- nodeBefore.previousSibling &&
- nodeBefore.previousSibling.is( 'element', 'softBreak' )
+ isEmptyishTextNode( nodeBefore ) &&
+ isSoftBreakNode( nodeBefore.previousSibling ) &&
+ isEmptyishTextNode( nodeBefore.previousSibling.previousSibling ) &&
+ isSoftBreakNode( nodeBefore.previousSibling.previousSibling.previousSibling )
) {
emptyLineRangeToRemoveOnEnter = model.createRange(
- model.createPositionBefore( nodeBefore.previousSibling ), model.createPositionAfter( nodeBefore )
+ model.createPositionBefore( nodeBefore.previousSibling.previousSibling.previousSibling ),
+ model.createPositionAfter( nodeBefore )
);
}
@@ -395,8 +414,9 @@ function leaveBlockEndOnEnter( editor, isSoftEnter ) {
//
// []
// a []
- // foobar[]
- // foo a []
+ // foo[]
+ // foobar[]
+ // foo a []
//
else {
return false;
@@ -404,7 +424,7 @@ function leaveBlockEndOnEnter( editor, isSoftEnter ) {
// We're doing everything in a single change block to have a single undo step.
editor.model.change( writer => {
- // Remove the last and all white space characters that followed it.
+ // Remove the last s and all white space characters that followed them.
writer.remove( emptyLineRangeToRemoveOnEnter );
// "Clone" the in the standard way.
@@ -422,3 +442,11 @@ function leaveBlockEndOnEnter( editor, isSoftEnter ) {
return true;
}
+
+function isEmptyishTextNode( node ) {
+ return node && node.is( '$text' ) && !node.data.match( /\S/ );
+}
+
+function isSoftBreakNode( node ) {
+ return node && node.is( 'element', 'softBreak' );
+}
diff --git a/packages/ckeditor5-code-block/tests/codeblockediting.js b/packages/ckeditor5-code-block/tests/codeblockediting.js
index 0ecebfffa56..d87d45257bf 100644
--- a/packages/ckeditor5-code-block/tests/codeblockediting.js
+++ b/packages/ckeditor5-code-block/tests/codeblockediting.js
@@ -394,7 +394,7 @@ describe( 'CodeBlockEditing', () => {
describe( 'leaving block using the enter key', () => {
describe( 'leaving the block end', () => {
- it( 'should leave the block when pressed twice at the end', () => {
+ it( 'should leave the block when pressed three times at the end', () => {
const spy = sinon.spy( editor.editing.view, 'scrollToTheSelection' );
setModelData( model, 'foo[]' );
@@ -406,6 +406,11 @@ describe( 'CodeBlockEditing', () => {
viewDoc.fire( 'enter', getEvent() );
+ expect( getModelData( model ) ).to.equal(
+ 'foo[]' );
+
+ viewDoc.fire( 'enter', getEvent() );
+
expect( getModelData( model ) ).to.equal(
'foo' +
'[]'
@@ -415,14 +420,17 @@ describe( 'CodeBlockEditing', () => {
editor.execute( 'undo' );
expect( getModelData( model ) ).to.equal(
- 'foo[]' );
+ 'foo[]' );
+
+ editor.execute( 'undo' );
+ expect( getModelData( model ) ).to.equal( 'foo[]' );
editor.execute( 'undo' );
expect( getModelData( model ) ).to.equal( 'foo[]' );
} );
it( 'should not leave the block when the selection is not collapsed', () => {
- setModelData( model, 'f[oo]' );
+ setModelData( model, 'f[oo]' );
viewDoc.fire( 'enter', getEvent() );
@@ -430,7 +438,7 @@ describe( 'CodeBlockEditing', () => {
'f[]' );
} );
- it( 'should not leave the block when pressed twice when in the middle of the code', () => {
+ it( 'should not leave the block when pressed three times when in the middle of the code', () => {
setModelData( model, 'fo[]o' );
viewDoc.fire( 'enter', getEvent() );
@@ -442,9 +450,16 @@ describe( 'CodeBlockEditing', () => {
expect( getModelData( model ) ).to.equal(
'fo[]o' );
+
+ viewDoc.fire( 'enter', getEvent() );
+
+ expect( getModelData( model ) ).to.equal(
+ '' +
+ 'fo[]o' +
+ '' );
} );
- it( 'should not leave the block when pressed twice at the beginning of the code', () => {
+ it( 'should not leave the block when pressed three times at the beginning of the code', () => {
setModelData( model, '[]foo' );
viewDoc.fire( 'enter', getEvent() );
@@ -456,23 +471,49 @@ describe( 'CodeBlockEditing', () => {
expect( getModelData( model ) ).to.equal(
'[]foo' );
+
+ viewDoc.fire( 'enter', getEvent() );
+
+ expect( getModelData( model ) ).to.equal(
+ '' +
+ '[]foo' +
+ '' );
} );
- it( 'should not leave the block when pressed shift+enter twice at the end of the code', () => {
- setModelData( model, 'foo[]' );
+ it( 'should not leave the block when pressed shift+enter three times at the end of the code', () => {
+ setModelData( model, 'foo[]' );
viewDoc.fire( 'enter', getEvent( { isSoft: true } ) );
expect( getModelData( model ) ).to.equal(
- 'foo[]' );
+ '' +
+ 'foo[]' +
+ '' );
+ } );
+
+ it( 'should clean up the last two lines if the last one has white-space characters only', () => {
+ setModelData( model, 'foo[]' );
+
+ model.change( writer => {
+ // foo []
+ writer.insertText( ' ', model.document.getRoot().getChild( 0 ), 5 );
+ } );
+
+ viewDoc.fire( 'enter', getEvent() );
+
+ expect( getModelData( model ) ).to.equal(
+ 'foo[]' );
} );
- it( 'should clean up the last line if has white–space characters only', () => {
- setModelData( model, 'foo[]' );
+ it( 'should clean up the last two lines if both have white-space characters only', () => {
+ setModelData( model, 'foo[]' );
model.change( writer => {
- // foo []
+ // foo []
writer.insertText( ' ', model.document.getRoot().getChild( 0 ), 4 );
+
+ // foo []
+ writer.insertText( ' ', model.document.getRoot().getChild( 0 ), 7 );
} );
viewDoc.fire( 'enter', getEvent() );
diff --git a/packages/ckeditor5-code-block/tests/manual/codeblock.md b/packages/ckeditor5-code-block/tests/manual/codeblock.md
index 3b781ac34f7..a9459815a5a 100644
--- a/packages/ckeditor5-code-block/tests/manual/codeblock.md
+++ b/packages/ckeditor5-code-block/tests/manual/codeblock.md
@@ -14,9 +14,9 @@
### Block end
-- Create an empty line at the end of the block and put the selection there.
+- Create two empty lines at the end of the block and put the selection there.
- Press Enter again.
-- The new line created in the code block should no longer be there.
+- The new lines created in the code block should no longer be there.
- A new empty paragraph should be created after the code block.
- The selection should be in that paragraph.