Skip to content

Commit

Permalink
Merge pull request #7381 from ckeditor/i/7379
Browse files Browse the repository at this point in the history
Other (table): Pasting a table into a table is more tolerant for whitespaces around a pasted table. Closes #7379.
  • Loading branch information
jodator authored Jun 5, 2020
2 parents d85bb62 + 1c371f1 commit 669d54f
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 8 deletions.
35 changes: 30 additions & 5 deletions packages/ckeditor5-table/src/tableclipboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -336,19 +336,44 @@ function expandTableSize( table, expectedHeight, expectedWidth, writer, tableUti
}
}

function getTableIfOnlyTableInContent( content ) {
function getTableIfOnlyTableInContent( content, model ) {
// Table passed directly.
if ( content.is( 'table' ) ) {
return 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.
Expand Down
165 changes: 162 additions & 3 deletions packages/ckeditor5-table/tests/tableclipboard-paste.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ describe( 'table clipboard', () => {

const table = viewTable( [
[ 'aa', 'ab' ],
[ 'ba', 'bb' ] ] );
[ 'ba', 'bb' ]
] );

const data = {
dataTransfer: createDataTransfer(),
Expand All @@ -204,7 +205,8 @@ describe( 'table clipboard', () => {

const table = viewTable( [
[ 'aa', 'ab' ],
[ 'ba', 'bb' ] ] );
[ 'ba', 'bb' ]
] );

const data = {
dataTransfer: createDataTransfer(),
Expand All @@ -230,7 +232,8 @@ describe( 'table clipboard', () => {

const table = viewTable( [
[ 'aa', 'ab' ],
[ 'ba', 'bb' ] ] );
[ 'ba', 'bb' ]
] );

const data = {
dataTransfer: createDataTransfer(),
Expand All @@ -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 ] ),
Expand Down

0 comments on commit 669d54f

Please sign in to comment.