|
@@ -8,18 +8,18 @@
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
|
|
|
-import ChangeBuffer from './changebuffer';
|
|
|
|
|
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
|
|
import ModelRange from '@ckeditor/ckeditor5-engine/src/model/range';
|
|
|
import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
|
|
import ViewPosition from '@ckeditor/ckeditor5-engine/src/view/position';
|
|
|
import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
|
|
import ViewText from '@ckeditor/ckeditor5-engine/src/view/text';
|
|
|
import diff from '@ckeditor/ckeditor5-utils/src/diff';
|
|
import diff from '@ckeditor/ckeditor5-utils/src/diff';
|
|
|
import diffToChanges from '@ckeditor/ckeditor5-utils/src/difftochanges';
|
|
import diffToChanges from '@ckeditor/ckeditor5-utils/src/difftochanges';
|
|
|
import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
|
import { getCode } from '@ckeditor/ckeditor5-utils/src/keyboard';
|
|
|
|
|
+import InputCommand from './inputcommand';
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Handles text input coming from the keyboard or other input methods.
|
|
* Handles text input coming from the keyboard or other input methods.
|
|
|
*
|
|
*
|
|
|
- * @extends core.Plugin
|
|
|
|
|
|
|
+ * @extends module:core/plugin~Plugin
|
|
|
*/
|
|
*/
|
|
|
export default class Input extends Plugin {
|
|
export default class Input extends Plugin {
|
|
|
/**
|
|
/**
|
|
@@ -28,19 +28,14 @@ export default class Input extends Plugin {
|
|
|
init() {
|
|
init() {
|
|
|
const editor = this.editor;
|
|
const editor = this.editor;
|
|
|
const editingView = editor.editing.view;
|
|
const editingView = editor.editing.view;
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 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 );
|
|
|
|
|
|
|
+ const inputCommand = new InputCommand( editor, editor.config.get( 'typing.undoStep' ) || 20 );
|
|
|
|
|
|
|
|
// TODO The above default configuration value should be defined using editor.config.define() once it's fixed.
|
|
// 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.listenTo( editingView, 'keydown', ( evt, data ) => {
|
|
|
- this._handleKeydown( data );
|
|
|
|
|
|
|
+ this._handleKeydown( data, inputCommand.buffer );
|
|
|
}, { priority: 'lowest' } );
|
|
}, { priority: 'lowest' } );
|
|
|
|
|
|
|
|
this.listenTo( editingView, 'mutations', ( evt, mutations, viewSelection ) => {
|
|
this.listenTo( editingView, 'mutations', ( evt, mutations, viewSelection ) => {
|
|
@@ -49,16 +44,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
|
|
* 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
|
|
* in typing. If so, then before character insertion happens, any selected content needs
|
|
|
* to be deleted. Otherwise the default browser deletion mechanism would be
|
|
* to be deleted. Otherwise the default browser deletion mechanism would be
|
|
@@ -72,8 +57,9 @@ export default class Input extends Plugin {
|
|
|
*
|
|
*
|
|
|
* @private
|
|
* @private
|
|
|
* @param {module:engine/view/observer/keyobserver~KeyEventData} evtData
|
|
* @param {module:engine/view/observer/keyobserver~KeyEventData} evtData
|
|
|
|
|
+ * @param {module:typing/changebuffer~ChangeBuffer} buffer
|
|
|
*/
|
|
*/
|
|
|
- _handleKeydown( evtData ) {
|
|
|
|
|
|
|
+ _handleKeydown( evtData, buffer ) {
|
|
|
const doc = this.editor.document;
|
|
const doc = this.editor.document;
|
|
|
|
|
|
|
|
if ( isSafeKeystroke( evtData ) || doc.selection.isCollapsed ) {
|
|
if ( isSafeKeystroke( evtData ) || doc.selection.isCollapsed ) {
|
|
@@ -81,20 +67,19 @@ export default class Input extends Plugin {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
doc.enqueueChanges( () => {
|
|
doc.enqueueChanges( () => {
|
|
|
- this.editor.data.deleteContent( doc.selection, this._buffer.batch );
|
|
|
|
|
|
|
+ this.editor.data.deleteContent( doc.selection, buffer.batch );
|
|
|
} );
|
|
} );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Handles DOM mutations.
|
|
* Handles DOM mutations.
|
|
|
*
|
|
*
|
|
|
- * @param {Array.<engine.view.Document~MutatatedText|engine.view.Document~MutatatedChildren>} mutations
|
|
|
|
|
|
|
+ * @private
|
|
|
|
|
+ * @param {Array.<module:engine/view/document~MutatatedText|module:engine/view/document~MutatatedChildren>} mutations
|
|
|
|
|
+ * @param {module:engine/view/selection~Selection|null} viewSelection
|
|
|
*/
|
|
*/
|
|
|
_handleMutations( mutations, viewSelection ) {
|
|
_handleMutations( mutations, viewSelection ) {
|
|
|
- const doc = this.editor.document;
|
|
|
|
|
- const handler = new MutationHandler( this.editor.editing, this._buffer );
|
|
|
|
|
-
|
|
|
|
|
- doc.enqueueChanges( () => handler.handle( mutations, viewSelection ) );
|
|
|
|
|
|
|
+ new MutationHandler( this.editor ).handle( mutations, viewSelection );
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -107,36 +92,32 @@ class MutationHandler {
|
|
|
/**
|
|
/**
|
|
|
* Creates an instance of the mutation handler.
|
|
* 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
|
|
|
|
|
|
|
+ * @readonly
|
|
|
|
|
+ * @member {module:engine/controller/editingcontroller~EditingController} #editing
|
|
|
*/
|
|
*/
|
|
|
- this.insertedCharacterCount = 0;
|
|
|
|
|
|
|
+ this.editing = this.editor.editing;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Handles given mutations.
|
|
* Handles given mutations.
|
|
|
*
|
|
*
|
|
|
- * @param {Array.<engine.view.Document~MutatatedText|engine.view.Document~MutatatedChildren>} mutations
|
|
|
|
|
|
|
+ * @param {Array.<module:engine/view/observer/mutationobserver~MutatedText|
|
|
|
|
|
+ * module:engine/view/observer/mutationobserver~MutatatedChildren>} mutations
|
|
|
|
|
+ * @param {module:engine/view/selection~Selection|null} viewSelection
|
|
|
*/
|
|
*/
|
|
|
handle( mutations, viewSelection ) {
|
|
handle( mutations, viewSelection ) {
|
|
|
for ( let mutation of mutations ) {
|
|
for ( let mutation of mutations ) {
|
|
@@ -144,8 +125,6 @@ class MutationHandler {
|
|
|
this._handleTextMutation( mutation, viewSelection );
|
|
this._handleTextMutation( mutation, viewSelection );
|
|
|
this._handleTextNodeInsertion( mutation );
|
|
this._handleTextNodeInsertion( mutation );
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- this.buffer.input( Math.max( this.insertedCharacterCount, 0 ) );
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_handleTextMutation( mutation, viewSelection ) {
|
|
_handleTextMutation( mutation, viewSelection ) {
|
|
@@ -210,21 +189,14 @@ class MutationHandler {
|
|
|
// Get the position in view and model where the changes will happen.
|
|
// Get the position in view and model where the changes will happen.
|
|
|
const viewPos = new ViewPosition( mutation.node, firstChangeAt );
|
|
const viewPos = new ViewPosition( mutation.node, firstChangeAt );
|
|
|
const modelPos = this.editing.mapper.toModelPosition( viewPos );
|
|
const modelPos = this.editing.mapper.toModelPosition( viewPos );
|
|
|
|
|
+ const removeRange = ModelRange.createFromPositionAndShift( modelPos, deletions );
|
|
|
|
|
+ const insertText = newText.substr( firstChangeAt, insertions );
|
|
|
|
|
|
|
|
- // 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 = newText.substr( firstChangeAt, insertions );
|
|
|
|
|
- this._insert( modelPos, insertedText );
|
|
|
|
|
-
|
|
|
|
|
- // 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.execute( 'input', {
|
|
|
|
|
+ text: insertText,
|
|
|
|
|
+ range: removeRange,
|
|
|
|
|
+ resultPosition: modelSelectionPosition
|
|
|
|
|
+ } );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_handleTextNodeInsertion( mutation ) {
|
|
_handleTextNodeInsertion( mutation ) {
|
|
@@ -255,29 +227,16 @@ class MutationHandler {
|
|
|
|
|
|
|
|
const viewPos = new ViewPosition( mutation.node, change.index );
|
|
const viewPos = new ViewPosition( mutation.node, change.index );
|
|
|
const modelPos = this.editing.mapper.toModelPosition( viewPos );
|
|
const modelPos = this.editing.mapper.toModelPosition( viewPos );
|
|
|
- let insertedText = change.values[ 0 ].data;
|
|
|
|
|
-
|
|
|
|
|
- // Replace 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 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 );
|
|
|
|
|
-
|
|
|
|
|
- this.insertedCharacterCount -= length;
|
|
|
|
|
|
|
+ const insertedText = change.values[ 0 ].data;
|
|
|
|
|
+
|
|
|
|
|
+ this.editor.execute( 'input', {
|
|
|
|
|
+ // Replace 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 in case there are some.
|
|
|
|
|
+ text: insertedText.replace( /\u00A0/g, ' ' ),
|
|
|
|
|
+ range: new ModelRange( modelPos )
|
|
|
|
|
+ } );
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|