Selaa lähdekoodia

Allowed leaving the code block by pressing enter in an empty line at the beginning of the block.

Aleksander Nowodzinski 6 vuotta sitten
vanhempi
commit
28c6b097c8

+ 61 - 9
packages/ckeditor5-code-block/src/codeblockediting.js

@@ -213,7 +213,9 @@ export default class CodeBlockEditing extends Plugin {
 				return;
 			}
 
-			leaveBlockOnEnter( editor, data.isSoft ) || breakLineOnEnter( editor );
+			leaveBlockStartOnEnter( editor, data.isSoft ) ||
+			leaveBlockEndOnEnter( editor, data.isSoft ) ||
+			breakLineOnEnter( editor );
 
 			data.preventDefault();
 			evt.stop();
@@ -261,6 +263,56 @@ function breakLineOnEnter( editor ) {
 	} );
 }
 
+// Leave the code block when Enter (but NOT Shift+Enter) has been pressed twice at the beginning
+// of the code block:
+//
+//		// Before:
+//		<codeBlock>[]<softBreak></softBreak>foo</codeBlock>
+//
+//		// After pressing:
+//		<paragraph>[]</paragraph><codeBlock>foo</codeBlock>
+//
+// @param {module:core/editor/editor~Editor} editor
+// @param {Boolean} isSoftEnter When `true`, enter was pressed along with <kbd>Shift</kbd>.
+// @returns {Boolean} `true` when selection left the block. `false` if stayed.
+function leaveBlockStartOnEnter( editor, isSoftEnter ) {
+	const model = editor.model;
+	const modelDoc = model.document;
+	const view = editor.editing.view;
+	const lastSelectionPosition = modelDoc.selection.getLastPosition();
+	const nodeAfter = lastSelectionPosition.nodeAfter;
+
+	if ( isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtStart ) {
+		return false;
+	}
+
+	if ( !nodeAfter || !nodeAfter.is( 'softBreak' ) ) {
+		return false;
+	}
+
+	// We're doing everything in a single change block to have a single undo step.
+	editor.model.change( writer => {
+		// "Clone" the <codeBlock> in the standard way.
+		editor.execute( 'enter' );
+
+		// The cloned block exists now before the original code block.
+		const newBlock = modelDoc.selection.anchor.parent.previousSibling;
+
+		// Make the cloned <codeBlock> a regular <paragraph> (with clean attributes, so no language).
+		writer.rename( newBlock, DEFAULT_ELEMENT );
+		writer.setSelection( newBlock, 'in' );
+		editor.model.schema.removeDisallowedAttributes( [ newBlock ], writer );
+
+		// Remove the <softBreak> that originally followed the selection position.
+		writer.remove( nodeAfter );
+	} );
+
+	// Eye candy.
+	view.scrollToTheSelection();
+
+	return true;
+}
+
 // Leave the code block when Enter (but NOT Shift+Enter) has been pressed twice at the end
 // of the code block:
 //
