Преглед на файлове

Merge pull request #7381 from ckeditor/i/7379

Other (table): Pasting a table into a table is more tolerant for whitespaces around a pasted table. Closes #7379.
Maciej преди 5 години
родител
ревизия
669d54f688
променени са 2 файла, в които са добавени 192 реда и са изтрити 8 реда
  1. 30 5
      packages/ckeditor5-table/src/tableclipboard.js
  2. 162 3
      packages/ckeditor5-table/tests/tableclipboard-paste.js

+ 30 - 5
packages/ckeditor5-table/src/tableclipboard.js

@@ -120,7 +120,7 @@ export default class TableClipboard extends Plugin {
 		}
 
 		// We might need to crop table before inserting so reference might change.
-		let pastedTable = getTableIfOnlyTableInContent( content );
+		let pastedTable = getTableIfOnlyTableInContent( content, model );
 
 		if ( !pastedTable ) {
 			return;
@@ -336,7 +336,7 @@ function expandTableSize( table, expectedHeight, expectedWidth, writer, tableUti
 	}
 }
 
-function getTableIfOnlyTableInContent( content ) {
+function getTableIfOnlyTableInContent( content, model ) {
 	// Table passed directly.
 	if ( content.is( 'table' ) ) {
 		return content;
@@ -344,11 +344,36 @@ function getTableIfOnlyTableInContent( content ) {
 
 	// We do not support mixed content when pasting table into table.
 	// See: https://github.com/ckeditor/ckeditor5/issues/6817.
-	if ( content.childCount != 1 || !content.getChild( 0 ).is( 'table' ) ) {
-		return null;
+	if ( content.childCount == 1 && content.getChild( 0 ).is( 'table' ) ) {
+		return content.getChild( 0 );
 	}
 
-	return content.getChild( 0 );
+	// If there are only whitespaces around a table then use that table for pasting.
+
+	const contentRange = model.createRangeIn( content );
+
+	for ( const element of contentRange.getItems() ) {
+		if ( element.is( 'table' ) ) {
+			// Stop checking if there is some content before table.
+			const rangeBefore = model.createRange( contentRange.start, model.createPositionBefore( element ) );
+
+			if ( model.hasContent( rangeBefore, { ignoreWhitespaces: true } ) ) {
+				return null;
+			}
+
+			// Stop checking if there is some content after table.
+			const rangeAfter = model.createRange( model.createPositionAfter( element ), contentRange.end );
+
+			if ( model.hasContent( rangeAfter, { ignoreWhitespaces: true } ) ) {
+				return null;
+			}
+
+			// There wasn't any content neither before nor after.
+			return element;
+		}
+	}
+
+	return null;
 }
 
 // Returns two-dimensional array that is addressed by [ row ][ column ] that stores cells anchored at given location.

+ 162 - 3
packages/ckeditor5-table/tests/tableclipboard-paste.js

@@ -178,7 +178,8 @@ describe( 'table clipboard', () => {
 
 			const table = viewTable( [
 				[ 'aa', 'ab' ],
-				[ 'ba', 'bb' ] ] );
+				[ 'ba', 'bb' ]
+			] );
 
 			const data = {
 				dataTransfer: createDataTransfer(),
@@ -204,7 +205,8 @@ describe( 'table clipboard', () => {
 
 			const table = viewTable( [
 				[ 'aa', 'ab' ],
-				[ 'ba', 'bb' ] ] );
+				[ 'ba', 'bb' ]
+			] );
 
 			const data = {
 				dataTransfer: createDataTransfer(),
@@ -230,7 +232,8 @@ describe( 'table clipboard', () => {
 
 			const table = viewTable( [
 				[ 'aa', 'ab' ],
-				[ 'ba', 'bb' ] ] );
+				[ 'ba', 'bb' ]
+			] );
 
 			const data = {
 				dataTransfer: createDataTransfer(),
@@ -248,6 +251,162 @@ describe( 'table clipboard', () => {
 			] ) );
 		} );
 
+		it( 'should alter model.insertContent if mixed content is pasted (table + empty paragraph)', () => {
+			tableSelection.setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+			);
+
+			const table = viewTable( [
+				[ 'aa', 'ab' ],
+				[ 'ba', 'bb' ]
+			] );
+
+			const data = {
+				dataTransfer: createDataTransfer(),
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy()
+			};
+			data.dataTransfer.setData( 'text/html', `${ table }<p>&nbsp;</p>` );
+			viewDocument.fire( 'paste', data );
+
+			assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+				[ 'aa', 'ab', '02', '03' ],
+				[ 'ba', 'bb', '12', '13' ],
+				[ '20', '21', '22', '23' ],
+				[ '30', '31', '32', '33' ]
+			] ) );
+		} );
+
+		it( 'should alter model.insertContent if mixed content is pasted (table + br)', () => {
+			tableSelection.setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+			);
+
+			const table = viewTable( [
+				[ 'aa', 'ab' ],
+				[ 'ba', 'bb' ]
+			] );
+
+			const data = {
+				dataTransfer: createDataTransfer(),
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy()
+			};
+			data.dataTransfer.setData( 'text/html', `${ table }<br>` );
+			viewDocument.fire( 'paste', data );
+
+			assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+				[ 'aa', 'ab', '02', '03' ],
+				[ 'ba', 'bb', '12', '13' ],
+				[ '20', '21', '22', '23' ],
+				[ '30', '31', '32', '33' ]
+			] ) );
+		} );
+
+		it( 'should alter model.insertContent if mixed content is pasted (empty paragraph + table)', () => {
+			tableSelection.setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+			);
+
+			const table = viewTable( [
+				[ 'aa', 'ab' ],
+				[ 'ba', 'bb' ]
+			] );
+
+			const data = {
+				dataTransfer: createDataTransfer(),
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy()
+			};
+			data.dataTransfer.setData( 'text/html', `<p>&nbsp;</p>${ table }` );
+			viewDocument.fire( 'paste', data );
+
+			assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+				[ 'aa', 'ab', '02', '03' ],
+				[ 'ba', 'bb', '12', '13' ],
+				[ '20', '21', '22', '23' ],
+				[ '30', '31', '32', '33' ]
+			] ) );
+		} );
+
+		it( 'should alter model.insertContent if mixed content is pasted (br + table)', () => {
+			tableSelection.setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+			);
+
+			const table = viewTable( [
+				[ 'aa', 'ab' ],
+				[ 'ba', 'bb' ]
+			] );
+
+			const data = {
+				dataTransfer: createDataTransfer(),
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy()
+			};
+			data.dataTransfer.setData( 'text/html', `<br>${ table }` );
+			viewDocument.fire( 'paste', data );
+
+			assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+				[ 'aa', 'ab', '02', '03' ],
+				[ 'ba', 'bb', '12', '13' ],
+				[ '20', '21', '22', '23' ],
+				[ '30', '31', '32', '33' ]
+			] ) );
+		} );
+
+		it( 'should alter model.insertContent if mixed content is pasted (p + p + table + p + br)', () => {
+			tableSelection.setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+			);
+
+			const table = viewTable( [
+				[ 'aa', 'ab' ],
+				[ 'ba', 'bb' ]
+			] );
+
+			const data = {
+				dataTransfer: createDataTransfer(),
+				preventDefault: sinon.spy(),
+				stopPropagation: sinon.spy()
+			};
+			data.dataTransfer.setData( 'text/html', `<p>&nbsp;</p><p>&nbsp;</p>${ table }<p>&nbsp;</p><br>` );
+			viewDocument.fire( 'paste', data );
+
+			assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+				[ 'aa', 'ab', '02', '03' ],
+				[ 'ba', 'bb', '12', '13' ],
+				[ '20', '21', '22', '23' ],
+				[ '30', '31', '32', '33' ]
+			] ) );
+		} );
+
+		it( 'should not alter model.insertContent if element other than a table is passed directly', () => {
+			tableSelection.setCellSelection(
+				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),
+				modelRoot.getNodeByPath( [ 0, 1, 1 ] )
+			);
+
+			model.change( writer => {
+				const element = writer.createElement( 'paragraph' );
+
+				writer.insertText( 'foo', element, 0 );
+				model.insertContent( element );
+			} );
+
+			assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
+				[ 'foo', '', '02', '03' ],
+				[ '', '', '12', '13' ],
+				[ '20', '21', '22', '23' ],
+				[ '30', '31', '32', '33' ]
+			] ) );
+		} );
+
 		it( 'should alter model.insertContent if selectable is a document selection', () => {
 			tableSelection.setCellSelection(
 				modelRoot.getNodeByPath( [ 0, 0, 0 ] ),