8
0
Krzysztof Krztoń 9 лет назад
Родитель
Сommit
b9668219b2
2 измененных файлов с 157 добавлено и 81 удалено
  1. 44 81
      packages/ckeditor5-typing/src/input.js
  2. 113 0
      packages/ckeditor5-typing/src/inputcommand.js

+ 44 - 81
packages/ckeditor5-typing/src/input.js

@@ -8,13 +8,13 @@
  */
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import ChangeBuffer from './changebuffer';
 import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
 import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
 import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
 import diff from '@ckeditor/ckeditor5-utils/src/diff';
 import diffToChanges from '@ckeditor/ckeditor5-utils/src/difftochanges';
 import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
+import InputCommand from './inputcommand';
 
 /**
  * Handles text input coming from the keyboard or other input methods.
@@ -28,19 +28,12 @@ export default class Input extends Plugin {
 	init() {
 		const editor = this.editor;
 		const editingView = editor.editing.view;
+		const inputCommand = new InputCommand( editor );
 
-		/**
-		 * Typing's change buffer used to group subsequent changes into batches.
-		 *
-		 * @protected
-		 * @member {typing.ChangeBuffer} #_buffer
-		 */
-		this._buffer = new ChangeBuffer( editor.document, editor.config.get( 'typing.undoStep' ) || 20 );
-
-		// TODO The above default configuration value should be defined using editor.config.define() once it's fixed.
+		editor.commands.set( 'input', inputCommand );
 
 		this.listenTo( editingView, 'keydown', ( evt, data ) => {
-			this._handleKeydown( data );
+			this._handleKeydown( data, inputCommand.buffer );
 		}, { priority: 'lowest' } );
 
 		this.listenTo( editingView, 'mutations', ( evt, mutations, viewSelection ) => {
@@ -48,16 +41,6 @@ export default class Input extends Plugin {
 		} );
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	destroy() {
-		super.destroy();
-
-		this._buffer.destroy();
-		this._buffer = null;
-	}
-
 	/**
 	 * Handles the keydown event. We need to guess whether such keystroke is going to result
 	 * in typing. If so, then before character insertion happens, any selected content needs
@@ -72,8 +55,9 @@ export default class Input extends Plugin {
 	 *
 	 * @private
 	 * @param {module:engine/view/observer/keyobserver~KeyEventData} evtData
+	 * @param {typing.ChangeBuffer} buffer
 	 */
-	_handleKeydown( evtData ) {
+	_handleKeydown( evtData, buffer ) {
 		const doc = this.editor.document;
 
 		if ( isSafeKeystroke( evtData ) || doc.selection.isCollapsed ) {
@@ -81,20 +65,20 @@ export default class Input extends Plugin {
 		}
 
 		doc.enqueueChanges( () => {
-			this.editor.data.deleteContent( doc.selection, this._buffer.batch );
+			this.editor.data.deleteContent( doc.selection, buffer.batch );
 		} );
 	}
 
 	/**
 	 * Handles DOM mutations.
 	 *
-	 * @param {Array.<engine.view.Document~MutatatedText|engine.view.Document~MutatatedChildren>} mutations
+	 * @param {Array.<module:engine/view/document~MutatatedText|module:engine/view/document~MutatatedChildren>} mutations
+	 * @param {module:engine/controller/editingcontroller~EditingController} viewSelection
 	 */
 	_handleMutations( mutations, viewSelection ) {
-		const doc = this.editor.document;
-		const handler = new MutationHandler( this.editor.editing, this._buffer );
+		const handler = new MutationHandler( this.editor );
 
-		doc.enqueueChanges( () => handler.handle( mutations, viewSelection ) );
+		handler.handle( mutations, viewSelection );
 	}
 }
 
@@ -107,36 +91,29 @@ class MutationHandler {
 	/**
 	 * Creates an instance of the mutation handler.
 	 *
-	 * @param {module:engine/controller/editingcontroller~EditingController} editing
-	 * @param {module:typing/changebuffer~ChangeBuffer} buffer
+	 * @param {module:core/editor/editor~Editor} editor
 	 */
-	constructor( editing, buffer ) {
+	constructor( editor ) {
 		/**
-		 * The editing controller.
+		 * Editor instance for which mutations are handled.
 		 *
-		 * @member {engine.controller.EditingController} #editing
+		 * @readonly
+		 * @member {module:core/editor/editor~Editor} #editor
 		 */
-		this.editing = editing;
+		this.editor = editor;
 
 		/**
-		 * The change buffer.
-		 *
-		 * @member {engine.controller.EditingController} #buffer
-		 */
-		this.buffer = buffer;
-
-		/**
-		 * The number of inserted characters which need to be fed to the {@link #buffer change buffer}.
+		 * The editing controller.
 		 *
-		 * @member {Number} #insertedCharacterCount
+		 * @member {module:engine/controller/editingcontroller~EditingController} #editing
 		 */
-		this.insertedCharacterCount = 0;
+		this.editing = this.editor.editing;
 	}
 
 	/**
 	 * Handles given mutations.
 	 *
-	 * @param {Array.<engine.view.Document~MutatatedText|engine.view.Document~MutatatedChildren>} mutations
+	 * @param {Array.<module:engine/view/document~MutatatedText|module:engine/view/document~MutatatedChildren>} mutations
 	 */
 	handle( mutations, viewSelection ) {
 		for ( let mutation of mutations ) {
@@ -144,8 +121,6 @@ class MutationHandler {
 			this._handleTextMutation( mutation, viewSelection );
 			this._handleTextNodeInsertion( mutation );
 		}
-
-		this.buffer.input( Math.max( this.insertedCharacterCount, 0 ) );
 	}
 
 	_handleTextMutation( mutation, viewSelection ) {
@@ -211,20 +186,17 @@ class MutationHandler {
 		const viewPos = new ViewPosition( mutation.node, firstChangeAt );
 		const modelPos = this.editing.mapper.toModelPosition( viewPos );
 
-		// Remove appropriate number of characters from the model text node.
-		if ( deletions > 0 ) {
-			const removeRange = ModelRange.createFromPositionAndShift( modelPos, deletions );
-			this._remove( removeRange, deletions );
-		}
-
-		// Insert appropriate characters basing on `mutation.text`.
-		const insertedText = mutation.newText.substr( firstChangeAt, insertions );
-		this._insert( modelPos, insertedText );
+		this.editor.execute( 'input', {
+			text: mutation.newText.substr( firstChangeAt, insertions ),
+			range: deletions > 0 ? ModelRange.createFromPositionAndShift( modelPos, deletions ) : null
+		} );
 
-		// If there was `viewSelection` and it got correctly mapped, collapse selection at found model position.
-		if ( modelSelectionPosition ) {
-			this.editing.model.selection.collapse( modelSelectionPosition );
-		}
+		this.editor.document.enqueueChanges( () => {
+			// If there was `viewSelection` and it got correctly mapped, collapse selection at found model position.
+			if ( modelSelectionPosition ) {
+				this.editing.model.selection.collapse( modelSelectionPosition );
+			}
+		} );
 	}
 
 	_handleTextNodeInsertion( mutation ) {
@@ -255,29 +227,20 @@ class MutationHandler {
 
 		const viewPos = new ViewPosition( mutation.node, change.index );
 		const modelPos = this.editing.mapper.toModelPosition( viewPos );
-		let insertedText = change.values[ 0 ].data;
-
-		// Replace &nbsp; inserted by the browser with normal space.
-		// See comment in `_handleTextMutation`.
-		// In this case we don't need to do this before `diff` because we diff whole nodes.
-		// Just change &nbsp; in case there are some.
-		insertedText = insertedText.replace( /\u00A0/g, ' ' );
-
-		this._insert( modelPos, insertedText );
-
-		this.editing.model.selection.collapse( modelPos.getShiftedBy( insertedText.length ) );
-	}
-
-	_insert( position, text ) {
-		this.buffer.batch.weakInsert( position, text );
-
-		this.insertedCharacterCount += text.length;
-	}
-
-	_remove( range, length ) {
-		this.buffer.batch.remove( range );
+		const insertedText = change.values[ 0 ].data;
+
+		this.editor.execute( 'input', {
+			// Replace &nbsp; inserted by the browser with normal space.
+			// See comment in `_handleTextMutation`.
+			// In this case we don't need to do this before `diff` because we diff whole nodes.
+			// Just change &nbsp; in case there are some.
+			text: insertedText.replace( /\u00A0/g, ' ' ),
+			range: new ModelRange( modelPos )
+		} );
 
-		this.insertedCharacterCount -= length;
+		this.editor.document.enqueueChanges( () => {
+			this.editing.model.selection.collapse( modelPos.getShiftedBy( insertedText.length ) );
+		} );
 	}
 }
 

+ 113 - 0
packages/ckeditor5-typing/src/inputcommand.js

@@ -0,0 +1,113 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module typing/inputcommand
+ */
+
+import Command from '@ckeditor/ckeditor5-core/src/command/command';
+import TreeWalker from '@ckeditor/ckeditor5-engine/src/model/treewalker';
+import ChangeBuffer from './changebuffer';
+
+/**
+ * The input command. Used by the {@link module:typing/input~Input input feature} to handle typing.
+ *
+ * @extends core.command.Command
+ */
+export default class InputCommand extends Command {
+	/**
+	 * Creates an instance of the command.
+	 *
+	 * @param {module:core/editor/editor~Editor} editor
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		/**
+		 * Typing's change buffer used to group subsequent changes into batches.
+		 *
+		 * @protected
+		 * @member {typing.ChangeBuffer} #_buffer
+		 */
+		this._buffer = new ChangeBuffer( editor.document, editor.config.get( 'typing.undoStep' ) || 20 );
+
+		// TODO The above default configuration value should be defined using editor.config.define() once it's fixed.
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		super.destroy();
+
+		this._buffer.destroy();
+		this._buffer = null;
+	}
+
+	/**
+	 * Returns the current buffer.
+	 *
+	 * @type {typing.ChangeBuffer}
+	 */
+	get buffer() {
+		return this._buffer;
+	}
+
+	/**
+	 * Executes the input command. It replaces the content within the given range with given text. Replacing is a two
+	 * step process, first content within range is removed and then new text is inserted on the beginning
+	 * of the range (which after removal is a collapsed range).
+	 *
+	 * @param {Object} [options] The command options.
+	 * @param {String} [options.text=''] Text to be inserted.
+	 * @param {module:engine/model/range~Range} [options.range=null] Range in which the text is inserted. Defaults
+	 * to first range in the current selection.
+	 */
+	_doExecute( options = {} ) {
+		const doc = this.editor.document;
+		const range = options.range || doc.selection.getFirstRange();
+		const text = options.text || '';
+		let textLength = 0;
+
+		if ( range ) {
+			doc.enqueueChanges( () => {
+				if ( !range.isCollapsed ) {
+					textLength -= this._getTextWithinRange( range ).length;
+					this._buffer.batch.remove( range );
+				}
+
+				if ( text ) {
+					textLength += text.length;
+					this._buffer.batch.weakInsert( range.start, text );
+				}
+
+				this._buffer.input( Math.max( textLength, 0 ) );
+			} );
+		}
+	}
+
+	/**
+	 * Returns text within given range.
+	 *
+	 * @protected
+	 * @param {module:engine/model/range~Range} range Range from which text will be extracted.
+	 * @returns {String} Text within the given range.
+	 */
+	_getTextWithinRange( range ) {
+		let txt = '';
+		const treeWalker = new TreeWalker( { boundaries: range } );
+
+		for ( let value of treeWalker ) {
+			let node = value.item;
+			let nodeText = node.data;
+
+			if ( nodeText ) {
+				txt += nodeText;
+			}
+		}
+
+		return txt;
+	}
+}