Skip to content

Commit

Permalink
Add Key, test for printable character on keydown when selection
Browse files Browse the repository at this point in the history
fixes #50
  • Loading branch information
bantic committed Aug 13, 2015
1 parent 6d4983d commit 83deff5
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 26 deletions.
30 changes: 17 additions & 13 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import CardCommand from '../commands/card';

import ImageCard from '../cards/image';

import Keycodes from '../utils/keycodes';
import Key from '../utils/key';
import {
getSelectionBlockElement
} from '../utils/selection-utils';
Expand Down Expand Up @@ -136,22 +136,25 @@ function bindSelectionEvent(editor) {
}

function bindKeyListeners(editor) {
// escape key
editor.addEventListener(document, 'keyup', (event) => {
if (event.keyCode === Keycodes.ESC) {
const key = Key.fromEvent(event);
if (key.isEscape()) {
editor.trigger('escapeKey');
}
});

editor.addEventListener(document, 'keydown', (event) => {
switch (event.keyCode) {
case Keycodes.BACKSPACE:
case Keycodes.DELETE:
editor.handleDeletion(event);
break;
case Keycodes.ENTER:
const key = Key.fromEvent(event);

if (key.isDelete()) {
editor.handleDeletion(event);
event.preventDefault();
} else if (key.isEnter()) {
editor.handleNewline(event);
break;
} else if (key.isPrintable()) {
if (editor.cursor.hasSelection()) {
editor.deleteSelection(event, {preventDefault:false});
}
}
});
}
Expand Down Expand Up @@ -308,8 +311,10 @@ class Editor {
this._renderer.render(this._renderTree);
}

deleteSelection(event) {
event.preventDefault();
deleteSelection(event, options={preventDefault:true}) {
if (options.preventDefault) {
event.preventDefault();
}

// types of selection deletion:
// * a selection starts at the beginning of a section
Expand Down Expand Up @@ -424,7 +429,6 @@ class Editor {
nextCursorOffset);

this.trigger('update');
event.preventDefault();
}

handleNewline(event) {
Expand Down
69 changes: 69 additions & 0 deletions src/js/utils/key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Keycodes from './keycodes';

/**
* An abstraction around a KeyEvent
* that key listeners in the editor can use
* to determine what sort of key was pressed
*/
const Key = class Key {
constructor(event) {
this.keyCode = event.keyCode;
this.event = event;
}

static fromEvent(event) {
return new Key(event);
}

isEscape() {
return this.keyCode === Keycodes.ESC;
}

isDelete() {
return this.keyCode === Keycodes.BACKSPACE ||
this.keyCode === Keycodes.DELETE;
}

isSpace() {
return this.keyCode === Keycodes.SPACE;
}

isEnter() {
return this.keyCode === Keycodes.ENTER;
}

get ctrlKey() {
return this.event.ctrlKey;
}

get metaKey() {
return this.event.metaKey;
}

/**
* See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Printable_keys_in_standard_position
* and http://stackoverflow.com/a/12467610/137784
*/
isPrintable() {
if (this.ctrlKey || this.metaKey) {
return false;
}

const {keyCode:code} = this;

return (
(code >= Keycodes['0'] && code <= Keycodes['9']) || // number keys
this.isSpace() ||
this.isEnter() ||
(code >= Keycodes.A && code <= Keycodes.Z) || // letter keys
(code >= Keycodes.NUMPAD_0 && code <= Keycodes.NUMPAD_9) || // numpad keys
(code >= Keycodes[';'] && code <= Keycodes['`']) || // punctuation
(code >= Keycodes['['] && code <= Keycodes['"']) ||
// FIXME the IME action seems to get lost when we issue an `editor.deleteSelection`
// before it (in Chrome)
code === Keycodes.IME
);
}
};

export default Key;
19 changes: 17 additions & 2 deletions src/js/utils/keycodes.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
export default {
LEFT_ARROW: 37,
BACKSPACE : 8,
SPACE: 32,
ENTER : 13,
ESC : 27,
DELETE : 46,
M : 77
'0': 48,
'9': 57,
A: 65,
Z: 90,
'NUMPAD_0': 186,
'NUMPAD_9': 111,
';': 186,
'`': 192,
'[': 219,
'"': 222,

// Input Method Editor uses multiple keystrokes to display characters.
// Example on mac: press option-i then i. This fires 2 key events in Chrome
// with keyCode 229 and displays ˆ and then î.
// See http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html#fixed-virtual-key-codes
IME: 229
};
12 changes: 5 additions & 7 deletions tests/acceptance/editor-sections-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,21 +274,19 @@ Helpers.skipInPhantom('keystroke of character results in unprintable being remov
assert.ok(!!textNode, 'gets text node');
Helpers.dom.moveCursorTo(textNode, 1);

const runDefault = Helpers.dom.triggerKeyEvent(document, 'keydown', Helpers.dom.KEY_CODES.M);
if (runDefault) {
document.execCommand('insertText', false, 'm');
Helpers.dom.triggerEvent(editor.element, 'input');
}
const key = "M";
const keyCode = key.charCodeAt(0);
Helpers.dom.triggerKeyEvent(document, 'keydown', keyCode, key);

textNode = getTextNode();
assert.equal(textNode.textContent, 'm',
assert.equal(textNode.textContent, key,
'adds character');

assert.equal(textNode.textContent.length, 1);

assert.deepEqual(Helpers.dom.getCursorPosition(),
{node: textNode, offset: 1},
'cursor moves to end of m text node');
`cursor moves to end of ${key} text node`);
});

test('keystroke of delete at start of section joins with previous section', (assert) => {
Expand Down
31 changes: 29 additions & 2 deletions tests/acceptance/editor-selections-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,33 @@ test('selecting text across sections and hitting enter deletes and moves cursor
});
});

Helpers.skipInPhantom('keystroke of printable character while text is selected deletes the text', (assert) => {
const done = assert.async();
editor = new Editor(editorElement, {mobiledoc: mobileDocWith2Sections});

Helpers.dom.selectText('first section', editorElement);
Helpers.dom.triggerEvent(document, 'mouseup');

setTimeout(() => {
Helpers.toolbar.clickButton(assert, 'heading');

assert.ok($('#editor h2:contains(first section)').length,
'first section is a heading');

const firstSectionTextNode = editorElement.childNodes[0].childNodes[0];
const secondSectionTextNode = editorElement.childNodes[1].childNodes[0];
Helpers.dom.selectText('section', firstSectionTextNode,
'secon', secondSectionTextNode);

const key = 'A';
const charCodeA = 65;
Helpers.dom.triggerKeyEvent(document, 'keydown', charCodeA, key);

assert.ok($(`#editor h2:contains(first ${key}d section)`).length,
'updates the section');

done();
});
});

// test selecting text that includes entire sections deletes the sections
// test selecting text across two types of sections and deleting
// test selecting text and hitting enter or keydown
7 changes: 5 additions & 2 deletions tests/helpers/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ function createKeyEvent(eventType, keyCode) {
return oEvent;
}

function triggerKeyEvent(node, eventType, keyCode=KEY_CODES.ENTER) {
function triggerKeyEvent(node, eventType, keyCode=KEY_CODES.ENTER, character=null) {
let oEvent = createKeyEvent(eventType, keyCode);
return node.dispatchEvent(oEvent);
node.dispatchEvent(oEvent);
if (character) {
document.execCommand('insertText', false, character);
}
}

function _buildDOM(tagName, attributes={}, children=[]) {
Expand Down

0 comments on commit 83deff5

Please sign in to comment.