Преглед изворни кода

Changed code block view structure from pre to pre > code.

Oskar Wróbel пре 6 година
родитељ
комит
4e7368309a

+ 47 - 13
packages/ckeditor5-code-block/src/codeblockediting.js

@@ -61,11 +61,13 @@ export default class CodeBlockEditing extends Plugin {
 		} );
 		} );
 
 
 		// Conversion.
 		// Conversion.
-		editor.conversion.for( 'editingDowncast' ).elementToElement( { model: 'codeBlock', view: 'pre' } );
+		editor.editing.downcastDispatcher.on( 'insert:codeBlock', modelViewCodeBlockInsertion( editor.model ), { priority: 'high' } );
+		editor.editing.downcastDispatcher.on( 'insert:softBreak', modelViewSoftBreakInsertion( editor.model ), { priority: 'high' } );
 
 
-		editor.conversion.for( 'dataDowncast' ).elementToElement( { model: 'codeBlock', view: 'pre' } );
-		editor.data.downcastDispatcher.on( 'insert:softBreak', dataModelViewSoftBreakInsertion( editor.model ), { priority: 'high' } );
-		editor.data.upcastDispatcher.on( 'element:pre', dataViewModelPreInsertion( editor.data ) );
+		editor.data.downcastDispatcher.on( 'insert:codeBlock', modelViewCodeBlockInsertion( editor.model ), { priority: 'high' } );
+		editor.data.downcastDispatcher.on( 'insert:softBreak', modelViewSoftBreakInsertion( editor.model ), { priority: 'high' } );
+
+		editor.data.upcastDispatcher.on( 'element:pre', dataViewModelCodeBlockInsertion( editor.data ) );
 	}
 	}
 
 
 	/**
 	/**
@@ -88,11 +90,33 @@ export default class CodeBlockEditing extends Plugin {
 	}
 	}
 }
 }
 
 
+// A model-to-view converter for the codeBlock element.
+//
+// @param {module:engine/model/model~Model} model
+// @returns {Function} Returns a conversion callback.
+function modelViewCodeBlockInsertion( model ) {
+	return ( evt, data, conversionApi ) => {
+		const { writer, mapper, consumable } = conversionApi;
+
+		if ( !consumable.consume( data.item, 'insert' ) ) {
+			return;
+		}
+
+		const targetViewPosition = mapper.toViewPosition( model.createPositionBefore( data.item ) );
+		const pre = writer.createContainerElement( 'pre' );
+		const code = writer.createContainerElement( 'code' );
+
+		writer.insert( writer.createPositionAt( pre, 0 ), code );
+		writer.insert( targetViewPosition, pre );
+		mapper.bindElements( data.item, code );
+	};
+}
+
 // A model-to-view converter for the new line separator.
 // A model-to-view converter for the new line separator.
 //
 //
 // @param {module:engine/model/model~Model} model
 // @param {module:engine/model/model~Model} model
 // @returns {Function} Returns a conversion callback.
 // @returns {Function} Returns a conversion callback.
-function dataModelViewSoftBreakInsertion( model ) {
+function modelViewSoftBreakInsertion( model ) {
 	return ( evt, data, conversionApi ) => {
 	return ( evt, data, conversionApi ) => {
 		if ( data.item.parent.name !== 'codeBlock' ) {
 		if ( data.item.parent.name !== 'codeBlock' ) {
 			return;
 			return;
@@ -110,22 +134,32 @@ function dataModelViewSoftBreakInsertion( model ) {
 	};
 	};
 }
 }
 
 
-// A view-to-model converter for `pre` tag.
+// A view-to-model converter for pre > code html.
 //
 //
 // @param {module:engine/controller/datacontroller~DataController} dataController
 // @param {module:engine/controller/datacontroller~DataController} dataController
 // @returns {Function} Returns a conversion callback.
 // @returns {Function} Returns a conversion callback.
-function dataViewModelPreInsertion( dataController ) {
+function dataViewModelCodeBlockInsertion( dataController ) {
 	return ( evt, data, conversionApi ) => {
 	return ( evt, data, conversionApi ) => {
+		const viewItem = data.viewItem;
+		const viewChild = viewItem.getChild( 0 );
+
+		if ( !viewChild || !viewChild.is( 'code' ) ) {
+			return;
+		}
+
 		const { consumable, writer } = conversionApi;
 		const { consumable, writer } = conversionApi;
 
 
-		if ( !consumable.consume( data.viewItem, { name: true } ) ) {
+		if ( !consumable.test( viewItem, { name: true } ) || !consumable.test( viewChild, { name: true } ) ) {
 			return;
 			return;
 		}
 		}
 
 
+		consumable.consume( viewItem, { name: true } );
+		consumable.consume( viewChild, { name: true } );
+
 		const modelItem = writer.createElement( 'codeBlock' );
 		const modelItem = writer.createElement( 'codeBlock' );
 
 
-		const stringifiedElement = dataController.processor.toData( data.viewItem );
-		const textData = extractDataFromPreElement( stringifiedElement );
+		const stringifiedElement = dataController.processor.toData( viewChild );
+		const textData = extractDataFromCodeElement( stringifiedElement );
 		const textLines = textData.split( '\n' ).map( data => writer.createText( data ) );
 		const textLines = textData.split( '\n' ).map( data => writer.createText( data ) );
 		const lastLine = textLines[ textLines.length - 1 ];
 		const lastLine = textLines[ textLines.length - 1 ];
 
 
@@ -140,15 +174,15 @@ function dataViewModelPreInsertion( dataController ) {
 		writer.insert( modelItem, data.modelCursor );
 		writer.insert( modelItem, data.modelCursor );
 
 
 		data.modelCursor = writer.createPositionAfter( modelItem );
 		data.modelCursor = writer.createPositionAfter( modelItem );
-		data.modelRange = writer.createRange( data.modelCursor );
+		data.modelRange = writer.createRangeOn( modelItem );
 	};
 	};
 }
 }
 
 
 // Returns content of `<pre></pre>` with unescaped html inside.
 // Returns content of `<pre></pre>` with unescaped html inside.
 //
 //
 // @param {String} stringifiedElement
 // @param {String} stringifiedElement
-function extractDataFromPreElement( stringifiedElement ) {
-	const data = new RegExp( /^<pre>(.*)<\/pre>$/, 's' ).exec( stringifiedElement )[ 1 ];
+function extractDataFromCodeElement( stringifiedElement ) {
+	const data = new RegExp( /^<code>(.*)<\/code>$/, 's' ).exec( stringifiedElement )[ 1 ];
 
 
 	return data
 	return data
 		.replace( /&lt;/g, '<' )
 		.replace( /&lt;/g, '<' )

+ 67 - 30
packages/ckeditor5-code-block/tests/codeblockediting.js

@@ -16,9 +16,10 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 
 
 describe( 'CodeBlockEditing', () => {
 describe( 'CodeBlockEditing', () => {
-	let editor, element, model;
+	let editor, element, model, view;
 
 
 	beforeEach( () => {
 	beforeEach( () => {
 		element = document.createElement( 'div' );
 		element = document.createElement( 'div' );
@@ -31,6 +32,7 @@ describe( 'CodeBlockEditing', () => {
 			.then( newEditor => {
 			.then( newEditor => {
 				editor = newEditor;
 				editor = newEditor;
 				model = editor.model;
 				model = editor.model;
+				view = editor.editing.view;
 			} );
 			} );
 	} );
 	} );
 
 
@@ -69,21 +71,6 @@ describe( 'CodeBlockEditing', () => {
 		expect( getModelData( model ) ).to.equal( '<codeBlock>f[o]o</codeBlock>' );
 		expect( getModelData( model ) ).to.equal( '<codeBlock>f[o]o</codeBlock>' );
 	} );
 	} );
 
 
-	it( 'adds converters to the data pipeline', () => {
-		const data = '<pre>x</pre>';
-
-		editor.setData( data );
-
-		expect( getModelData( model ) ).to.equal( '<codeBlock>[]x</codeBlock>' );
-		expect( editor.getData() ).to.equal( data );
-	} );
-
-	it( 'adds a converter to the view pipeline', () => {
-		setModelData( model, '<codeBlock>x</codeBlock>' );
-
-		expect( editor.getData() ).to.equal( '<pre>x</pre>' );
-	} );
-
 	it( 'should force shiftEnter command when pressing enter inside a codeBlock', () => {
 	it( 'should force shiftEnter command when pressing enter inside a codeBlock', () => {
 		const enterCommand = editor.commands.get( 'enter' );
 		const enterCommand = editor.commands.get( 'enter' );
 		const shiftEnterCommand = editor.commands.get( 'shiftEnter' );
 		const shiftEnterCommand = editor.commands.get( 'shiftEnter' );
@@ -120,17 +107,57 @@ describe( 'CodeBlockEditing', () => {
 		sinon.assert.notCalled( shiftEnterCommand.execute );
 		sinon.assert.notCalled( shiftEnterCommand.execute );
 	} );
 	} );
 
 
+	describe( 'editing pipeline m -> v', () => {
+		it( 'should convert empty codeBlock to empty pre tag', () => {
+			setModelData( model, '<codeBlock></codeBlock>' );
+
+			expect( getViewData( view ) ).to.equal( '<pre><code>[]</code></pre>' );
+		} );
+
+		it( 'should convert non-empty codeBlock to pre tag', () => {
+			setModelData( model, '<codeBlock>Foo</codeBlock>' );
+
+			expect( getViewData( view ) ).to.equal( '<pre><code>{}Foo</code></pre>' );
+		} );
+
+		it( 'should convert codeBlock with softBreaks to pre tag #1', () => {
+			setModelData( model,
+				'<codeBlock>' +
+					'Foo<softBreak></softBreak>' +
+					'Bar<softBreak></softBreak>' +
+					'Biz' +
+				'</codeBlock>'
+			);
+
+			expect( getViewData( view ) ).to.equal( '<pre><code>{}Foo\nBar\nBiz</code></pre>' );
+		} );
+
+		it( 'should convert codeBlock with softBreaks to pre tag #2', () => {
+			setModelData( model,
+				'<codeBlock>' +
+					'<softBreak></softBreak>' +
+					'<softBreak></softBreak>' +
+					'Foo' +
+					'<softBreak></softBreak>' +
+					'<softBreak></softBreak>' +
+				'</codeBlock>'
+			);
+
+			expect( getViewData( view ) ).to.equal( '<pre><code>{}\n\nFoo\n\n</code></pre>' );
+		} );
+	} );
+
 	describe( 'data pipeline m -> v conversion ', () => {
 	describe( 'data pipeline m -> v conversion ', () => {
 		it( 'should convert empty codeBlock to empty pre tag', () => {
 		it( 'should convert empty codeBlock to empty pre tag', () => {
 			setModelData( model, '<codeBlock></codeBlock>' );
 			setModelData( model, '<codeBlock></codeBlock>' );
 
 
-			expect( editor.getData( { trim: 'none' } ) ).to.equal( '<pre>&nbsp;</pre>' );
+			expect( editor.getData( { trim: 'none' } ) ).to.equal( '<pre><code>&nbsp;</code></pre>' );
 		} );
 		} );
 
 
 		it( 'should convert non-empty codeBlock to pre tag', () => {
 		it( 'should convert non-empty codeBlock to pre tag', () => {
 			setModelData( model, '<codeBlock>Foo</codeBlock>' );
 			setModelData( model, '<codeBlock>Foo</codeBlock>' );
 
 
-			expect( editor.getData() ).to.equal( '<pre>Foo</pre>' );
+			expect( editor.getData() ).to.equal( '<pre><code>Foo</code></pre>' );
 		} );
 		} );
 
 
 		it( 'should convert codeBlock with softBreaks to pre tag #1', () => {
 		it( 'should convert codeBlock with softBreaks to pre tag #1', () => {
@@ -142,7 +169,7 @@ describe( 'CodeBlockEditing', () => {
 				'</codeBlock>'
 				'</codeBlock>'
 			);
 			);
 
 
-			expect( editor.getData() ).to.equal( '<pre>Foo\nBar\nBiz</pre>' );
+			expect( editor.getData() ).to.equal( '<pre><code>Foo\nBar\nBiz</code></pre>' );
 		} );
 		} );
 
 
 		it( 'should convert codeBlock with softBreaks to pre tag #2', () => {
 		it( 'should convert codeBlock with softBreaks to pre tag #2', () => {
@@ -156,19 +183,31 @@ describe( 'CodeBlockEditing', () => {
 				'</codeBlock>'
 				'</codeBlock>'
 			);
 			);
 
 
-			expect( editor.getData() ).to.equal( '<pre>\n\nFoo\n\n</pre>' );
+			expect( editor.getData() ).to.equal( '<pre><code>\n\nFoo\n\n</code></pre>' );
 		} );
 		} );
 	} );
 	} );
 
 
 	describe( 'data pipeline v -> m conversion ', () => {
 	describe( 'data pipeline v -> m conversion ', () => {
-		it( 'should convert empty pre tag to code block', () => {
+		it( 'should not convert empty pre tag to code block', () => {
 			editor.setData( '<pre></pre>' );
 			editor.setData( '<pre></pre>' );
 
 
+			expect( getModelData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
+		} );
+
+		it( 'should not convert pre with no code child to code block', () => {
+			editor.setData( '<pre><samp></samp></pre>' );
+
+			expect( getModelData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
+		} );
+
+		it( 'should convert pre > code to code block', () => {
+			editor.setData( '<pre><code></code></pre>' );
+
 			expect( getModelData( model ) ).to.equal( '<codeBlock>[]</codeBlock>' );
 			expect( getModelData( model ) ).to.equal( '<codeBlock>[]</codeBlock>' );
 		} );
 		} );
 
 
-		it( 'should convert pre tag with multi-line text to code block #1', () => {
-			editor.setData( '<pre>foo\nbar</pre>' );
+		it( 'should convert pre > code with multi-line text to code block #1', () => {
+			editor.setData( '<pre><code>foo\nbar</code></pre>' );
 
 
 			expect( getModelData( model ) ).to.equal(
 			expect( getModelData( model ) ).to.equal(
 				'<codeBlock>[]' +
 				'<codeBlock>[]' +
@@ -179,11 +218,9 @@ describe( 'CodeBlockEditing', () => {
 			);
 			);
 		} );
 		} );
 
 
-		it( 'should convert pre tag with multi-line text to code block #2', () => {
-			editor.setData( '<pre>\n\n\nfoo\n\n</pre>' );
+		it( 'should convert pre > code with multi-line text to code block #2', () => {
+			editor.setData( '<pre><code>\n\nfoo\n\n</code></pre>' );
 
 
-			// The result may be unclear. 3 new line characters are converted to 2 softBreak elements.
-			// That's because of HTML rule, leading new line character for <pre/> element is stripped by the DOMParser.
 			expect( getModelData( model ) ).to.equal(
 			expect( getModelData( model ) ).to.equal(
 				'<codeBlock>[]' +
 				'<codeBlock>[]' +
 					'<softBreak></softBreak>' +
 					'<softBreak></softBreak>' +
@@ -195,8 +232,8 @@ describe( 'CodeBlockEditing', () => {
 			);
 			);
 		} );
 		} );
 
 
-		it( 'should convert pre tag with HTML inside', () => {
-			editor.setData( '<pre><p>Foo</p>\n<p>Bar</p></pre>' );
+		it( 'should convert pre > code with HTML inside', () => {
+			editor.setData( '<pre><code><p>Foo</p>\n<p>Bar</p></code></pre>' );
 
 
 			expect( getModelData( model ) ).to.equal(
 			expect( getModelData( model ) ).to.equal(
 				'<codeBlock>[]' +
 				'<codeBlock>[]' +
@@ -208,7 +245,7 @@ describe( 'CodeBlockEditing', () => {
 		} );
 		} );
 
 
 		it( 'should convert pre tag with HTML and nested pre tag', () => {
 		it( 'should convert pre tag with HTML and nested pre tag', () => {
-			editor.setData( '<pre><p>Foo</p><pre>Bar</pre><p>Biz</p></pre>' );
+			editor.setData( '<pre><code><p>Foo</p><pre>Bar</pre><p>Biz</p></code></pre>' );
 
 
 			expect( getModelData( model ) ).to.equal( '<codeBlock>[]<p>Foo</p><pre>Bar</pre><p>Biz</p></codeBlock>' );
 			expect( getModelData( model ) ).to.equal( '<codeBlock>[]<p>Foo</p><pre>Bar</pre><p>Biz</p></codeBlock>' );
 		} );
 		} );