浏览代码

Simplified implementation (CC: 100%).

Kamil Piechaczek 7 年之前
父节点
当前提交
b06c59f0ba
共有 2 个文件被更改,包括 101 次插入56 次删除
  1. 50 38
      packages/ckeditor5-media-embed/src/automediaembed.js
  2. 51 18
      packages/ckeditor5-media-embed/tests/automediaembed.js

+ 50 - 38
packages/ckeditor5-media-embed/src/automediaembed.js

@@ -45,7 +45,6 @@ export default class AutoMediaEmbed extends Plugin {
 	init() {
 		const editor = this.editor;
 		const modelDocument = editor.model.document;
-		const mediaRegistry = editor.plugins.get( MediaEmbedEditing ).registry;
 
 		let leftLivePosition, rightLivePosition, timeoutId;
 
@@ -59,6 +58,15 @@ export default class AutoMediaEmbed extends Plugin {
 
 			rightLivePosition = LivePosition.createFromPosition( firstRange.end );
 			rightLivePosition.stickiness = 'toNext';
+
+			modelDocument.once( 'change:data', () => {
+				this._autoEmbedingEventHandler( leftLivePosition, rightLivePosition )
+					.then( _timeoutId => {
+						timeoutId = _timeoutId;
+						leftLivePosition = null;
+						rightLivePosition = null;
+					} );
+			}, { priority: 'high' } );
 		} );
 
 		editor.commands.get( 'undo' ).on( 'execute', () => {
@@ -67,55 +75,59 @@ export default class AutoMediaEmbed extends Plugin {
 				timeoutId = null;
 			}
 		}, { priority: 'high' } );
+	}
 
-		modelDocument.on( 'change:data', () => {
-			if ( !leftLivePosition ) {
-				return;
-			}
-
-			const urlRange = new LiveRange( leftLivePosition, rightLivePosition );
-			const walker = new TreeWalker( { boundaries: urlRange, ignoreElementEnd: true } );
+	/**
+	 * A handler that replaces the pasted URL with `<media>` element.
+	 *
+	 * @private
+	 * @param {module:engine/model/liveposition} leftPosition Left position of the selection.
+	 * @param {module:engine/model/liveposition} rightPosition Right position of the selection.
+	 * @returns {Promise}
+	 */
+	_autoEmbedingEventHandler( leftPosition, rightPosition ) {
+		const editor = this.editor;
+		const mediaRegistry = editor.plugins.get( MediaEmbedEditing ).registry;
+		const urlRange = new LiveRange( leftPosition, rightPosition );
+		const walker = new TreeWalker( { boundaries: urlRange, ignoreElementEnd: true } );
 
-			let url = '';
+		let url = '';
 
-			for ( const node of walker ) {
-				if ( node.type === 'elementStart' ) {
-					return detach();
-				}
+		for ( const node of walker ) {
+			url += node.item.data;
+		}
 
-				url += node.item.data;
-			}
+		url = url.trim();
 
-			url = url.trim();
+		// If the url does not match to universal url regexp, let's skip that.
+		if ( !url.match( URL_REGEXP ) ) {
+			return detach();
+		}
 
-			// If the url does not match to universal url regexp, let's skip that.
-			if ( !url.match( URL_REGEXP ) ) {
-				return detach();
-			}
+		// If the url is valid from MediaEmbed plugin point of view, let's use it.
+		if ( !mediaRegistry.hasMedia( url ) ) {
+			return detach();
+		}
 
-			// If the url is valid from MediaEmbed plugin point of view, let's use it.
-			if ( !mediaRegistry.hasMedia( url ) ) {
-				return detach();
-			}
+		// Positions won't be available in `setTimeout` function so let's clone it.
+		const positionToInsert = Position.createFromPosition( leftPosition );
 
-			// `leftLivePosition` won't be available in `setTimeout` function so let's clone it.
-			const positionToInsert = Position.createFromPosition( leftLivePosition );
+		// This action mustn't be executed if undo was called between pasting and ąuto-embeding.
+		const timeoutId = global.window.setTimeout( () => {
+			editor.model.change( writer => {
+				writer.remove( urlRange );
+				writer.setSelection( positionToInsert );
+				editor.commands.execute( 'mediaEmbed', url );
+			} );
+		}, 100 );
 
-			timeoutId = global.window.setTimeout( () => {
-				editor.model.change( writer => {
-					writer.remove( urlRange );
-					writer.setSelection( positionToInsert );
-					editor.commands.execute( 'mediaEmbed', url );
-				} );
-			}, 100 );
-		} );
+		return detach().then( () => timeoutId );
 
 		function detach() {
-			leftLivePosition.detach();
-			rightLivePosition.detach();
+			leftPosition.detach();
+			rightPosition.detach();
 
-			leftLivePosition = null;
-			rightLivePosition = null;
+			return Promise.resolve();
 		}
 	}
 }

