Explorar el Código

Use range from text watcher event to properly alter matched text.

In the previous check we didn't account that <softBreak> or other inline
elements were excluded from match. Also the matched text was trimmed
to the last inline node.
Maciej Gołaszewski hace 6 años
padre
commit
9ed03f8f17

+ 2 - 3
packages/ckeditor5-typing/src/texttransformation.js

@@ -118,8 +118,7 @@ export default class TextTransformation extends Plugin {
 				const matches = from.exec( data.text );
 				const replaces = to( matches.slice( 1 ) );
 
-				// Used `focus` to be in line with `TextWatcher#_getText()`.
-				const selectionParent = editor.model.document.selection.focus.parent;
+				const matchedRange = data.range;
 
 				let changeIndex = matches.index;
 
@@ -134,7 +133,7 @@ export default class TextTransformation extends Plugin {
 							continue;
 						}
 
-						const replacePosition = model.createPositionAt( selectionParent, changeIndex );
+						const replacePosition = matchedRange.start.getShiftedBy( changeIndex );
 						const replaceRange = model.createRange( replacePosition, replacePosition.getShiftedBy( match.length ) );
 						const attributes = getTextAttributesAfterPosition( replacePosition );
 

+ 14 - 8
packages/ckeditor5-typing/src/textwatcher.js

@@ -82,7 +82,7 @@ export default class TextWatcher {
 	 * @param {Object} data Data object for event.
 	 */
 	_evaluateTextBeforeSelection( suffix, data = {} ) {
-		const text = this._getText();
+		const { text, range } = this._getText();
 
 		const textHasMatch = this.testCallback( text );
 
@@ -98,7 +98,7 @@ export default class TextWatcher {
 		this.hasMatch = textHasMatch;
 
 		if ( textHasMatch ) {
-			const eventData = Object.assign( data, { text } );
+			const eventData = Object.assign( data, { text, range } );
 
 			/**
 			 * Fired whenever the text watcher found a match for data changes.
@@ -122,7 +122,7 @@ export default class TextWatcher {
 	/**
 	 * Returns the text before the caret from the current selection block.
 	 *
-	 * @returns {String|undefined} The text from the block or undefined if the selection is not collapsed.
+	 * @returns {Object} The text from the block or undefined if the selection is not collapsed.
 	 * @private
 	 */
 	_getText() {
@@ -132,23 +132,29 @@ export default class TextWatcher {
 
 		const rangeBeforeSelection = model.createRange( model.createPositionAt( selection.focus.parent, 0 ), selection.focus );
 
-		return _getText( rangeBeforeSelection );
+		return _getText( rangeBeforeSelection, model );
 	}
 }
 
 // Returns the whole text from a given range by adding all data from the text nodes together.
 //
 // @param {module:engine/model/range~Range} range
-// @returns {String}
-function _getText( range ) {
-	return Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
+// @returns {Object}
+function _getText( range, model ) {
+	let start = range.start;
+
+	const text = Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
 		if ( node.is( 'softBreak' ) ) {
-			// Trim text to a softBreak.
+			// Trim text to a <softBreak> and update range start.
+			start = model.createPositionAfter( node );
+
 			return '';
 		}
 
 		return rangeText + node.data;
 	}, '' );
+
+	return { text, range: model.createRange( start, range.end ) };
 }
 
 mix( TextWatcher, EmitterMixin );

+ 19 - 0
packages/ckeditor5-typing/tests/texttransformation.js

@@ -103,6 +103,7 @@ describe( 'Text transformation feature', () => {
 					testTransformation( '\' foo "bar"', '\' foo “bar”' );
 					testTransformation( 'Foo "Bar bar\'s it\'s a baz"', 'Foo “Bar bar\'s it\'s a baz”' );
 					testTransformation( ' ""', ' “”' );
+					testTransformation( ' "Bar baz"', ' “Bar baz”', '"A foo<softBreak></softBreak>' );
 				} );
 
 				describe( 'secondary', () => {
@@ -145,6 +146,15 @@ describe( 'Text transformation feature', () => {
 				.to.equal( '<paragraph>F<$text bold="true">oo “B</$text>ar”</paragraph>' );
 		} );
 
+		it( 'should work with soft breaks in parent', () => {
+			setData( model, '<paragraph>"Foo <softBreak></softBreak>"Bar[]</paragraph>' );
+
+			simulateTyping( '"' );
+
+			expect( getData( model, { withoutSelection: true } ) )
+			.to.equal( '<paragraph>"Foo <softBreak></softBreak>“Bar”</paragraph>' );
+		} );
+
 		function testTransformation( transformFrom, transformTo, textInParagraph = 'A foo' ) {
 			it( `should transform "${ transformFrom }" to "${ transformTo }"`, () => {
 				setData( model, `<paragraph>${ textInParagraph }[]</paragraph>` );
@@ -352,6 +362,15 @@ describe( 'Text transformation feature', () => {
 
 				model = editor.model;
 				doc = model.document;
+
+				model.schema.register( 'softBreak', {
+					allowWhere: '$text',
+					isInline: true
+				} );
+				editor.conversion.elementToElement( {
+					model: 'softBreak',
+					view: 'br'
+				} );
 			} );
 	}