@@ -276,14 +328,14 @@ function breakLineOnEnter( editor ) {
 // @param {module:core/editor/editor~Editor} editor
 // @param {Boolean} isSoftEnter When `true`, enter was pressed along with <kbd>Shift</kbd>.
 // @returns {Boolean} `true` when selection left the block. `false` if stayed.
-function leaveBlockOnEnter( editor, isSoftEnter ) {
+function leaveBlockEndOnEnter( editor, isSoftEnter ) {
 	const model = editor.model;
 	const modelDoc = model.document;
 	const view = editor.editing.view;
 	const lastSelectionPosition = modelDoc.selection.getLastPosition();
 	const nodeBefore = lastSelectionPosition.nodeBefore;
 
-	let emptyLineRangeToRemoveOnDoubleEnter;
+	let emptyLineRangeToRemoveOnEnter;
 
 	if ( isSoftEnter || !modelDoc.selection.isCollapsed || !lastSelectionPosition.isAtEnd || !nodeBefore ) {
 		return false;
@@ -298,7 +350,7 @@ function leaveBlockOnEnter( editor, isSoftEnter ) {
 	//		<codeBlock>foo[<softBreak></softBreak>]</codeBlock>
 	//
 	if ( nodeBefore.is( 'softBreak' ) ) {
-		emptyLineRangeToRemoveOnDoubleEnter = model.createRangeOn( nodeBefore );
+		emptyLineRangeToRemoveOnEnter = model.createRangeOn( nodeBefore );
 	}
 
 	// When there's some text before the position made purely of white–space characters
@@ -319,7 +371,7 @@ function leaveBlockOnEnter( editor, isSoftEnter ) {
 		nodeBefore.previousSibling &&
 		nodeBefore.previousSibling.is( 'softBreak' )
 	) {
-		emptyLineRangeToRemoveOnDoubleEnter = model.createRange(
+		emptyLineRangeToRemoveOnEnter = model.createRange(
 			model.createPositionBefore( nodeBefore.previousSibling ), model.createPositionAfter( nodeBefore )
 		);
 	}
@@ -338,7 +390,7 @@ function leaveBlockOnEnter( editor, isSoftEnter ) {
 	// We're doing everything in a single change block to have a single undo step.
 	editor.model.change( writer => {
 		// Remove the last <softBreak> and all white space characters that followed it.
-		writer.remove( emptyLineRangeToRemoveOnDoubleEnter );
+		writer.remove( emptyLineRangeToRemoveOnEnter );
 
 		// "Clone" the <codeBlock> in the standard way.
 		editor.execute( 'enter' );
@@ -348,10 +400,10 @@ function leaveBlockOnEnter( editor, isSoftEnter ) {
 		// Make the cloned <codeBlock> a regular <paragraph> (with clean attributes, so no language).
 		writer.rename( newBlock, DEFAULT_ELEMENT );
 		editor.model.schema.removeDisallowedAttributes( [ newBlock ], writer );
-
-		// Eye candy.
-		view.scrollToTheSelection();
 	} );
 
+	// Eye candy.
+	view.scrollToTheSelection();
+
 	return true;
 }

+ 127 - 54
packages/ckeditor5-code-block/tests/codeblockediting.js

@@ -409,90 +409,163 @@ describe( 'CodeBlockEditing', () => {
 			} );
 		} );
 
-		describe( 'leaving block on double enter', () => {
-			it( 'should leave the block when pressed twice at the end', () => {
-				const spy = sinon.spy( editor.editing.view, 'scrollToTheSelection' );
+		describe( 'leaving block using the enter key', () => {
+			describe( 'leaving the block end', () => {
+				it( 'should leave the block when pressed twice at the end', () => {
+					const spy = sinon.spy( editor.editing.view, 'scrollToTheSelection' );
 
-				setModelData( model, '<codeBlock language="css">foo[]</codeBlock>' );
+					setModelData( model, '<codeBlock language="css">foo[]</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent() );
+					viewDoc.fire( 'enter', getEvent() );
 
-				expect( getModelData( model ) ).to.equal( '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
+					expect( getModelData( model ) ).to.equal( '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent() );
+					viewDoc.fire( 'enter', getEvent() );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css">foo</codeBlock>' +
-					'<paragraph>[]</paragraph>'
-				);
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css">foo</codeBlock>' +
+						'<paragraph>[]</paragraph>'
+					);
 
-				sinon.assert.calledOnce( spy );
+					sinon.assert.calledOnce( spy );
 
-				editor.execute( 'undo' );
-				expect( getModelData( model ) ).to.equal( '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
+					editor.execute( 'undo' );
+					expect( getModelData( model ) ).to.equal( '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
 
-				editor.execute( 'undo' );
-				expect( getModelData( model ) ).to.equal( '<codeBlock language="css">foo[]</codeBlock>' );
-			} );
+					editor.execute( 'undo' );
+					expect( getModelData( model ) ).to.equal( '<codeBlock language="css">foo[]</codeBlock>' );
+				} );
 
-			it( 'should not leave the block when the selection is not collapsed', () => {
-				setModelData( model, '<codeBlock language="css">f[oo<softBreak></softBreak>]</codeBlock>' );
+				it( 'should not leave the block when the selection is not collapsed', () => {
+					setModelData( model, '<codeBlock language="css">f[oo<softBreak></softBreak>]</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent() );
+					viewDoc.fire( 'enter', getEvent() );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css">f<softBreak></softBreak>[]</codeBlock>' );
-			} );
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css">f<softBreak></softBreak>[]</codeBlock>' );
+				} );
 
-			it( 'should not leave the block when pressed twice when in the middle of the code', () => {
-				setModelData( model, '<codeBlock language="css">fo[]o</codeBlock>' );
+				it( 'should not leave the block when pressed twice when in the middle of the code', () => {
+					setModelData( model, '<codeBlock language="css">fo[]o</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent() );
+					viewDoc.fire( 'enter', getEvent() );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css">fo<softBreak></softBreak>[]o</codeBlock>' );
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css">fo<softBreak></softBreak>[]o</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent() );
+					viewDoc.fire( 'enter', getEvent() );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css">fo<softBreak></softBreak><softBreak></softBreak>[]o</codeBlock>' );
-			} );
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css">fo<softBreak></softBreak><softBreak></softBreak>[]o</codeBlock>' );
+				} );
 
-			it( 'should not leave the block when pressed twice at the beginning of the code', () => {
-				setModelData( model, '<codeBlock language="css">[]foo</codeBlock>' );
+				it( 'should not leave the block when pressed twice at the beginning of the code', () => {
+					setModelData( model, '<codeBlock language="css">[]foo</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent() );
+					viewDoc.fire( 'enter', getEvent() );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css"><softBreak></softBreak>[]foo</codeBlock>' );
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css"><softBreak></softBreak>[]foo</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent() );
+					viewDoc.fire( 'enter', getEvent() );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css"><softBreak></softBreak><softBreak></softBreak>[]foo</codeBlock>' );
-			} );
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css"><softBreak></softBreak><softBreak></softBreak>[]foo</codeBlock>' );
+				} );
 
-			it( 'should not leave the block when pressed shift+enter twice at the end of the code', () => {
-				setModelData( model, '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
+				it( 'should not leave the block when pressed shift+enter twice at the end of the code', () => {
+					setModelData( model, '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
 
-				viewDoc.fire( 'enter', getEvent( { isSoft: true } ) );
+					viewDoc.fire( 'enter', getEvent( { isSoft: true } ) );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css">foo<softBreak></softBreak><softBreak></softBreak>[]</codeBlock>' );
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css">foo<softBreak></softBreak><softBreak></softBreak>[]</codeBlock>' );
+				} );
+
+				it( 'should clean up the last line if has white–space characters only', () => {
+					setModelData( model, '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
+
+					model.change( writer => {
+						// <codeBlock language="css">foo<softBreak></softBreak>  []</codeBlock>
+						writer.insertText( '  ', model.document.getRoot().getChild( 0 ), 4 );
+					} );
+
+					viewDoc.fire( 'enter', getEvent() );
+
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css">foo</codeBlock><paragraph>[]</paragraph>' );
+				} );
 			} );
 
-			it( 'should clean up the last line if has white–space characters only', () => {
-				setModelData( model, '<codeBlock language="css">foo<softBreak></softBreak>[]</codeBlock>' );
+			describe( 'leaving the block at the beginning', () => {
+				it( 'should leave the block when pressed at the beginning in a new line', () => {
+					const spy = sinon.spy( editor.editing.view, 'scrollToTheSelection' );
 
-				model.change( writer => {
-					// <codeBlock language="css">foo<softBreak></softBreak>  []</codeBlock>
-					writer.insertText( '  ', model.document.getRoot().getChild( 0 ), 4 );
+					setModelData( model, '<codeBlock language="css">[]<softBreak></softBreak>foo</codeBlock>' );
+
+					viewDoc.fire( 'enter', getEvent() );
+
+					expect( getModelData( model ) ).to.equal(
+						'<paragraph>[]</paragraph>' +
+						'<codeBlock language="css">foo</codeBlock>'
+					);
+
+					sinon.assert.calledOnce( spy );
+
+					editor.execute( 'undo' );
+					expect( getModelData( model ) ).to.equal( '<codeBlock language="css">[]<softBreak></softBreak>foo</codeBlock>' );
 				} );
 
-				viewDoc.fire( 'enter', getEvent() );
+				it( 'should not leave the block when the selection is not collapsed (#1)', () => {
+					setModelData( model, '<codeBlock language="css">[f]<softBreak></softBreak>oo</codeBlock>' );
 
-				expect( getModelData( model ) ).to.equal(
-					'<codeBlock language="css">foo</codeBlock><paragraph>[]</paragraph>' );
+					viewDoc.fire( 'enter', getEvent() );
+
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css"><softBreak></softBreak>[]<softBreak></softBreak>oo</codeBlock>' );
+				} );
+
+				it( 'should not leave the block when the selection is not collapsed (#2)', () => {
+					setModelData( model, '<codeBlock language="css">[<softBreak></softBreak>oo]</codeBlock>' );
+
+					viewDoc.fire( 'enter', getEvent() );
+
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css"><softBreak></softBreak>[]</codeBlock>' );
+				} );
+
+				it( 'should not leave the block when pressed shift+enter at the beginning of the code', () => {
+					setModelData( model, '<codeBlock language="css">[]<softBreak></softBreak>foo</codeBlock>' );
+
+					viewDoc.fire( 'enter', getEvent( { isSoft: true } ) );
+
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css"><softBreak></softBreak>[]<softBreak></softBreak>foo</codeBlock>' );
+				} );
+
+				it( 'should not leave the block when there is some text after the selection', () => {
+					setModelData( model, '<codeBlock language="css">[]foo<softBreak></softBreak>foo</codeBlock>' );
+
+					viewDoc.fire( 'enter', getEvent() );
+
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css"><softBreak></softBreak>[]foo<softBreak></softBreak>foo</codeBlock>' );
+				} );
+
+				it( 'should not leave the block when there is some text before the selection', () => {
+					setModelData( model, '<codeBlock language="css">[]<softBreak></softBreak>foo</codeBlock>' );
+
+					// <codeBlock language="css">    []<softBreak></softBreak>foo</codeBlock>
+					model.change( writer => {
+						writer.insertText( '    ', model.document.getRoot().getChild( 0 ), 0 );
+					} );
+
+					viewDoc.fire( 'enter', getEvent() );
+
+					// Extra spaces before "[]" come from the indentation retention mechanism.
+					expect( getModelData( model ) ).to.equal(
+						'<codeBlock language="css">    <softBreak></softBreak>    []<softBreak></softBreak>foo</codeBlock>' );
+				} );
 			} );
 		} );