+ 51 - 18
packages/ckeditor5-media-embed/tests/automediaembed.js

@@ -3,6 +3,8 @@
  * For licensing, see LICENSE.md.
  */
 
+/* global setTimeout */
+
 import MediaEmbed from '../src/mediaembed';
 import AutoMediaEmbed from '../src/automediaembed';
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
@@ -11,7 +13,6 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Link from '@ckeditor/ckeditor5-link/src/link';
 import List from '@ckeditor/ckeditor5-list/src/list';
 import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
-import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
 import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
@@ -24,7 +25,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 
 		return ClassicTestEditor
 			.create( editorElement, {
-				plugins: [ MediaEmbed, AutoMediaEmbed, Link, List, Bold, Undo ]
+				plugins: [ MediaEmbed, AutoMediaEmbed, Link, List, Bold ]
 			} )
 			.then( newEditor => {
 				editor = newEditor;
@@ -45,7 +46,7 @@ describe( 'AutoMediaEmbed - integration', () => {
 		expect( AutoMediaEmbed.pluginName ).to.equal( 'AutoMediaEmbed' );
 	} );
 
-	describe( 'auto-media embed', () => {
+	describe( 'use fake timers', () => {
 		let clock;
 
 		beforeEach( () => {
@@ -258,7 +259,18 @@ describe( 'AutoMediaEmbed - integration', () => {
 			);
 		} );
 
-		it( 'does nothing if pastes a block of content that looks like a URL', () => {
+		it( 'does nothing if pasted a paragraph with the url', () => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, '<p>https://www.youtube.com/watch?v=H08tGjXNHO4</p>' );
+
+			clock.tick( 100 );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>https://www.youtube.com/watch?v=H08tGjXNHO4[]</paragraph>'
+			);
+		} );
+
+		it( 'does nothing if pasted a block of content that looks like a URL', () => {
 			setData( editor.model, '<paragraph>[]</paragraph>' );
 			pasteHtml( editor, '<ul><li>https://</li><li>youtube.com/watch?</li></ul><p>v=H08tGjXNHO4</p>' );
 
@@ -307,21 +319,42 @@ describe( 'AutoMediaEmbed - integration', () => {
 					return editor.destroy();
 				} );
 		} );
+	} );
 
-		function pasteHtml( editor, html ) {
-			editor.editing.view.document.fire( 'paste', {
-				dataTransfer: createDataTransfer( { 'text/html': html } ),
-				preventDefault() {
-				}
+	describe( 'real timers', () => {
+		it( 'undo breaks the auto-media embed feature', done => {
+			setData( editor.model, '<paragraph>[]</paragraph>' );
+			pasteHtml( editor, 'https://www.youtube.com/watch?v=H08tGjXNHO4' );
+
+			expect( getData( editor.model ) ).to.equal(
+				'<paragraph>https://www.youtube.com/watch?v=H08tGjXNHO4[]</paragraph>'
+			);
+
+			setTimeout( () => {
+				editor.commands.execute( 'undo' );
+
+				expect( getData( editor.model ) ).to.equal(
+					'<paragraph>[]</paragraph>'
+				);
+
+				done();
 			} );
-		}
-
-		function createDataTransfer( data ) {
-			return {
-				getData( type ) {
-					return data[ type ];
-				}
-			};
-		}
+		} );
 	} );
+
+	function pasteHtml( editor, html ) {
+		editor.editing.view.document.fire( 'paste', {
+			dataTransfer: createDataTransfer( { 'text/html': html } ),
+			preventDefault() {
+			}
+		} );
+	}
+
+	function createDataTransfer( data ) {
+		return {
+			getData( type ) {
+				return data[ type ];
+			}
+		};
+	}
 } );