Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Commit

Permalink
Merge pull request #17 from ckeditor/t/15
Browse files Browse the repository at this point in the history
Arrow keys handling.
  • Loading branch information
Reinmar committed Dec 14, 2016
2 parents 180a0c0 + 64553da commit 006a433
Show file tree
Hide file tree
Showing 4 changed files with 686 additions and 284 deletions.
128 changes: 111 additions & 17 deletions src/widget/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,25 @@ export default class Widget extends Plugin {
*/
_onKeydown( eventInfo, domEventData ) {
const keyCode = domEventData.keyCode;
const isForward = keyCode == keyCodes.delete || keyCode == keyCodes.arrowdown || keyCode == keyCodes.arrowright;

// Handle only delete and backspace.
if ( keyCode !== keyCodes.delete && keyCode !== keyCodes.backspace ) {
return;
// Checks if delete/backspace or arrow keys were handled and then prevents default event behaviour and stops
// event propagation.
if ( ( isDeleteKeyCode( keyCode ) && this._handleDelete( isForward ) ) ||
( isArrowKeyCode( keyCode ) && this._handleArrowKeys( isForward ) ) ) {
domEventData.preventDefault();
eventInfo.stop();
}
}

const dataController = this.editor.data;
/**
* Handles delete keys: backspace and delete.
*
* @private
* @param {Boolean} isForward Set to true if delete was performed in forward direction.
* @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
*/
_handleDelete( isForward ) {
const modelDocument = this.editor.document;
const modelSelection = modelDocument.selection;

Expand All @@ -104,22 +116,12 @@ export default class Widget extends Plugin {
return;
}

// Clone current selection to use it as a probe. We must leave default selection as it is so it can return
// to its current state after undo.
const probe = ModelSelection.createFromSelection( modelSelection );
const isForward = ( keyCode == keyCodes.delete );

dataController.modifySelection( probe, { direction: isForward ? 'forward' : 'backward' } );

const objectElement = isForward ? probe.focus.nodeBefore : probe.focus.nodeAfter;

if ( objectElement instanceof ModelElement && modelDocument.schema.objects.has( objectElement.name ) ) {
domEventData.preventDefault();
eventInfo.stop();
const objectElement = this._getObjectElementNextToSelection( isForward );

if ( objectElement ) {
modelDocument.enqueueChanges( () => {
// Remove previous element if empty.
const previousNode = probe.anchor.parent;
const previousNode = modelSelection.anchor.parent;

if ( previousNode.isEmpty ) {
const batch = modelDocument.batch();
Expand All @@ -128,6 +130,51 @@ export default class Widget extends Plugin {

this._setSelectionOverElement( objectElement );
} );

return true;
}
}

/**
* Handles arrow keys.
*
* @param {Boolean} isForward Set to true if arrow key should be handled in forward direction.
* @returns {Boolean|undefined} Returns `true` if keys were handled correctly.
*/
_handleArrowKeys( isForward ) {
const modelDocument = this.editor.document;
const schema = modelDocument.schema;
const modelSelection = modelDocument.selection;
const objectElement = modelSelection.getSelectedElement();

// if object element is selected.
if ( objectElement && schema.objects.has( objectElement.name ) ) {
const position = isForward ? modelSelection.getLastPosition() : modelSelection.getFirstPosition();
const newRange = modelDocument.getNearestSelectionRange( position, isForward ? 'forward' : 'backward' );

if ( newRange ) {
modelDocument.enqueueChanges( () => {
modelSelection.setRanges( [ newRange ] );
} );
}

return true;
}

// If selection is next to object element.
// Return if not collapsed.
if ( !modelSelection.isCollapsed ) {
return;
}

const objectElement2 = this._getObjectElementNextToSelection( isForward );

if ( objectElement2 instanceof ModelElement && modelDocument.schema.objects.has( objectElement2.name ) ) {
modelDocument.enqueueChanges( () => {
this._setSelectionOverElement( objectElement2 );
} );

return true;
}
}

Expand All @@ -140,4 +187,51 @@ export default class Widget extends Plugin {
_setSelectionOverElement( element ) {
this.editor.document.selection.setRanges( [ ModelRange.createOn( element ) ] );
}

/**
* Checks if {@link module:engine/model/element~Element element} placed next to the current
* {@link module:engine/model/selection~Selection model selection} exists and is marked in
* {@link module:engine/model/schema~Schema schema} as `object`.
*
* @private
* @param {Boolean} forward Direction of checking.
* @returns {module:engine/model/element~Element|null}
*/
_getObjectElementNextToSelection( forward ) {
const modelDocument = this.editor.document;
const schema = modelDocument.schema;
const modelSelection = modelDocument.selection;
const dataController = this.editor.data;

// Clone current selection to use it as a probe. We must leave default selection as it is so it can return
// to its current state after undo.
const probe = ModelSelection.createFromSelection( modelSelection );
dataController.modifySelection( probe, { direction: forward ? 'forward' : 'backward' } );
const objectElement = forward ? probe.focus.nodeBefore : probe.focus.nodeAfter;

if ( objectElement instanceof ModelElement && schema.objects.has( objectElement.name ) ) {
return objectElement;
}

return null;
}
}

// Returns 'true' if provided key code represents one of the arrow keys.
//
// @param {Number} keyCode
// @returns {Boolean}
function isArrowKeyCode( keyCode ) {
return keyCode == keyCodes.arrowright ||
keyCode == keyCodes.arrowleft ||
keyCode == keyCodes.arrowup ||
keyCode == keyCodes.arrowdown;
}

//Returns 'true' if provided key code represents one of the delete keys: delete or backspace.
//
//@param {Number} keyCode
//@returns {Boolean}
function isDeleteKeyCode( keyCode ) {
return keyCode == keyCodes.delete || keyCode == keyCodes.backspace;
}
8 changes: 6 additions & 2 deletions tests/manual/image.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
<div id="editor">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. </p>
<figure class="image">
<img src="logo.png" alt="CKEditor logo" />
<img src="logo.png" alt="" />
</figure>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. </p>
<figure class="image">
<img src="logo.png" alt="" />
</figure>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. </p>
<figure class="image">
<img src="logo.png" alt="CKEditor logo" />
</figure>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum. </p>
</div>
9 changes: 8 additions & 1 deletion tests/manual/image.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Image feature

* Two images with CKEditor logo should be loaded.
* Images with CKEditor logo should be loaded.
* Hovering over image should apply yellow outline.
* Clicking on image should apply blue outline which should not change when hovering over.

Expand All @@ -12,3 +12,10 @@
* Place selection at the beginning of the second paragraph and press <kbd>backspace</kbd> (<kbd>delete</kbd> on Mac) - image should be selected. Second press should delete it.
* Place selection in an empty paragraph before image and press <kbd>delete</kbd> (<kbd>forward delete</kbd> on Mac) - image should be selected and paragraph removed.
* Place selection in an empty paragraph after image and press <kbd>backspace</kbd> (<kbd>delete</kbd> on Mac) - image should be selected and paragraph removed.

### Arrow key handling

* Click first image and press <kbd>right arrow</kbd> - second image should be selected. Same effect should happen for <kbd>down arrow</kbd>.
* Click second image and press <kbd>left arrow</kbd> - first image should be selected. Same effect should happen for <kbd>up arrow</kbd>.
* Place selection at the end of the first paragraph and press <kbd>right arrow</kbd> - third image should be selected. Same effect should happen for <kbd>down arrow</kbd>.
* Place selection at the beginning of the first paragraph and press <kbd>left arrow</kbd> - first image should be selected. Same effect should happen for <kbd>up arrow</kbd>.
Loading

0 comments on commit 006a433

Please sign in to comment.