8
0
Просмотр исходного кода

Auto-paragraphing during conversion moved to engine.

Kuba Niegowski 5 лет назад
Родитель
Сommit
5bf3f75e5e

+ 37 - 11
packages/ckeditor5-engine/src/conversion/upcastdispatcher.js

@@ -349,22 +349,30 @@ export default class UpcastDispatcher {
 	 * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#splitToAllowedParent
 	 */
 	_splitToAllowedParent( node, modelCursor ) {
+		const { schema, writer } = this.conversionApi;
+
 		// Try to find allowed parent.
-		const allowedParent = this.conversionApi.schema.findAllowedParent( modelCursor, node );
+		let allowedParent = schema.findAllowedParent( modelCursor, node );
 
-		// When there is no parent that allows to insert node then return `null`.
-		if ( !allowedParent ) {
-			return null;
-		}
+		if ( allowedParent ) {
+			// When current position parent allows to insert node then return this position.
+			if ( allowedParent === modelCursor.parent ) {
+				return { position: modelCursor };
+			}
 
-		// When current position parent allows to insert node then return this position.
-		if ( allowedParent === modelCursor.parent ) {
-			return { position: modelCursor };
+			// When allowed parent is in context tree (it's outside the converted tree).
+			if ( this._modelCursor.parent.getAncestors().includes( allowedParent ) ) {
+				allowedParent = null;
+			}
 		}
 
-		// When allowed parent is in context tree.
-		if ( this._modelCursor.parent.getAncestors().includes( allowedParent ) ) {
-			return null;
+		if ( !allowedParent ) {
+			// Check if the node wrapped with a paragraph would be accepted by the schema.
+			const paragraph = wrapWithParagraphIfPossible( node, modelCursor, writer, schema );
+
+			return paragraph && {
+				position: writer.createPositionAt( paragraph, 0 )
+			};
 		}
 
 		// Split element to allowed parent.
@@ -570,6 +578,24 @@ function createContextTree( contextDefinition, writer ) {
 	return position;
 }
 
+// Auto-paragraphing
+function wrapWithParagraphIfPossible( node, position, writer, schema ) {
+	// Check if the node wrapped with a paragraph would be accepted by the schema.
+	const context = schema.createContext( position );
+
+	// If paragraph is not acceptable in the current position or the model node is not accepted in that context
+	// there is nothing more that can be done with it.
+	if ( !schema.checkChild( context, 'paragraph' ) || !schema.checkChild( context.push( 'paragraph' ), node ) ) {
+		return null;
+	}
+
+	const paragraph = writer.createElement( 'paragraph' );
+
+	writer.insert( paragraph, position );
+
+	return paragraph;
+}
+
 /**
  * A set of conversion utils available as the third parameter of
  * {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher upcast dispatcher}'s events.

+ 27 - 10
packages/ckeditor5-engine/src/conversion/upcasthelpers.js

@@ -459,20 +459,37 @@ export function convertToModelFragment() {
  * @returns {Function} {@link module:engine/view/text~Text View text} converter.
  */
 export function convertText() {
-	return ( evt, data, conversionApi ) => {
-		if ( conversionApi.schema.checkChild( data.modelCursor, '$text' ) ) {
-			if ( conversionApi.consumable.consume( data.viewItem ) ) {
-				const text = conversionApi.writer.createText( data.viewItem.data );
+	return ( evt, data, { schema, consumable, writer } ) => {
+		let position = data.modelCursor;
 
-				conversionApi.writer.insert( text, data.modelCursor );
+		// When node is already converted then do nothing.
+		if ( data.modelRange || !consumable.test( data.viewItem ) ) {
+			return;
+		}
 
-				data.modelRange = conversionApi.writer.createRange(
-					data.modelCursor,
-					data.modelCursor.getShiftedBy( text.offsetSize )
-				);
-				data.modelCursor = data.modelRange.end;
+		if ( !schema.checkChild( position, '$text' ) ) {
+			// Check if this text node would be allowed if wrapped with a paragraph.
+			const context = schema.createContext( position );
+
+			if ( !schema.checkChild( context, 'paragraph' ) || !schema.checkChild( context.push( 'paragraph' ), '$text' ) ) {
+				return;
 			}
+
+			const paragraph = writer.createElement( 'paragraph' );
+
+			writer.insert( paragraph, position );
+			position = writer.createPositionAt( paragraph, 0 );
 		}
+
+		const text = writer.createText( data.viewItem.data );
+
+		writer.insert( text, position );
+
+		data.modelRange = writer.createRange(
+			position,
+			position.getShiftedBy( text.offsetSize )
+		);
+		data.modelCursor = data.modelRange.end;
 	};
 }
 

+ 1 - 1
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -156,7 +156,7 @@ describe( 'DataController', () => {
 			const viewFragment = new ViewDocumentFragment( viewDocument, [ parseView( 'foo' ) ] );
 
 			// Model fragment in root.
-			expect( stringify( data.toModel( viewFragment ) ) ).to.equal( '' );
+			expect( stringify( data.toModel( viewFragment ) ) ).to.equal( '<paragraph>foo</paragraph>' );
 
 			// Model fragment in inline root.
 			expect( stringify( data.toModel( viewFragment, [ 'inlineRoot' ] ) ) ).to.equal( 'foo' );

+ 33 - 0
packages/ckeditor5-engine/tests/conversion/upcastdispatcher.js

@@ -605,6 +605,39 @@ describe( 'UpcastDispatcher', () => {
 				sinon.assert.calledOnce( spy );
 			} );
 
+			it( 'should insert paragraph if it\'s allowed and if it would allow conversion', () => {
+				const spy = sinon.spy();
+
+				model.schema.register( 'section', {
+					allowIn: '$root'
+				} );
+				model.schema.register( 'span', {
+					allowIn: 'paragraph'
+				} );
+				model.schema.extend( 'paragraph', {
+					allowIn: 'section'
+				} );
+
+				dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
+					const section = conversionApi.writer.createElement( 'section' );
+					const position = ModelPosition._createAt( section, 0 );
+
+					const span = conversionApi.writer.createElement( 'span' );
+
+					const result = conversionApi.splitToAllowedParent( span, position );
+
+					expect( section.getChild( 0 ).name ).to.equal( 'paragraph' );
+					expect( result ).to.deep.equal( {
+						position: ModelPosition._createAt( section.getChild( 0 ), 0 )
+					} );
+
+					spy();
+				} );
+
+				model.change( writer => dispatcher.convert( new ViewDocumentFragment( viewDocument ), writer ) );
+				sinon.assert.calledOnce( spy );
+			} );
+
 			it( 'should return null if element is not allowed in position and any of ancestors', () => {
 				const spy = sinon.spy();
 

+ 26 - 1
packages/ckeditor5-engine/tests/conversion/upcasthelpers.js

@@ -949,7 +949,7 @@ describe( 'upcast-converters', () => {
 
 		it( 'should not convert text if it is wrong with schema', () => {
 			schema.addChildCheck( ( ctx, childDef ) => {
-				if ( childDef.name == '$text' && ctx.endsWith( '$root' ) ) {
+				if ( ( childDef.name == '$text' || childDef.name == 'paragraph' ) && ctx.endsWith( '$root' ) ) {
 					return false;
 				}
 			} );
@@ -969,6 +969,31 @@ describe( 'upcast-converters', () => {
 			expect( conversionResult.getChild( 0 ).data ).to.equal( 'foobar' );
 		} );
 
+		it( 'should wrap text with paragraph if it would allow conversion', () => {
+			schema.addChildCheck( ( ctx, childDef ) => {
+				if ( childDef.name == '$text' && ctx.endsWith( '$root' ) ) {
+					return false;
+				}
+			} );
+
+			const viewText = new ViewText( viewDocument, 'foobar' );
+			dispatcher.on( 'text', convertText() );
+			let conversionResult = model.change( writer => dispatcher.convert( viewText, writer, context ) );
+
+			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+			expect( conversionResult.childCount ).to.equal( 1 );
+			expect( conversionResult.getChild( 0 ).name ).to.equal( 'paragraph' );
+			expect( conversionResult.getNodeByPath( [ 0, 0 ] ) ).to.be.instanceof( ModelText );
+			expect( conversionResult.getNodeByPath( [ 0, 0 ] ).data ).to.equal( 'foobar' );
+
+			conversionResult = model.change( writer => dispatcher.convert( viewText, writer, [ '$block' ] ) );
+
+			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+			expect( conversionResult.childCount ).to.equal( 1 );
+			expect( conversionResult.getChild( 0 ) ).to.be.instanceof( ModelText );
+			expect( conversionResult.getChild( 0 ).data ).to.equal( 'foobar' );
+		} );
+
 		it( 'should support unicode', () => {
 			const viewText = new ViewText( viewDocument, 'நிலைக்கு' );
 

+ 3 - 76
packages/ckeditor5-paragraph/src/paragraph.js

@@ -40,7 +40,6 @@ export default class Paragraph extends Plugin {
 	init() {
 		const editor = this.editor;
 		const model = editor.model;
-		const data = editor.data;
 
 		editor.commands.add( 'paragraph', new ParagraphCommand( editor ) );
 		editor.commands.add( 'insertParagraph', new InsertParagraphCommand( editor ) );
@@ -50,10 +49,7 @@ export default class Paragraph extends Plugin {
 
 		editor.conversion.elementToElement( { model: 'paragraph', view: 'p' } );
 
-		// Content autoparagraphing. --------------------------------------------------
-
-		// Handles element which has not been converted by any plugin and checks if it would be converted if
-		// we wrap it in a paragraph or change it to a paragraph.
+		// Conversion for paragraph-like elements which has not been converted by any plugin.
 		editor.conversion.for( 'upcast' ).elementToElement( {
 			model: ( viewElement, modelWriter ) => {
 				if ( !Paragraph.paragraphLikeElements.has( viewElement.name ) ) {
@@ -71,53 +67,6 @@ export default class Paragraph extends Plugin {
 			converterPriority: 'low'
 		} );
 
-		const conversionApi = data.upcastDispatcher.conversionApi;
-		const originalSplitToAllowedParent = conversionApi.splitToAllowedParent;
-
-		conversionApi.splitToAllowedParent = ( node, modelCursor ) => {
-			const result = originalSplitToAllowedParent( node, modelCursor );
-
-			if ( result ) {
-				return result;
-			}
-
-			if ( !isParagraphable( node, modelCursor, conversionApi.schema ) ) {
-				return null;
-			}
-
-			const paragraph = conversionApi.writer.createElement( 'paragraph' );
-
-			conversionApi.writer.insert( paragraph, modelCursor );
-
-			return {
-				position: conversionApi.writer.createPositionAt( paragraph, 0 )
-			};
-		};
-
-		data.upcastDispatcher.on( 'element', ( evt, data, conversionApi ) => {
-			// Do not try auto-paragraphing if the element was already converted.
-			if ( !conversionApi.consumable.test( data.viewItem, { name: data.viewItem.name } ) ) {
-				return;
-			}
-
-			// If the element is not paragraph-like try wrapping it in a paragraph.
-			if ( isParagraphable( data.viewItem, data.modelCursor, conversionApi.schema ) ) {
-				Object.assign( data, wrapInParagraph( data.viewItem, data.modelCursor, conversionApi ) );
-			}
-		}, { priority: 'low' } );
-
-		// Handles not converted text nodes and checks if would be converted if we wraps then by a paragraph.
-		data.upcastDispatcher.on( 'text', ( evt, data, conversionApi ) => {
-			// When node is already converted then do nothing.
-			if ( data.modelRange ) {
-				return;
-			}
-
-			if ( isParagraphable( data.viewItem, data.modelCursor, conversionApi.schema ) ) {
-				Object.assign( data, wrapInParagraph( data.viewItem, data.modelCursor, conversionApi ) );
-			}
-		}, { priority: 'lowest' } );
-
 		// Empty roots autoparagraphing. -----------------------------------------------
 
 		// Post-fixer which takes care of adding empty paragraph elements to empty roots.
@@ -198,28 +147,6 @@ Paragraph.paragraphLikeElements = new Set( [
 	'h6',
 	'li',
 	'p',
-	'td'
+	'td',
+	'th'
 ] );
-
-function wrapInParagraph( input, position, conversionApi ) {
-	const paragraph = conversionApi.writer.createElement( 'paragraph' );
-
-	conversionApi.writer.insert( paragraph, position );
-	return conversionApi.convertItem( input, conversionApi.writer.createPositionAt( paragraph, 0 ) );
-}
-
-function isParagraphable( node, position, schema ) {
-	const context = schema.createContext( position );
-
-	// When paragraph is allowed in this context...
-	if ( !schema.checkChild( context, 'paragraph' ) ) {
-		return false;
-	}
-
-	// And a node would be allowed in this paragraph...
-	if ( !schema.checkChild( context.push( 'paragraph' ), node ) ) {
-		return false;
-	}
-
-	return true;
-}