8
0
Просмотр исходного кода

Added short version of inline formatting, updated comments, marked problematic place.

Maksymilian Barnaś 9 лет назад
Родитель
Сommit
416b625799

+ 29 - 44
packages/ckeditor5-autoformat/src/autoformat.js

@@ -38,7 +38,7 @@ export default class Autoformat extends Feature {
 	}
 
 	/**
-	 * Add autoformats related to ListEngine commands.
+	 * Adds autoformats related to ListEngine commands.
 	 *
 	 * When typed:
 	 *
@@ -56,7 +56,7 @@ export default class Autoformat extends Feature {
 	}
 
 	/**
-	 * Add autoformats related to HeadingEngine commands.
+	 * Adds autoformats related to HeadingEngine commands.
 	 *
 	 * When typed:
 	 *
@@ -77,43 +77,25 @@ export default class Autoformat extends Feature {
 		} );
 	}
 
+	/**
+	 * Adds inline autoformatting capabilities to the editor.
+	 *
+	 * When typed:
+	 *
+	 *	`**foobar**`
+	 *		The `**` characters are removed, and `foobar` is set to bold.
+	 *	`*foobar*`
+	 *		The `*` characters are removed, and `foobar` is set to italic.
+	 *
+	 * @private
+	 */
 	_addInlineAutoformats() {
-		// Bold autoformat.
-		new InlineAutoformatEngine(
-			this.editor,
-			( text ) => {
-				const pattern = /\*\*(.+?)\*\*/g;
-
-				let result;
-				let remove = [];
-				let format = [];
-
-				while ( ( result = pattern.exec( text ) ) !== null ) {
-					const start = result.index;
-					const fullMatchLen = result[ 0 ].length;
-					const delimiterLen = 2; // Length of '**'.
+		// Bold text between `**`, e.g. `**text to bold**`.
+		new InlineAutoformatEngine( this.editor, /(\*\*)(.+?)(\*\*)/g, 'bold' );
 
-					const delStart = [ start,                               start + delimiterLen ];
-					const delEnd =   [ start + fullMatchLen - delimiterLen, start + fullMatchLen ];
-
-					remove.push( delStart );
-					remove.push( delEnd );
-
-					// Calculation of offsets after text deletion is not needed.
-					format.push( [ start, start + fullMatchLen - delimiterLen ] );
-				}
-
-				return {
-					remove,
-					format
-				};
-			},
-			( editor, range, batch ) => {
-				this.editor.execute( 'bold', { ranges: [ range ], batch } );
-			}
-		);
-
-		// Italic autoformat.
+		// Italicize text between `*`, e.g. `*text to italicize*`.
+		// Slightly more complicated because if the clashing with the Bold autoformat.
+		// Won't work for text shorter than 3 characters.
 		new InlineAutoformatEngine(
 			this.editor,
 			( text ) => {
@@ -138,14 +120,20 @@ export default class Autoformat extends Feature {
 					const fullMatchLen = result[ 2 ].length;
 					const delimiterLen = 1; // Length of '*'.
 
-					const delStart = [ start,                               start + delimiterLen ];
-					const delEnd =   [ start + fullMatchLen - delimiterLen, start + fullMatchLen ];
+					const delStart = [
+						start,
+						start + delimiterLen
+					];
+					const delEnd = [
+						start + fullMatchLen - delimiterLen,
+						start + fullMatchLen
+					];
 
 					remove.push( delStart );
 					remove.push( delEnd );
 
 					// Calculation of offsets after deletion is not needed.
-					format.push( [ start, start + fullMatchLen - delimiterLen ] );
+					format.push( [ start + delimiterLen, start + fullMatchLen - delimiterLen ] );
 				}
 
 				return {
@@ -154,11 +142,8 @@ export default class Autoformat extends Feature {
 				};
 			},
 			( editor, range, batch ) => {
-				this.editor.execute( 'italic', { batch } );
+				this.editor.execute( 'italic', { batch, forceValue: true } );
 			}
 		);
-
-		// 3 capture groups: (remove)(format)(remove).
-		// new InlineAutoformatEngine( this.editor, /(\*\*.+?\*\*)/g, 'bold' );
 	}
 }

+ 72 - 20
packages/ckeditor5-autoformat/src/inlineautoformatengine.js

@@ -5,6 +5,7 @@
 
 import Range from '../engine/model/range.js';
 import CKEditorError from '../utils/ckeditorerror.js';
