From a2c5559872eac1b0c01aec343d0590d0ea84e47a Mon Sep 17 00:00:00 2001 From: iseulde Date: Tue, 20 Feb 2018 12:49:19 +0100 Subject: [PATCH] Expand selection on consecutive Meta+A presses --- .../editor-global-keyboard-shortcuts/index.js | 15 -------- editor/components/writing-flow/index.js | 28 +++++++++++++-- utils/dom.js | 34 +++++++++++++++++++ utils/keycodes.js | 18 +++++++++- 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/editor/components/editor-global-keyboard-shortcuts/index.js b/editor/components/editor-global-keyboard-shortcuts/index.js index f7d23bd410b0e..859a9b1dbe589 100644 --- a/editor/components/editor-global-keyboard-shortcuts/index.js +++ b/editor/components/editor-global-keyboard-shortcuts/index.js @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { first, last } from 'lodash'; - /** * WordPress dependencies */ @@ -17,19 +12,12 @@ class EditorGlobalKeyboardShortcuts extends Component { constructor() { super( ...arguments ); - this.selectAll = this.selectAll.bind( this ); this.undoOrRedo = this.undoOrRedo.bind( this ); this.save = this.save.bind( this ); this.deleteSelectedBlocks = this.deleteSelectedBlocks.bind( this ); this.clearMultiSelection = this.clearMultiSelection.bind( this ); } - selectAll( event ) { - const { uids, onMultiSelect } = this.props; - event.preventDefault(); - onMultiSelect( first( uids ), last( uids ) ); - } - undoOrRedo( event ) { const { onRedo, onUndo } = this.props; @@ -72,7 +60,6 @@ class EditorGlobalKeyboardShortcuts extends Component { { const { clearSelectedBlock, - multiSelect, redo, undo, removeBlocks, @@ -120,7 +106,6 @@ export default compose( [ return { clearSelectedBlock, - onMultiSelect: multiSelect, onRedo: redo, onUndo: undo, onRemove: removeBlocks, diff --git a/editor/components/writing-flow/index.js b/editor/components/writing-flow/index.js index 45fe750446e2f..38ef58ae979fe 100644 --- a/editor/components/writing-flow/index.js +++ b/editor/components/writing-flow/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { overEvery, find, findLast, reverse } from 'lodash'; +import { overEvery, find, findLast, reverse, first, last } from 'lodash'; /** * WordPress dependencies @@ -16,6 +16,7 @@ import { isVerticalEdge, placeCaretAtHorizontalEdge, placeCaretAtVerticalEdge, + isFullySelected, } from '@wordpress/utils'; import { withSelect, withDispatch } from '@wordpress/data'; @@ -32,7 +33,7 @@ import { * Module Constants */ -const { UP, DOWN, LEFT, RIGHT } = keycodes; +const { UP, DOWN, LEFT, RIGHT, is } = keycodes; /** * Given an element, returns true if the element is a tabbable text field, or @@ -179,7 +180,7 @@ class WritingFlow extends Component { } onKeyDown( event ) { - const { hasMultiSelection } = this.props; + const { hasMultiSelection, onMultiSelect, blocks } = this.props; const { keyCode, target } = event; const isUp = keyCode === UP; @@ -201,6 +202,25 @@ class WritingFlow extends Component { } if ( ! isNav ) { + const activeElement = document.activeElement; + + // Set right before the meta+a combination can be pressed. + if ( is.primary( event ) ) { + this.isFullySelected = isFullySelected( activeElement ); + } + + if ( is.primary( event, 'a' ) ) { + // In the case of contentEditable, we want to know the earlier value + // because the selection will have already been set by TinyMCE. + if ( activeElement.isContentEditable ? this.isFullySelected : isFullySelected( activeElement ) ) { + onMultiSelect( first( blocks ), last( blocks ) ); + event.preventDefault(); + } + + // Set in case the meta key doesn't get released. + this.isFullySelected = isFullySelected( activeElement ); + } + return; } @@ -278,6 +298,7 @@ export default compose( [ getFirstMultiSelectedBlockUid, getLastMultiSelectedBlockUid, hasMultiSelection, + getBlockOrder, } = select( 'core/editor' ); const selectedBlockUID = getSelectedBlockUID(); @@ -292,6 +313,7 @@ export default compose( [ selectedFirstUid: getFirstMultiSelectedBlockUid(), selectedLastUid: getLastMultiSelectedBlockUid(), hasMultiSelection: hasMultiSelection(), + blocks: getBlockOrder(), }; } ), withDispatch( ( dispatch ) => { diff --git a/utils/dom.js b/utils/dom.js index f0dbeb5c5ae8f..d0d21c1a926a9 100644 --- a/utils/dom.js +++ b/utils/dom.js @@ -395,6 +395,40 @@ export function documentHasSelection() { return range && ! range.collapsed; } +/** + * Check wether the contents of the element have been fully selected. + * Returns true if there is no possibility of full selection. + * + * @param {Element} element The element to check. + * + * @return {boolean} True if fully selected, false if not. + */ +export function isFullySelected( element ) { + if ( includes( [ 'INPUT', 'TEXTAREA' ], element.nodeName ) ) { + return element.selectionStart === 0 && element.value.length === element.selectionEnd; + } + + if ( ! element.isContentEditable ) { + return true; + } + + const selection = window.getSelection(); + const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; + + if ( ! range ) { + return true; + } + + const { startContainer, endContainer, startOffset, endOffset } = range; + + return ( + startContainer === element && + endContainer === element && + startOffset === 0 && + endOffset === element.childNodes.length + ); +} + /** * Given a DOM node, finds the closest scrollable container node. * diff --git a/utils/keycodes.js b/utils/keycodes.js index 40274e25bfa6c..5e0871613d068 100644 --- a/utils/keycodes.js +++ b/utils/keycodes.js @@ -12,7 +12,7 @@ /** * External dependencies */ -import { get, mapValues } from 'lodash'; +import { get, mapValues, includes } from 'lodash'; export const BACKSPACE = 8; export const TAB = 9; @@ -90,3 +90,19 @@ export const displayShortcut = mapValues( modifiers, ( modifier ) => { return shortcut.replace( /⌘\+([A-Z0-9])$/g, '⌘$1' ); }; } ); + +export const is = mapValues( modifiers, ( getModifiers ) => { + return ( event, character, _isMac = isMacOS ) => { + const mods = getModifiers( _isMac ); + + if ( ! mods.every( ( key ) => event[ `${ key }Key` ] ) ) { + return false; + } + + if ( ! character ) { + return includes( mods, event.key.toLowerCase() ); + } + + return event.key === character; + }; +} );