浏览代码

Text transformation should only be triggered on typing.

Maciej Gołaszewski 6 年之前
父节点
当前提交
18a1fbdc18

+ 4 - 0
packages/ckeditor5-typing/src/texttransformation.js

@@ -111,6 +111,10 @@ export default class TextTransformation extends Plugin {
 			const watcher = new TextWatcher( editor.model, text => from.test( text ) );
 
 			watcher.on( 'matched:data', ( evt, data ) => {
+				if ( !data.isTyping ) {
+					return;
+				}
+
 				const matches = from.exec( data.text );
 				const replaces = to( matches.slice( 1 ) );
 

+ 28 - 4
packages/ckeditor5-typing/src/textwatcher.js

@@ -66,7 +66,9 @@ export default class TextWatcher {
 				return;
 			}
 
-			this._evaluateTextBeforeSelection( 'data' );
+			const isTyping = checkTyping( model );
+
+			this._evaluateTextBeforeSelection( 'data', { isTyping } );
 		} );
 	}
 
@@ -79,8 +81,9 @@ export default class TextWatcher {
 	 *
 	 * @private
 	 * @param {'data'|'selection'} suffix Suffix used for generating event name.
+	 * @param {Object} data Data object for event.
 	 */
-	_evaluateTextBeforeSelection( suffix ) {
+	_evaluateTextBeforeSelection( suffix, data = {} ) {
 		const text = this._getText();
 
 		const textHasMatch = this.testCallback( text );
@@ -97,18 +100,22 @@ export default class TextWatcher {
 		this.hasMatch = textHasMatch;
 
 		if ( textHasMatch ) {
+			const eventData = Object.assign( data, { text } );
+
 			/**
 			 * Fired whenever the text watcher found a match for data changes.
 			 *
 			 * @event matched:data
+			 * @param {String} text The full text before selection.
+			 * @param {Boolean} isTyping Set to true for user typing changes.
 			 */
-
 			/**
 			 * Fired whenever the text watcher found a match for selection changes.
 			 *
 			 * @event matched:selection
+			 * @param {String} text The full text before selection.
 			 */
-			this.fire( `matched:${ suffix }`, { text } );
+			this.fire( `matched:${ suffix }`, eventData );
 		}
 	}
 
@@ -144,5 +151,22 @@ function _getText( range ) {
 	}, '' );
 }
 
+// Returns true if the change was done by typing.
+//
+// @param {module:engine/model/model~Model} model
+function checkTyping( model ) {
+	const changes = Array.from( model.document.differ.getChanges() );
+
+	// Typing is represented by only a single change...
+	if ( changes.length != 1 ) {
+		return false;
+	}
+
+	const entry = changes[ 0 ];
+
+	// ...and is an insert of a text.
+	return entry.type === 'insert' && entry.name == '$text' && entry.length == 1;
+}
+
 mix( TextWatcher, EmitterMixin );
 

+ 49 - 49
packages/ckeditor5-typing/tests/texttransformation.js

@@ -52,6 +52,30 @@ describe( 'Text transformation feature', () => {
 			expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>foo bar(tm) baz</paragraph>' );
 		} );
 
+		it( 'should not work for deletion changes', () => {
+			setData( model, '<paragraph>foo bar(tm) []</paragraph>' );
+
+			// Simulate delete command.
+			model.change( writer => {
+				const selection = writer.createSelection( doc.selection );
+				model.modifySelection( selection, { direction: 'backward', unit: 'character' } );
+				model.deleteContent( selection, { doNotResetEntireContent: true } );
+			} );
+
+			expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>foo bar(tm)</paragraph>' );
+		} );
+
+		it( 'should not work for merging changes', () => {
+			setData( model, '<paragraph>foo bar(tm)</paragraph><paragraph>[] baz</paragraph>' );
+
+			// Simulate delete command.
+			model.change( writer => {
+				writer.merge( writer.createPositionAfter( doc.getRoot().getChild( 0 ) ) );
+			} );
+
+			expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>foo bar(tm) baz</paragraph>' );
+		} );
+
 		describe( 'symbols', () => {
 			testTransformation( '(c)', '©' );
 			testTransformation( '(r)', '®' );
@@ -88,9 +112,7 @@ describe( 'Text transformation feature', () => {
 		it( 'should replace only the parts of content which changed', () => {
 			setData( model, '<paragraph>Foo "<$text bold="true">Bar</$text>[]</paragraph>' );
 
-			model.change( writer => {
-				writer.insertText( '"', doc.selection.focus );
-			} );
+			simulateTyping( '"' );
 
 			expect( getData( model, { withoutSelection: true } ) )
 				.to.equal( '<paragraph>Foo “<$text bold="true">Bar</$text>”</paragraph>' );
@@ -110,9 +132,7 @@ describe( 'Text transformation feature', () => {
 		it( 'should keep styles of the replaced text #2', () => {
 			setData( model, '<paragraph>F<$text bold="true">oo "B</$text>ar[]</paragraph>' );
 
-			model.change( writer => {
-				writer.insertText( '"', doc.selection.focus );
-			} );
+			simulateTyping( '"' );
 
 			expect( getData( model, { withoutSelection: true } ) )
 				.to.equal( '<paragraph>F<$text bold="true">oo “B</$text>ar”</paragraph>' );
@@ -122,13 +142,7 @@ describe( 'Text transformation feature', () => {
 			it( `should transform "${ transformFrom }" to "${ transformTo }"`, () => {
 				setData( model, '<paragraph>A foo[]</paragraph>' );
 
-				const letters = transformFrom.split( '' );
-
-				for ( const letter of letters ) {
-					model.enqueueChange( model.createBatch(), writer => {
-						writer.insertText( letter, doc.selection.focus );
-					} );
-				}
+				simulateTyping( transformFrom );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( `<paragraph>A foo${ transformTo }</paragraph>` );
 			} );
@@ -164,9 +178,7 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>[]</paragraph>' );
 
-				model.enqueueChange( model.createBatch(), writer => {
-					writer.insertText( 'CKE', doc.selection.focus );
-				} );
+				simulateTyping( 'CKE' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>CKEditor</paragraph>' );
 			} );
@@ -184,9 +196,7 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>[]</paragraph>' );
 
-				model.enqueueChange( model.createBatch(), writer => {
-					writer.insertText( 'user@example.com', doc.selection.focus );
-				} );
+				simulateTyping( 'user@example.com' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>user.at.example.com</paragraph>' );
 			} );
@@ -204,9 +214,7 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>Foo. []</paragraph>' );
 
-				model.enqueueChange( model.createBatch(), writer => {
-					writer.insertText( 'b', doc.selection.focus );
-				} );
+				simulateTyping( 'b' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>Foo. B</paragraph>' );
 			} );
@@ -224,15 +232,11 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>[]</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( 'CKE', doc.selection.focus );
-				} );
+				simulateTyping( 'CKE' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>CKEditor</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '(tm)', doc.selection.focus );
-				} );
+				simulateTyping( '(tm)' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>CKEditor™</paragraph>' );
 			} );
@@ -250,15 +254,11 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>[]</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( 'CKE', doc.selection.focus );
-				} );
+				simulateTyping( 'CKE' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>CKEditor</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '(tm)', doc.selection.focus );
-				} );
+				simulateTyping( '(tm)' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>CKEditor(tm)</paragraph>' );
 			} );
