diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index 3b0ab621b..82f5b26f7 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -26,7 +26,7 @@ import { DEFAULT_TEXT_EXPANSIONS, findExpansion, validateExpansion } from './text-expansions'; import { - DEFAULT_KEY_COMMANDS, findKeyCommand, validateKeyCommand + DEFAULT_KEY_COMMANDS, findKeyCommands, validateKeyCommand } from './key-commands'; import { capitalize } from '../utils/string-utils'; import LifecycleCallbacksMixin from '../utils/lifecycle-callbacks'; @@ -196,7 +196,7 @@ class Editor { if (!validateKeyCommand(keyCommand)) { throw new Error('Key Command is not valid'); } - this.keyCommands.push(keyCommand); + this.keyCommands.unshift(keyCommand); } handleExpansion(event) { @@ -602,11 +602,27 @@ class Editor { this.handleKeyCommand(event); } + /** + * Finds and runs the first matching key command for the event + * + * If multiple commands are bound to a key combination, the + * first matching one is run. + * + * If a command returns `false` then the next matching command + * is run instead. + * + * @method handleKeyCommand + * @param {Event} event The keyboard event triggered by the user + * @private + */ handleKeyCommand(event) { - const keyCommand = findKeyCommand(this.keyCommands, event); - if (keyCommand) { - event.preventDefault(); - keyCommand.run(this); + const keyCommands = findKeyCommands(this.keyCommands, event); + for (let i=0; i { + return filter(keyCommands, ({modifier, str}) => { return key.hasModifier(modifier) && key.isChar(str); }); } diff --git a/tests/acceptance/editor-key-commands-test.js b/tests/acceptance/editor-key-commands-test.js index 29c59db11..ae1246984 100644 --- a/tests/acceptance/editor-key-commands-test.js +++ b/tests/acceptance/editor-key-commands-test.js @@ -72,3 +72,59 @@ test('new key commands can be registered', (assert) => { assert.ok(!!passedEditor && passedEditor === editor, 'run method is called'); }); + +test('duplicate key commands can be registered with the last registered winning', (assert) => { + const mobiledoc = Helpers.mobiledoc.build( + ({post, markupSection, marker}) => post([ + markupSection('p', [marker('something')]) + ])); + + let firstCommandRan, secondCommandRan; + editor = new Editor({mobiledoc}); + editor.registerKeyCommand({ + modifier: MODIFIERS.CTRL, + str: 'X', + run() { firstCommandRan = true; } + }); + editor.registerKeyCommand({ + modifier: MODIFIERS.CTRL, + str: 'X', + run() { secondCommandRan = true; } + }); + editor.render(editorElement); + + Helpers.dom.triggerKeyCommand(editor, 'X', MODIFIERS.CTRL); + + assert.ok(!firstCommandRan, 'first registered method not called'); + assert.ok(!!secondCommandRan, 'last registered method is called'); +}); + +test('returning false from key command causes next match to run', (assert) => { + const mobiledoc = Helpers.mobiledoc.build( + ({post, markupSection, marker}) => post([ + markupSection('p', [marker('something')]) + ])); + + let firstCommandRan, secondCommandRan; + editor = new Editor({mobiledoc}); + editor.registerKeyCommand({ + modifier: MODIFIERS.CTRL, + str: 'X', + run() { firstCommandRan = true; } + }); + editor.registerKeyCommand({ + modifier: MODIFIERS.CTRL, + str: 'X', + run() { + secondCommandRan = true; + return false; + } + }); + editor.render(editorElement); + + Helpers.dom.triggerKeyCommand(editor, 'X', MODIFIERS.CTRL); + + assert.ok(!!secondCommandRan, 'last registered method is called'); + assert.ok(!!firstCommandRan, 'first registered method is called'); +}); +