Kaynağa Gözat

Merge pull request #7778 from ckeditor/i/1006

Feature (clipboard): Pasting a plain text will inherit selection attributes. Closes #1006.
Maciej 5 yıl önce
ebeveyn
işleme
2a163e389a

+ 23 - 0
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;
+}

+ 110 - 0
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, '<paragraph><$text bold="true">Bolded []text.</$text></paragraph>' );
+
+				const dataTransferMock = createDataTransfer( { 'text/plain': 'foo' } );
+
+				viewDocument.fire( 'paste', {
+					dataTransfer: dataTransferMock,
+					stopPropagation() {},
+					preventDefault() {}
+				} );
+
+				expect( getModelData( model ) ).to.equal( '<paragraph><$text bold="true">Bolded foo[]text.</$text></paragraph>' );
+				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, '<paragraph><$text bold="true">Bolded [text.]</$text></paragraph>' );
+
+				const dataTransferMock = createDataTransfer( { 'text/plain': 'foo' } );
+
+				viewDocument.fire( 'paste', {
+					dataTransfer: dataTransferMock,
+					stopPropagation() {},
+					preventDefault() {}
+				} );
+
+				expect( getModelData( model ) ).to.equal( '<paragraph><$text bold="true">Bolded foo[]</$text></paragraph>' );
+				expect( insertedNode.getAttribute( 'bold' ) ).to.equal( true );
+			} );
+
+			it( 'should inherit selection attributes while pasting a plain text as text/html', () => {
+				setModelData( model, '<paragraph><$text bold="true">Bolded []text.</$text></paragraph>' );
+
+				const dataTransferMock = createDataTransfer( {
+					'text/html': 'foo',
+					'text/plain': 'foo'
+				} );
+
+				viewDocument.fire( 'paste', {
+					dataTransfer: dataTransferMock,
+					stopPropagation() {},
+					preventDefault() {}
+				} );
+
+				expect( getModelData( model ) ).to.equal( '<paragraph><$text bold="true">Bolded foo[]text.</$text></paragraph>' );
+			} );
+
+			it( 'should inherit selection attributes while pasting a plain text as text/html (Chrome style)', () => {
+				setModelData( model, '<paragraph><$text bold="true">Bolded []text.</$text></paragraph>' );
+
+				const dataTransferMock = createDataTransfer( {
+					'text/html': '<meta http-equiv="content-type" content="text/html; charset=utf-8">foo',
+					'text/plain': 'foo'
+				} );
+
+				viewDocument.fire( 'paste', {
+					dataTransfer: dataTransferMock,
+					stopPropagation() {},
+					preventDefault() {}
+				} );
+
+				expect( getModelData( model ) ).to.equal( '<paragraph><$text bold="true">Bolded foo[]text.</$text></paragraph>' );
+			} );
+
+			it( 'should inherit selection attributes while pasting HTML with unsupported attributes', () => {
+				setModelData( model, '<paragraph><$text bold="true">Bolded []text.</$text></paragraph>' );
+
+				const dataTransferMock = createDataTransfer( {
+					'text/html': '<i>foo</i>',
+					'text/plain': 'foo'
+				} );
+
+				viewDocument.fire( 'paste', {
+					dataTransfer: dataTransferMock,
+					stopPropagation() {},
+					preventDefault() {}
+				} );
+
+				expect( getModelData( model ) ).to.equal( '<paragraph><$text bold="true">Bolded foo[]text.</$text></paragraph>' );
+			} );
+		} );
+
 		function createDataTransfer( data ) {
 			return {
 				getData( type ) {