浏览代码

Merge pull request #25 from ckeditor/t/ckeditor5-engine/1563

Added: Postfixer cleaning incorrect blockquotes.
Piotr Jasiun 7 年之前
父节点
当前提交
1836b32493

+ 2 - 0
packages/ckeditor5-block-quote/package.json

@@ -24,6 +24,8 @@
     "@ckeditor/ckeditor5-list": "^11.0.1",
     "@ckeditor/ckeditor5-list": "^11.0.1",
     "@ckeditor/ckeditor5-paragraph": "^10.0.2",
     "@ckeditor/ckeditor5-paragraph": "^10.0.2",
     "@ckeditor/ckeditor5-typing": "^11.0.0",
     "@ckeditor/ckeditor5-typing": "^11.0.0",
+    "@ckeditor/ckeditor5-engine": "^10.2.0",
+    "@ckeditor/ckeditor5-basic-styles": "^10.0.2",
     "eslint": "^5.5.0",
     "eslint": "^5.5.0",
     "eslint-config-ckeditor5": "^1.0.7",
     "eslint-config-ckeditor5": "^1.0.7",
     "husky": "^0.14.3",
     "husky": "^0.14.3",

+ 54 - 0
packages/ckeditor5-block-quote/src/blockquoteediting.js

@@ -8,6 +8,9 @@
  */
  */
 
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import Range from '@ckeditor/ckeditor5-engine/src/model/range';
+import Position from '@ckeditor/ckeditor5-engine/src/model/position';
+
 import BlockQuoteCommand from './blockquotecommand';
 import BlockQuoteCommand from './blockquotecommand';
 
 
 /**
 /**
@@ -40,6 +43,57 @@ export default class BlockQuoteEditing extends Plugin {
 		} );
 		} );
 
 
 		editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } );
 		editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } );
+
+		// Postfixer which cleans incorrect model states connected with block quotes.
+		editor.model.document.registerPostFixer( writer => {
+			const changes = editor.model.document.differ.getChanges();
+
+			for ( const entry of changes ) {
+				if ( entry.type == 'insert' ) {
+					const element = entry.position.nodeAfter;
+
+					if ( !element ) {
+						// We are inside a text node.
+						continue;
+					}
+
+					if ( element.is( 'blockQuote' ) && element.isEmpty ) {
+						// Added an empty blockQuote - remove it.
+						writer.remove( element );
+
+						return true;
+					} else if ( element.is( 'blockQuote' ) && !schema.checkChild( entry.position, element ) ) {
+						// Added a blockQuote in incorrect place - most likely inside another blockQuote. Unwrap it
+						// so the content inside is not lost.
+						writer.unwrap( element );
+
+						return true;
+					} else if ( element.is( 'element' ) ) {
+						// Just added an element. Check its children to see if there are no nested blockQuotes somewhere inside.
+						const range = Range.createIn( element );
+
+						for ( const child of range.getItems() ) {
+							if ( child.is( 'blockQuote' ) && !schema.checkChild( Position.createBefore( child ), child ) ) {
+								writer.unwrap( child );
+
+								return true;
+							}
+						}
+					}
+				} else if ( entry.type == 'remove' ) {
+					const parent = entry.position.parent;
+
+					if ( parent.is( 'blockQuote' ) && parent.isEmpty ) {
+						// Something got removed and now blockQuote is empty. Remove the blockQuote as well.
+						writer.remove( parent );
+
+						return true;
+					}
+				}
+			}
+
+			return false;
+		} );
 	}
 	}
 
 
 	/**
 	/**

+ 33 - 21
packages/ckeditor5-block-quote/tests/blockquotecommand.js

@@ -193,17 +193,19 @@ describe( 'BlockQuoteCommand', () => {
 
 
 				editor.execute( 'blockQuote' );
 				editor.execute( 'blockQuote' );
 
 
+				// Selection incorrectly trimmed.
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<blockQuote>' +
 					'<blockQuote>' +
-						'<heading>a[bc</heading>' +
-						'<paragraph>x]x</paragraph>' +
+						'<heading>abc</heading>' +
+						'<paragraph>[x]x</paragraph>' +
 						'<paragraph>yy</paragraph>' +
 						'<paragraph>yy</paragraph>' +
 					'</blockQuote>' +
 					'</blockQuote>' +
 					'<paragraph>def</paragraph>'
 					'<paragraph>def</paragraph>'
 				);
 				);
 
 
+				// Selection incorrectly trimmed.
 				expect( getViewData( editor.editing.view ) ).to.equal(
 				expect( getViewData( editor.editing.view ) ).to.equal(
-					'<blockquote><h>a{bc</h><p>x}x</p><p>yy</p></blockquote><p>def</p>'
+					'<blockquote><h>abc</h><p>{x}x</p><p>yy</p></blockquote><p>def</p>'
 				);
 				);
 			} );
 			} );
 
 
@@ -258,17 +260,19 @@ describe( 'BlockQuoteCommand', () => {
 
 
 				editor.execute( 'blockQuote' );
 				editor.execute( 'blockQuote' );
 
 
+				// Selection incorrectly trimmed.
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<blockQuote>' +
 					'<blockQuote>' +
-						'<heading>a[bc</heading>' +
+						'<heading>abc</heading>' +
 						'<paragraph>def</paragraph>' +
 						'<paragraph>def</paragraph>' +
-						'<paragraph>x]x</paragraph>' +
+						'<paragraph>[x]x</paragraph>' +
 					'</blockQuote>' +
 					'</blockQuote>' +
 					'<paragraph>ghi</paragraph>'
 					'<paragraph>ghi</paragraph>'
 				);
 				);
 
 
+				// Selection incorrectly trimmed.
 				expect( getViewData( editor.editing.view ) ).to.equal(
 				expect( getViewData( editor.editing.view ) ).to.equal(
-					'<blockquote><h>a{bc</h><p>def</p><p>x}x</p></blockquote><p>ghi</p>'
+					'<blockquote><h>abc</h><p>def</p><p>{x}x</p></blockquote><p>ghi</p>'
 				);
 				);
 			} );
 			} );
 
 
@@ -282,18 +286,20 @@ describe( 'BlockQuoteCommand', () => {
 
 
 				editor.execute( 'blockQuote' );
 				editor.execute( 'blockQuote' );
 
 
+				// Selection incorrectly trimmed.
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<blockQuote>' +
 					'<blockQuote>' +
-						'<paragraph>a[bc</paragraph>' +
+						'<paragraph>abc</paragraph>' +
 					'</blockQuote>' +
 					'</blockQuote>' +
-					'<widget>xx</widget>' +
+					'[<widget>xx</widget>' +
 					'<blockQuote>' +
 					'<blockQuote>' +
 						'<paragraph>de]f</paragraph>' +
 						'<paragraph>de]f</paragraph>' +
 					'</blockQuote>'
 					'</blockQuote>'
 				);
 				);
 
 
+				// Selection incorrectly trimmed.
 				expect( getViewData( editor.editing.view ) ).to.equal(
 				expect( getViewData( editor.editing.view ) ).to.equal(
-					'<blockquote><p>a{bc</p></blockquote><widget>xx</widget><blockquote><p>de}f</p></blockquote>'
+					'<blockquote><p>abc</p></blockquote>[<widget>xx</widget><blockquote><p>de}f</p></blockquote>'
 				);
 				);
 			} );
 			} );
 
 
@@ -310,17 +316,19 @@ describe( 'BlockQuoteCommand', () => {
 
 
 				editor.execute( 'blockQuote' );
 				editor.execute( 'blockQuote' );
 
 
+				// Selection incorrectly trimmed.
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
-					'<blockQuote><paragraph>a[bc</paragraph></blockQuote>' +
-					'<widget>xx</widget>' +
+					'<blockQuote><paragraph>abc</paragraph></blockQuote>' +
+					'[<widget>xx</widget>' +
 					'<blockQuote><paragraph>def</paragraph><paragraph>ghi</paragraph></blockQuote>' +
 					'<blockQuote><paragraph>def</paragraph><paragraph>ghi</paragraph></blockQuote>' +
 					'<widget>yy</widget>' +
 					'<widget>yy</widget>' +
 					'<blockQuote><paragraph>jk]l</paragraph></blockQuote>'
 					'<blockQuote><paragraph>jk]l</paragraph></blockQuote>'
 				);
 				);
 
 
+				// Selection incorrectly trimmed.
 				expect( getViewData( editor.editing.view ) ).to.equal(
 				expect( getViewData( editor.editing.view ) ).to.equal(
-					'<blockquote><p>a{bc</p></blockquote>' +
-					'<widget>xx</widget>' +
+					'<blockquote><p>abc</p></blockquote>' +
+					'[<widget>xx</widget>' +
 					'<blockquote><p>def</p><p>ghi</p></blockquote>' +
 					'<blockquote><p>def</p><p>ghi</p></blockquote>' +
 					'<widget>yy</widget>' +
 					'<widget>yy</widget>' +
 					'<blockquote><p>jk}l</p></blockquote>'
 					'<blockquote><p>jk}l</p></blockquote>'
@@ -341,26 +349,28 @@ describe( 'BlockQuoteCommand', () => {
 
 
 				editor.execute( 'blockQuote' );
 				editor.execute( 'blockQuote' );
 
 
+				// Selection incorrectly trimmed.
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<paragraph>x</paragraph>' +
 					'<paragraph>x</paragraph>' +
 					'<blockQuote>' +
 					'<blockQuote>' +
-						'<paragraph>a[bc</paragraph>' +
+						'<paragraph>abc</paragraph>' +
 						'<paragraph>def</paragraph>' +
 						'<paragraph>def</paragraph>' +
 						'<paragraph>ghi</paragraph>' +
 						'<paragraph>ghi</paragraph>' +
 						'<paragraph>jkl</paragraph>' +
 						'<paragraph>jkl</paragraph>' +
-						'<paragraph>mn]o</paragraph>' +
+						'<paragraph>[mn]o</paragraph>' +
 					'</blockQuote>' +
 					'</blockQuote>' +
 					'<paragraph>y</paragraph>'
 					'<paragraph>y</paragraph>'
 				);
 				);
 
 
+				// Selection incorrectly trimmed.
 				expect( getViewData( editor.editing.view ) ).to.equal(
 				expect( getViewData( editor.editing.view ) ).to.equal(
 					'<p>x</p>' +
 					'<p>x</p>' +
 					'<blockquote>' +
 					'<blockquote>' +
-						'<p>a{bc</p>' +
+						'<p>abc</p>' +
 						'<p>def</p>' +
 						'<p>def</p>' +
 						'<p>ghi</p>' +
 						'<p>ghi</p>' +
 						'<p>jkl</p>' +
 						'<p>jkl</p>' +
-						'<p>mn}o</p>' +
+						'<p>{mn}o</p>' +
 					'</blockquote>' +
 					'</blockquote>' +
 					'<p>y</p>'
 					'<p>y</p>'
 				);
 				);
@@ -386,11 +396,12 @@ describe( 'BlockQuoteCommand', () => {
 
 
 				editor.execute( 'blockQuote' );
 				editor.execute( 'blockQuote' );
 
 
+				// Selection incorrectly trimmed.
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<blockQuote>' +
 					'<blockQuote>' +
-						'<paragraph>a[bc</paragraph>' +
+						'<paragraph>abc</paragraph>' +
 					'</blockQuote>' +
 					'</blockQuote>' +
-					'<fooBlock>xx</fooBlock>' +
+					'<fooBlock>[xx</fooBlock>' +
 					'<blockQuote>' +
 					'<blockQuote>' +
 						'<paragraph>de]f</paragraph>' +
 						'<paragraph>de]f</paragraph>' +
 					'</blockQuote>'
 					'</blockQuote>'
@@ -417,11 +428,12 @@ describe( 'BlockQuoteCommand', () => {
 
 
 				editor.execute( 'blockQuote' );
 				editor.execute( 'blockQuote' );
 
 
+				// Selection incorrectly trimmed.
 				expect( getModelData( model ) ).to.equal(
 				expect( getModelData( model ) ).to.equal(
 					'<blockQuote>' +
 					'<blockQuote>' +
-						'<paragraph>a[bc</paragraph>' +
+						'<paragraph>abc</paragraph>' +
 					'</blockQuote>' +
 					'</blockQuote>' +
-					'<fooWrapper><fooBlock>xx</fooBlock></fooWrapper>' +
+					'<fooWrapper><fooBlock>[xx</fooBlock></fooWrapper>' +
 					'<blockQuote>' +
 					'<blockQuote>' +
 						'<paragraph>de]f</paragraph>' +
 						'<paragraph>de]f</paragraph>' +
 					'</blockQuote>'
 					'</blockQuote>'

+ 65 - 1
packages/ckeditor5-block-quote/tests/blockquoteediting.js

@@ -6,6 +6,9 @@
 import BlockQuoteEditing from '../src/blockquoteediting';
 import BlockQuoteEditing from '../src/blockquoteediting';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
 import ListEditing from '@ckeditor/ckeditor5-list/src/listediting';
+import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
+
+import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 
 
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
 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';
@@ -18,7 +21,7 @@ describe( 'BlockQuoteEditing', () => {
 	beforeEach( () => {
 	beforeEach( () => {
 		return VirtualTestEditor
 		return VirtualTestEditor
 			.create( {
 			.create( {
-				plugins: [ BlockQuoteEditing, Paragraph ]
+				plugins: [ BlockQuoteEditing, Paragraph, BoldEditing ]
 			} )
 			} )
 			.then( newEditor => {
 			.then( newEditor => {
 				editor = newEditor;
 				editor = newEditor;
@@ -77,4 +80,65 @@ describe( 'BlockQuoteEditing', () => {
 				expect( editor.getData() ).to.equal( '<blockquote><ul><li>xx</li></ul></blockquote>' );
 				expect( editor.getData() ).to.equal( '<blockquote><ul><li>xx</li></ul></blockquote>' );
 			} );
 			} );
 	} );
 	} );
+
+	it( 'should remove empty blockQuote elements', () => {
+		setModelData( model, '<blockQuote></blockQuote><paragraph>Foo</paragraph>' );
+
+		expect( editor.getData() ).to.equal( '<p>Foo</p>' );
+	} );
+
+	it( 'should remove blockQuotes which became empty', () => {
+		setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote>' );
+
+		model.change( writer => {
+			const root = model.document.getRoot();
+			const bq = root.getChild( 0 );
+
+			writer.remove( Range.createIn( bq ) );
+		} );
+
+		expect( editor.getData() ).to.equal( '<p>&nbsp;</p>' ); // Autoparagraphed.
+	} );
+
+	it( 'should unwrap a blockQuote if it was inserted into another blockQuote', () => {
+		setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote>' );
+
+		model.change( writer => {
+			const root = model.document.getRoot();
+			const bq = writer.createElement( 'blockQuote' );
+			const p = writer.createElement( 'paragraph' );
+
+			writer.insertText( 'Bar', p, 0 ); // <p>Bar</p>.
+			writer.insert( p, bq, 0 ); // <blockquote><p>Bar</p></blockquote>.
+			writer.insert( bq, root.getChild( 0 ), 1 ); // Insert after <p>Foo</p>.
+		} );
+
+		expect( editor.getData() ).to.equal( '<blockquote><p>Foo</p><p>Bar</p></blockquote>' );
+	} );
+
+	it( 'should unwrap nested blockQuote if it was wrapped into another blockQuote', () => {
+		setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote><paragraph>Bar</paragraph>' );
+
+		model.change( writer => {
+			const root = model.document.getRoot();
+
+			writer.wrap( Range.createIn( root ), 'blockQuote' );
+		} );
+
+		expect( editor.getData() ).to.equal( '<blockquote><p>Foo</p><p>Bar</p></blockquote>' );
+	} );
+
+	it( 'postfixer should do nothing on attribute change', () => {
+		// This is strictly a 100% CC test.
+		setModelData( model, '<blockQuote><paragraph>Foo</paragraph></blockQuote>' );
+
+		model.change( writer => {
+			const root = model.document.getRoot();
+			const p = root.getChild( 0 ).getChild( 0 );
+
+			writer.setAttribute( 'bold', true, Range.createIn( p ) );
+		} );
+
+		expect( editor.getData() ).to.equal( '<blockquote><p><strong>Foo</strong></p></blockquote>' );
+	} );
 } );
 } );

+ 3 - 2
packages/ckeditor5-block-quote/tests/integration.js

@@ -383,13 +383,14 @@ describe( 'BlockQuote integration', () => {
 
 
 			editor.execute( 'blockQuote' );
 			editor.execute( 'blockQuote' );
 
 
+			// Selection incorrectly trimmed.
 			expect( getModelData( model ) ).to.equal(
 			expect( getModelData( model ) ).to.equal(
 				'<blockQuote>' +
 				'<blockQuote>' +
-					'<paragraph>fo[o</paragraph>' +
+					'<paragraph>foo</paragraph>' +
 					'<image src="foo.png">' +
 					'<image src="foo.png">' +
 						'<caption>xxx</caption>' +
 						'<caption>xxx</caption>' +
 					'</image>' +
 					'</image>' +
-					'<paragraph>b]ar</paragraph>' +
+					'<paragraph>[b]ar</paragraph>' +
 				'</blockQuote>'
 				'</blockQuote>'
 			);
 			);
 		} );
 		} );