diff --git a/packages/ckeditor5-clipboard/src/clipboard.js b/packages/ckeditor5-clipboard/src/clipboard.js index 5d612ffbc7c..8f3b9984422 100644 --- a/packages/ckeditor5-clipboard/src/clipboard.js +++ b/packages/ckeditor5-clipboard/src/clipboard.js @@ -103,6 +103,15 @@ export default class Clipboard extends Plugin { return; } + // While pasting plain text, apply selection attributes on the text. + if ( isPlainText( modelFragment ) ) { + const node = modelFragment.getChild( 0 ); + + model.change( writer => { + writer.setAttributes( modelDocument.selection.getAttributes(), node ); + } ); + } + model.insertContent( modelFragment ); evt.stop(); } @@ -198,3 +207,17 @@ export default class Clipboard extends Plugin { * * @member {'copy'|'cut'} module:clipboard/clipboard~ClipboardOutputEventData#method */ + +// Returns true if specified `documentFragment` represents a plain text. +// +// @param {module:engine/view/documentfragment~DocumentFragment} documentFragment +// @returns {Boolean} +function isPlainText( documentFragment ) { + if ( documentFragment.childCount > 1 ) { + return false; + } + + const child = documentFragment.getChild( 0 ); + + return [ ...child.getAttributeKeys() ].length == 0; +} diff --git a/packages/ckeditor5-clipboard/tests/clipboard.js b/packages/ckeditor5-clipboard/tests/clipboard.js index 84f4fde509f..3ecc18eee7a 100644 --- a/packages/ckeditor5-clipboard/tests/clipboard.js +++ b/packages/ckeditor5-clipboard/tests/clipboard.js @@ -343,6 +343,116 @@ describe( 'Clipboard feature', () => { expect( spy.callCount ).to.equal( 1 ); } ); + // https://github.com/ckeditor/ckeditor5/issues/1006 + describe( 'pasting plain text', () => { + let model; + + beforeEach( () => { + model = editor.model; + + model.schema.extend( '$text', { allowAttributes: 'bold' } ); + } ); + + it( 'should inherit selection attributes (collapsed selection)', () => { + const insertContent = model.insertContent.bind( model ); + let insertedNode; + + sinon.stub( model, 'insertContent' ).callsFake( documentFragment => { + insertedNode = documentFragment.getChild( 0 ); + + return insertContent( documentFragment ); + } ); + + setModelData( model, '<$text bold="true">Bolded []text.' ); + + const dataTransferMock = createDataTransfer( { 'text/plain': 'foo' } ); + + viewDocument.fire( 'paste', { + dataTransfer: dataTransferMock, + stopPropagation() {}, + preventDefault() {} + } ); + + expect( getModelData( model ) ).to.equal( '<$text bold="true">Bolded foo[]text.' ); + expect( insertedNode.getAttribute( 'bold' ) ).to.equal( true ); + } ); + + it( 'should inherit selection attributes (non-collapsed selection)', () => { + const insertContent = model.insertContent.bind( model ); + let insertedNode; + + sinon.stub( model, 'insertContent' ).callsFake( documentFragment => { + insertedNode = documentFragment.getChild( 0 ); + + return insertContent( documentFragment ); + } ); + + setModelData( model, '<$text bold="true">Bolded [text.]' ); + + const dataTransferMock = createDataTransfer( { 'text/plain': 'foo' } ); + + viewDocument.fire( 'paste', { + dataTransfer: dataTransferMock, + stopPropagation() {}, + preventDefault() {} + } ); + + expect( getModelData( model ) ).to.equal( '<$text bold="true">Bolded foo[]' ); + expect( insertedNode.getAttribute( 'bold' ) ).to.equal( true ); + } ); + + it( 'should inherit selection attributes while pasting a plain text as text/html', () => { + setModelData( model, '<$text bold="true">Bolded []text.' ); + + const dataTransferMock = createDataTransfer( { + 'text/html': 'foo', + 'text/plain': 'foo' + } ); + + viewDocument.fire( 'paste', { + dataTransfer: dataTransferMock, + stopPropagation() {}, + preventDefault() {} + } ); + + expect( getModelData( model ) ).to.equal( '<$text bold="true">Bolded foo[]text.' ); + } ); + + it( 'should inherit selection attributes while pasting a plain text as text/html (Chrome style)', () => { + setModelData( model, '<$text bold="true">Bolded []text.' ); + + const dataTransferMock = createDataTransfer( { + 'text/html': 'foo', + 'text/plain': 'foo' + } ); + + viewDocument.fire( 'paste', { + dataTransfer: dataTransferMock, + stopPropagation() {}, + preventDefault() {} + } ); + + expect( getModelData( model ) ).to.equal( '<$text bold="true">Bolded foo[]text.' ); + } ); + + it( 'should inherit selection attributes while pasting HTML with unsupported attributes', () => { + setModelData( model, '<$text bold="true">Bolded []text.' ); + + const dataTransferMock = createDataTransfer( { + 'text/html': 'foo', + 'text/plain': 'foo' + } ); + + viewDocument.fire( 'paste', { + dataTransfer: dataTransferMock, + stopPropagation() {}, + preventDefault() {} + } ); + + expect( getModelData( model ) ).to.equal( '<$text bold="true">Bolded foo[]text.' ); + } ); + } ); + function createDataTransfer( data ) { return { getData( type ) {