@@ -275,15 +275,11 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>[]</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '(tm)', doc.selection.focus );
-				} );
+				simulateTyping( '(tm)' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>(tm)</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '(r)', doc.selection.focus );
-				} );
+				simulateTyping( '(r)' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>(tm)®</paragraph>' );
 			} );
@@ -300,15 +296,11 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>[]</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '(tm)', doc.selection.focus );
-				} );
+				simulateTyping( '(tm)' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>(tm)</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '...', doc.selection.focus );
-				} );
+				simulateTyping( '...' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>(tm)…</paragraph>' );
 			} );
@@ -334,9 +326,7 @@ describe( 'Text transformation feature', () => {
 			} ).then( () => {
 				setData( model, '<paragraph>[]</paragraph>' );
 
-				model.change( writer => {
-					writer.insertText( '(tm)', doc.selection.focus );
-				} );
+				simulateTyping( '(tm)' );
 
 				expect( getData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>™</paragraph>' );
 			} );
@@ -355,4 +345,14 @@ describe( 'Text transformation feature', () => {
 				doc = model.document;
 			} );
 	}
+
+	function simulateTyping( transformFrom ) {
+		const letters = transformFrom.split( '' );
+
+		for ( const letter of letters ) {
+			model.enqueueChange( model.createBatch(), writer => {
+				writer.insertText( letter, doc.selection.focus );
+			} );
+		}
+	}
 } );