浏览代码

Added: text/plain support for copy/cut.

Szymon Cofalik 8 年之前
父节点
当前提交
69fa635739

+ 2 - 0
packages/ckeditor5-clipboard/src/clipboard.js

@@ -13,6 +13,7 @@ import ClipboardObserver from './clipboardobserver';
 
 import plainTextToHtml from './utils/plaintexttohtml';
 import normalizeClipboardHtml from './utils/normalizeclipboarddata';
+import viewToPlainText from './utils/viewtoplaintext.js';
 
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
 
@@ -179,6 +180,7 @@ export default class Clipboard extends Plugin {
 		this.listenTo( editingView, 'clipboardOutput', ( evt, data ) => {
 			if ( !data.content.isEmpty ) {
 				data.dataTransfer.setData( 'text/html', this._htmlDataProcessor.toData( data.content ) );
+				data.dataTransfer.setData( 'text/plain', viewToPlainText( data.content ) );
 			}
 
 			if ( data.method == 'cut' ) {

+ 53 - 0
packages/ckeditor5-clipboard/src/utils/viewtoplaintext.js

@@ -0,0 +1,53 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module clipboard/utils/viewtoplaintext
+ */
+
+/**
+ * Deeply converts {@link module:engine/model/view/item view item} to plain text.
+ *
+ * @param {module:engine/model/view/item} viewItem View item to convert.
+ * @returns {String} Plain text representation of `viewItem`.
+ */
+export default function viewToPlainText( viewItem ) {
+	let text = '';
+
+	if ( viewItem.is( 'text' ) || viewItem.is( 'textProxy' ) ) {
+		// If item is `Text` or `TextProxy` simple take its text data.
+		text = viewItem.data;
+	} else if ( viewItem.is( 'img' ) && viewItem.hasAttribute( 'alt' ) ) {
+		// Special case for images - use alt attribute if it is provided.
+		text = viewItem.getAttribute( 'alt' );
+	} else {
+		// Other elements are document fragments, attribute elements or container elements.
+		// They don't have their own text value, so convert their children.
+		let prev = null;
+
+		for ( let child of viewItem.getChildren() ) {
+			const childText = viewToPlainText( child );
+
+			// Separate container element children with one or more new-line characters.
+			if ( prev && ( prev.is( 'containerElement' ) || child.is( 'containerElement' ) ) ) {
+				if ( smallPaddingElements.includes( prev.name ) || smallPaddingElements.includes( child.name ) ) {
+					text += '\n';
+				} else {
+					text += '\n\n';
+				}
+			}
+
+			text += childText;
+			prev = child;
+		}
+	}
+
+	return text;
+}
+
+// Elements which should not have empty-line padding.
+// Most `view.ContainerElement` want to be separate by new-line, but some are creating one structure
+// together (like `<li>`) so it is better to separate them by only one "\n".
+const smallPaddingElements = [ 'figcaption', 'li' ];

+ 90 - 5
packages/ckeditor5-clipboard/tests/clipboard.js

@@ -9,7 +9,10 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 
 import ClipboardObserver from '../src/clipboardobserver';
 
-import { stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import {
+	stringify as stringifyView,
+	parse as parseView
+} from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import {
 	stringify as stringifyModel,
 	setData as setModelData,
@@ -287,16 +290,98 @@ describe( 'Clipboard feature', () => {
 		it( 'sets clipboard HTML data', () => {
 			const dataTransferMock = createDataTransfer();
 
-			setModelData( editor.document, '<paragraph>f[o]o</paragraph>' );
+			const input =
+				'<blockquote>' +
+					'<p>foo</p>' +
+					'<p>bar</p>' +
+				'</blockquote>' +
+				'<ul>' +
+					'<li>u<strong>l ite</strong>m</li>' +
+					'<li>ul item</li>' +
+				'</ul>' +
+				'<p>foobar</p>' +
+				'<ol>' +
+					'<li>o<a href="foo">l ite</a>m</li>' +
+					'<li>ol item</li>' +
+				'</ol>' +
+				'<figure>' +
+					'<img src="foo.jpg" alt="image foo" />' +
+					'<figcaption>caption</figcaption>' +
+				'</figure>';
+
+			const output =
+				'<blockquote>' +
+					'<p>foo</p>' +
+					'<p>bar</p>' +
+				'</blockquote>' +
+				'<ul>' +
+					'<li>u<strong>l ite</strong>m</li>' +
+					'<li>ul item</li>' +
+				'</ul>' +
+				'<p>foobar</p>' +
+				'<ol>' +
+					'<li>o<a href="foo">l ite</a>m</li>' +
+					'<li>ol item</li>' +
+				'</ol>' +
+				'<figure>' +
+					'<img alt="image foo" src="foo.jpg">' + // Weird attributes ordering behavior + no closing "/>".
+					'<figcaption>caption</figcaption>' +
+				'</figure>';
 
 			editingView.fire( 'clipboardOutput', {
 				dataTransfer: dataTransferMock,
-				content: new ViewDocumentFragment( [ new ViewText( 'abc' ) ] ),
+				content: parseView( input ),
+				method: 'copy'
+			} );
+
+			expect( dataTransferMock.getData( 'text/html' ) ).to.equal( output );
+		} );
+
+		it( 'sets clipboard plain text data', () => {
+			const dataTransferMock = createDataTransfer();
+
+			const input =
+				'<container:blockquote>' +
+					'<container:p>foo</container:p>' +
+					'<container:p>bar</container:p>' +
+				'</container:blockquote>' +
+				'<container:ul>' +
+					'<container:li>u<strong>l ite</strong>m</container:li>' +
+					'<container:li>ul item</container:li>' +
+				'</container:ul>' +
+				'<container:p>foobar</container:p>' +
+				'<container:ol>' +
+					'<container:li>o<a href="foo">l ite</a>m</container:li>' +
+					'<container:li>ol item</container:li>' +
+				'</container:ol>' +
+				'<container:figure>' +
+					'<img alt="image foo" src="foo.jpg" />' +
+					'<container:figcaption>caption</container:figcaption>' +
+				'</container:figure>';
+
+			const output =
+				'foo\n' +
+				'\n' +
+				'bar\n' +
+				'\n' +
+				'ul item\n' +
+				'ul item\n' +
+				'\n' +
+				'foobar\n' +
+				'\n' +
+				'ol item\n' +
+				'ol item\n' +
+				'\n' +
+				'image foo\n' +
+				'caption';
+
+			editingView.fire( 'clipboardOutput', {
+				dataTransfer: dataTransferMock,
+				content: parseView( input ),
 				method: 'copy'
 			} );
 
-			expect( dataTransferMock.getData( 'text/html' ) ).to.equal( 'abc' );
-			expect( getModelData( editor.document ) ).to.equal( '<paragraph>f[o]o</paragraph>' );
+			expect( dataTransferMock.getData( 'text/plain' ) ).to.equal( output );
 		} );
 
 		it( 'does not set clipboard HTML data if content is empty', () => {

+ 2 - 1
packages/ckeditor5-clipboard/tests/manual/copycut.md

@@ -2,4 +2,5 @@
 
 Play with copy and cut. Paste copied content.
 
-Compare the results with the native editable. Don't expect that they behave identically.
+Compare the results with the native editable. Don't expect that they behave identically. Check plain text pasting
+(for example paste editor content to code editor).