From 8cc1eab8f228ad5d990011260ce6b5cbb7d5c7b3 Mon Sep 17 00:00:00 2001 From: Thibaud Colas Date: Thu, 12 Jan 2017 18:31:48 +0200 Subject: [PATCH] Start implementation of keyboard shortcut availability. Fix #32 --- CHANGELOG.md | 1 + docs/README.md | 22 ++++----- examples/basic.js | 1 + lib/api/config.js | 79 +++++++++++++++++++++++++++++++- lib/api/constants.js | 13 +++++- lib/components/DraftailEditor.js | 4 +- lib/utils/keybindings.js | 48 ------------------- 7 files changed, 103 insertions(+), 65 deletions(-) delete mode 100644 lib/utils/keybindings.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9a9709..2e30d4a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html ### Fixed - Fix erratic behavior of list nesting changes with tab and shift+tab shortcuts. #34 +- Fix keyboard shortcuts giving access to unallowed formatting. #32 ### Removed diff --git a/docs/README.md b/docs/README.md index 50c99f47..f1a9956e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,18 +9,19 @@ We support most of the common keyboard shortcuts users would expect to find in t Here are the most important shortcuts: -|Shortcut|Function| +|Shortcut|Function (if enabled)| |--------|--------| -|Cmd + B | Bolden text (if enabled) | -|Cmd + I | Italicise text (if enabled) | -|Cmd + U | Underline text (if enabled) | -|Cmd + J | Format as code (if enabled) | +|Cmd + B | Bold | +|Cmd + I | Italicize | +|Cmd + U | Underline | +|Cmd + J | Monospace (code) | +|Option + Shift + 5 | Strikethrough | |Cmd + Z | Undo | -|Cmd + Maj + Z | Redo | -|Cmd + Left | Move selection to start of block | -|Cmd + Right | Move selection to end of block | -|Cmd + Tab|Increase indentation of list items| -|Cmd + Maj + Tab|Decrease indentation of list items| +|Cmd + Shift + Z | Redo | +|Cmd + Left | Move focus to start of block | +|Cmd + Right | Move focus to end of block | +|Cmd + Tab |Increase indentation of list items | +|Cmd + Shift + Tab |Decrease indentation of list items | Other shortcuts we would like to support in the future: @@ -71,7 +72,6 @@ For all browser versions defined as "latest", we will ensure support by using a | IE | Desktop | 9 | | | IE | Desktop | 8 | | - ## R&D notes ### Other Draft.js editors diff --git a/examples/basic.js b/examples/basic.js index 333067a5..8a2f31d3 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -24,6 +24,7 @@ const options = { INLINE_STYLES: [ { label: 'Bold', style: INLINE_STYLE.BOLD, icon: 'icon-bold' }, { label: 'Italic', style: INLINE_STYLE.ITALIC, icon: 'icon-italic' }, + { label: 'Strikethrough', style: INLINE_STYLE.STRIKETHROUGH, icon: 'icon-pacman' }, ], }; diff --git a/lib/api/config.js b/lib/api/config.js index 853cf815..6815d73e 100644 --- a/lib/api/config.js +++ b/lib/api/config.js @@ -1,5 +1,13 @@ import { Map } from 'immutable'; -import { DefaultDraftBlockRenderMap } from 'draft-js'; +import { + DefaultDraftBlockRenderMap, + getDefaultKeyBinding, + KeyBindingUtil, +} from 'draft-js'; + +import { INLINE_STYLE, KEY_CODES } from '../api/constants'; + +const { isOptionKeyCommand, hasCommandModifier } = KeyBindingUtil; // Maximum level of nesting for unordered and ordered lists. export const MAX_LIST_NESTING = 1; @@ -43,4 +51,73 @@ export default { return blockStyleFn; }, + + /** + * Configure key binding function from available blocks, styles, entities. + */ + getKeyBindingFn(BLOCK_TYPES = [], INLINE_STYLES = []) { + const isStyleAvailable = style => INLINE_STYLES.find(item => item.style === style); + + const isAvailable = Object.keys(INLINE_STYLE).reduce((available, style) => { + // eslint-disable-next-line no-param-reassign + available[style] = isStyleAvailable(style); + + return available; + }, {}); + + // Emits key commands to use in `handleKeyCommand` in `Editor`. + const keyBindingFn = (e) => { + switch (e.keyCode) { + case KEY_CODES.B: + return isAvailable.BOLD && hasCommandModifier(e) ? 'bold' : null; + case KEY_CODES.I: + return isAvailable.ITALIC && hasCommandModifier(e) ? 'italic' : null; + case KEY_CODES.J: + return isAvailable.CODE && hasCommandModifier(e) ? 'code' : null; + case KEY_CODES.U: + return isAvailable.UNDERLINE && hasCommandModifier(e) ? 'underline' : null; + case KEY_CODES[5]: + return isAvailable.STRIKETHROUGH && e.shiftKey && isOptionKeyCommand(e) ? 'strikethrough' : null; + default: + } + + // if (e.altKey === true && !e.ctrlKey) { + // if (e.shiftKey === true) { + // switch (e.which) { + // // Alt + Shift + A + // // case 65: return addNewBlock(); + // default: return getDefaultKeyBinding(e); + // } + // } + + // switch (e.which) { + // // // 1 + // // case 49: return changeType('ordered-list-item'); + // // // @ + // // case 50: return showLinkInput(); + // // // # + // // case 51: return changeType('header-three'); + // // // * + // // case 56: return changeType('unordered-list-item'); + // // // < + // // case 188: return changeType('caption'); + // // // // - + // // // case 189: return 'changetype:caption'; + // // // > + // // case 190: return changeType('unstyled'); + // // // " + // // case 222: return changeType('blockquote'); + // default: return getDefaultKeyBinding(e); + // } + // } + + // if (e.keyCode === 46 && !e.ctrlKey) { + // return KEY_COMMANDS.deleteBlock(); + // } + return getDefaultKeyBinding(e); + }; + + return keyBindingFn; + }, + }; diff --git a/lib/api/constants.js b/lib/api/constants.js index 624c73ab..c39948a5 100644 --- a/lib/api/constants.js +++ b/lib/api/constants.js @@ -29,8 +29,17 @@ export const ENTITY_TYPE = { // Originally from https://github.com/draft-js-utils/draft-js-utils/blob/master/src/Constants.js. export const INLINE_STYLE = { BOLD: 'BOLD', - CODE: 'CODE', ITALIC: 'ITALIC', - STRIKETHROUGH: 'STRIKETHROUGH', + CODE: 'CODE', UNDERLINE: 'UNDERLINE', + STRIKETHROUGH: 'STRIKETHROUGH', +}; + +// Originally from https://github.com/facebook/draft-js/blob/master/src/component/utils/getDefaultKeyBinding.js. +export const KEY_CODES = { + B: 66, + U: 85, + J: 74, + I: 73, + 5: 53, }; diff --git a/lib/components/DraftailEditor.js b/lib/components/DraftailEditor.js index 54467321..cdf6d2d2 100644 --- a/lib/components/DraftailEditor.js +++ b/lib/components/DraftailEditor.js @@ -15,8 +15,6 @@ import config, { MAX_LIST_NESTING, STATE_SAVE_INTERVAL } from '../api/config'; import DraftUtils from '../api/DraftUtils'; import conversion from '../api/conversion'; -import keyBindingFn from '../utils/keybindings'; - import BlockControls from '../components/BlockControls'; import InlineStyleControls from '../components/InlineStyleControls'; import Button from '../components/Button'; @@ -506,7 +504,7 @@ class DraftailEditor extends Component { readOnly={readOnly} handleReturn={this.handleReturn} handleKeyCommand={this.handleKeyCommand} - keyBindingFn={keyBindingFn} + keyBindingFn={config.getKeyBindingFn(options.BLOCK_TYPES, options.INLINE_STYLES)} onTab={this.handleTabCommand} spellCheck={false} blockRendererFn={this.blockRenderer} diff --git a/lib/utils/keybindings.js b/lib/utils/keybindings.js deleted file mode 100644 index dcfabad1..00000000 --- a/lib/utils/keybindings.js +++ /dev/null @@ -1,48 +0,0 @@ -import { getDefaultKeyBinding } from 'draft-js'; - -// const { isCtrlKeyCommand, isOptionKeyCommand, hasCommandModifier } = KeyBindingUtil; - -// import { KEY_COMMANDS } from './constants'; - -// const { changeType, showLinkInput } = KEY_COMMANDS; - -/* -Emits various key commands to be used by `handleKeyCommand` in `Editor` based -on various key combos. -*/ -export default (e) => { - // if (e.altKey === true && !e.ctrlKey) { - // if (e.shiftKey === true) { - // switch (e.which) { - // // Alt + Shift + A - // // case 65: return addNewBlock(); - // default: return getDefaultKeyBinding(e); - // } - // } - - // switch (e.which) { - // // // 1 - // // case 49: return changeType('ordered-list-item'); - // // // @ - // // case 50: return showLinkInput(); - // // // # - // // case 51: return changeType('header-three'); - // // // * - // // case 56: return changeType('unordered-list-item'); - // // // < - // // case 188: return changeType('caption'); - // // // // - - // // // case 189: return 'changetype:caption'; - // // // > - // // case 190: return changeType('unstyled'); - // // // " - // // case 222: return changeType('blockquote'); - // default: return getDefaultKeyBinding(e); - // } - // } - - // if (e.keyCode === 46 && !e.ctrlKey) { - // return KEY_COMMANDS.deleteBlock(); - // } - return getDefaultKeyBinding(e); -};