+
 /**
  * A paragraph feature for editor.
  * Introduces `<paragraph>` element in the model which renders as `<p>` in the DOM and data.
@@ -14,6 +15,34 @@ import CKEditorError from '../utils/ckeditorerror.js';
  */
 export default class InlineAutoformatEngine {
 
+	/**
+	 * Assigns to `editor` to watch for pattern (either by executing that pattern or passing the text to `testCallbackOrPattern` callback).
+	 * It formats found text by executing command `formatCallbackOrCommand` or by running `formatCallbackOrCommand` format callback.
+	 *
+	 * @param {core.editor.Editor} editor Editor instance.
+	 * @param {Function|RegExp} testCallbackOrPattern RegExp literal to execute on text or test callback returning Object with offsets to
+	 * remove and offsets to format.
+	 *  * Format is applied before deletion,
+	 *	* RegExp literal *must* have 3 capture groups.
+	 *
+	 * Example of object that should be returned from test callback.
+	 *
+	 *	{
+	 *		remove: [
+	 *			[ 0, 1 ],
+	 *			[ 5, 6 ]
+	 *		],
+	 *		format: [
+	 *			[ 1, 5 ]
+	 *		],
+	 *	}
+	 *
+	 * @param {Function|String} formatCallbackOrCommand Name of command to execute on matched text or format callback.
+	 * Format callback gets following parameters:
+	 *  * {core.editor.Editor} Editor instance,
+	 *  * {engine.model.Range} Range of matched text to format,
+	 *  * {engine.model.Batch} Batch to group format operations.
+	 */
 	constructor( editor, testCallbackOrPattern, formatCallbackOrCommand ) {
 		this.editor = editor;
 
@@ -36,37 +65,57 @@ export default class InlineAutoformatEngine {
 			formatClb = formatCallbackOrCommand;
 		}
 
+		// Callback to run on changed text.
 		testClb = testClb || ( ( text ) => {
 			let result;
 			let remove = [];
 			let format = [];
 
-			// First run.
-			result = pattern.exec( text );
-
-			// There should be full match and 3 capture groups.
-			if ( result.length < 4 ) {
-				throw new CKEditorError( 'inlineautoformat-missing-capture-groups: Less than 3 capture groups in regular expression.' );
+			if ( !text ) {
+				return;
 			}
 
-			do {
+			while ( ( result = pattern.exec( text ) ) !== null ) {
+				// If nothing matched, stop early.
+				if ( !result ) {
+					return;
+				}
+
+				// There should be full match and 3 capture groups.
+				if ( result && result.length < 4 ) {
+					throw new CKEditorError( 'inlineautoformat-missing-capture-groups: Less than 3 capture groups in regular expression.' );
+				}
+
 				const {
-					index: start,
-					'1': leftDelimiter,
+					index,
+					'1': leftDel,
 					'2': content,
-					'3': rightDelimiter
+					'3': rightDel
 				} = result;
 
-				const delStart = [ start,           start + leftDelimiter.length ];
-				const delEnd =   [ start + content, start + content.length + rightDelimiter.length ];
+				// Start and End offsets of delimiters to remove.
+				const delStart = [
+					index,
+					index + leftDel.length
+				];
+				const delEnd = [
+					index + leftDel.length + content.length,
+					index + leftDel.length + content.length + rightDel.length
+				];
 
 				remove.push( delStart );
 				remove.push( delEnd );
 
-				format.push( [ start + leftDelimiter.length, start + leftDelimiter.length + content.length ] );
-			} while ( ( result = pattern.exec( text ) ) !== null );
+				format.push( [ index + leftDel.length, index + leftDel.length + content.length ] );
+			}
+
+			return {
+				remove,
+				format
+			};
 		} );
 
+		// Format callback to run on matched text.
 		formatClb = formatClb || ( ( editor, range, batch ) => {
 			editor.execute( command, { batch: batch } );
 		} );
@@ -77,7 +126,8 @@ export default class InlineAutoformatEngine {
 			}
 
 			const batch = editor.document.batch();
-			const block = editor.document.selection.focus.parent;
+			const selection = this.editor.document.selection;
+			const block = selection.focus.parent;
 			const text = getText( block );
 
 			if ( block.name !== 'paragraph' || !text ) {
@@ -86,6 +136,10 @@ export default class InlineAutoformatEngine {
 
 			const ranges = testClb( text );
 
+			if ( !ranges ) {
+				return;
+			}
+
 			// Apply format before deleting text.
 			ranges.format.forEach( ( range ) => {
 				if ( range[ 0 ] === undefined || range[ 1 ] === undefined ) {
@@ -97,19 +151,17 @@ export default class InlineAutoformatEngine {
 					block, range[ 1 ]
 				);
 
-				const selection = this.editor.document.selection;
-				const originalRanges = [ ...selection.getRanges() ];
-
 				editor.document.enqueueChanges( () => {
 					selection.setRanges( [ rangeToFormat ] );
 
 					formatClb( this.editor, rangeToFormat, batch );
 
-					selection.setRanges( originalRanges );
+					// FIXME: Problematic part. Changing selection after formatting breaks the formatting.
+					// selection.collapseToEnd();
 				} );
 			} );
 
-			// Reverse order of deleted ranges to not mix the positions.
+			// Reverse order to not mix the offsets while removing.
 			ranges.remove.slice().reverse().forEach( ( range ) => {
 				if ( range[ 0 ] === undefined || range[ 1 ] === undefined ) {
 					return;