Skip to content

Commit

Permalink
Add remaining editable shortcuts (#4411)
Browse files Browse the repository at this point in the history
* Add remaining editable shortcuts
* Add shortcuts to formatting control tooltips
* Move shortcut text in tooltips
* Adjust tooltip styles
* feat: Add Keyboard Shortcut text utility
* chore: Improve Mac keycodes by using names instead of just glyphs
  • Loading branch information
ellatrix authored and tofumatt committed May 4, 2018
1 parent 480990a commit c37a222
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 13 deletions.
12 changes: 11 additions & 1 deletion blocks/rich-text/format-toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { prependHTTP } from '@wordpress/url';
/**
* Internal dependencies
*/
import { accessShortcut, primaryShortcut } from 'utils/keycodes';
import './style.scss';
import UrlInput from '../../url-input';
import { filterURLForDisplay } from '../../../editor/utils/url';
Expand All @@ -26,19 +27,25 @@ const FORMATTING_CONTROLS = [
{
icon: 'editor-bold',
title: __( 'Bold' ),
shortcut: primaryShortcut( 'B' ),
format: 'bold',
},
{
icon: 'editor-italic',
title: __( 'Italic' ),
shortcut: primaryShortcut( 'I' ),
format: 'italic',
},
{
icon: 'editor-strikethrough',
title: __( 'Strikethrough' ),
shortcut: accessShortcut( 'D' ),
format: 'strikethrough',
},
{
icon: 'admin-links',
title: __( 'Link' ),
shortcut: primaryShortcut( 'K' ),
format: 'link',
},
];
Expand Down Expand Up @@ -85,13 +92,16 @@ class FormatToolbar extends Component {
componentWillReceiveProps( nextProps ) {
if ( this.props.selectedNodeId !== nextProps.selectedNodeId ) {
this.setState( {
isAddingLink: false,
isEditingLink: false,
settingsVisible: false,
opensInNewWindow: !! nextProps.formats.link && !! nextProps.formats.link.target,
newLinkValue: '',
} );
}

this.setState( {
isAddingLink: !! nextProps.formats.link && nextProps.formats.link.isAdding,
} );
}

onChangeLinkValue( value ) {
Expand Down
12 changes: 11 additions & 1 deletion blocks/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export function getFormatProperties( formatName, parents ) {
}
}

const DEFAULT_FORMATS = [ 'bold', 'italic', 'strikethrough', 'link' ];
const DEFAULT_FORMATS = [ 'bold', 'italic', 'strikethrough', 'link', 'code' ];

export class RichText extends Component {
constructor() {
Expand Down Expand Up @@ -193,6 +193,12 @@ export class RichText extends Component {
if ( this.props.onSetup ) {
this.props.onSetup( editor );
}

editor.shortcuts.add( 'meta+k', '', () => this.changeFormats( { link: { isAdding: true } } ) );
editor.shortcuts.add( 'access+a', '', () => this.changeFormats( { link: { isAdding: true } } ) );
editor.shortcuts.add( 'access+s', '', () => this.changeFormats( { link: undefined } ) );
editor.shortcuts.add( 'access+d', '', () => this.changeFormats( { strikethrough: ! this.state.formats.strikethrough } ) );
editor.shortcuts.add( 'access+x', '', () => this.changeFormats( { code: ! this.state.formats.code } ) );
}

/**
Expand Down Expand Up @@ -758,6 +764,10 @@ export class RichText extends Component {
forEach( formats, ( formatValue, format ) => {
if ( format === 'link' ) {
if ( formatValue !== undefined ) {
if ( formatValue.isAdding ) {
return;
}

const anchor = this.editor.dom.getParent( this.editor.selection.getNode(), 'a' );
if ( ! anchor ) {
this.removeFormat( 'link' );
Expand Down
8 changes: 6 additions & 2 deletions components/icon-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Dashicon from '../dashicon';
// is common to apply a ref to the button element (only supported in class)
class IconButton extends Component {
render() {
const { icon, children, label, className, tooltip, focus, ...additionalProps } = this.props;
const { icon, children, label, className, tooltip, focus, shortcut, ...additionalProps } = this.props;
const classes = classnames( 'components-icon-button', className );
const tooltipText = tooltip || label;

Expand All @@ -42,7 +42,11 @@ class IconButton extends Component {
);

if ( showTooltip ) {
element = <Tooltip text={ tooltipText }>{ element }</Tooltip>;
element = (
<Tooltip text={ tooltipText } shortcut={ shortcut }>
{ element }
</Tooltip>
);
}

return element;
Expand Down
1 change: 1 addition & 0 deletions components/toolbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ function Toolbar( { controls = [], children, className } ) {
<IconButton
icon={ control.icon }
label={ control.title }
shortcut={ control.shortcut }
data-subscript={ control.subscript }
onClick={ ( event ) => {
event.stopPropagation();
Expand Down
3 changes: 2 additions & 1 deletion components/tooltip/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class Tooltip extends Component {
}

render() {
const { children, position, text } = this.props;
const { children, position, text, shortcut } = this.props;
if ( Children.count( children ) !== 1 ) {
if ( 'development' === process.env.NODE_ENV ) {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -179,6 +179,7 @@ class Tooltip extends Component {
aria-hidden="true"
>
{ text }
{ shortcut && <span className="components-tooltip__shortcut">{ shortcut }</span> }
</Popover>
),
),
Expand Down
12 changes: 9 additions & 3 deletions components/tooltip/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@
}

&.is-top:after {
border-top-color: $dark-gray-400;
border-top-color: $dark-gray-900;
}

&.is-bottom:after {
border-bottom-color: $dark-gray-400;
border-bottom-color: $dark-gray-900;
}
}

.components-tooltip .components-popover__content {
padding: 4px 12px;
background: $dark-gray-400;
background: $dark-gray-900;
border-width: 0;
color: $white;
white-space: nowrap;
Expand All @@ -25,3 +25,9 @@
.components-tooltip:not(.is-mobile) .components-popover__content {
min-width: 0;
}

.components-tooltip__shortcut {
display: block;
text-align: center;
color: $dark-gray-200;
}
10 changes: 6 additions & 4 deletions edit-post/keyboard-shortcuts.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const isMac = window.navigator.platform.toUpperCase().indexOf( 'MAC' ) >= 0;
const mod = isMac ? '⌘' : 'Ctrl';
/**
* Internal dependencies
*/
import { secondaryKeyCode, secondaryShortcut } from 'utils/keycodes';

export default {
toggleEditorMode: {
value: 'mod+shift+alt+m',
label: `${ mod }+Shift+Alt+M`,
value: secondaryKeyCode( 'm' ),
label: secondaryShortcut( 'M' ),
},
};
5 changes: 4 additions & 1 deletion utils/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { omit } from 'lodash';

import * as focus from './focus';
import * as keycodes from './keycodes';
import * as keycodesAll from './keycodes';
import * as viewPort from './viewport';
import { decodeEntities } from './entities';

const keycodes = omit( keycodesAll, [ 'keyboardShortcut' ] );
export { focus };
export { keycodes };
export { decodeEntities };
Expand Down
124 changes: 124 additions & 0 deletions utils/keycodes.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* Note: The order of the modifier keys in many of the [foo]Shortcut()
* functions in this file are intentional and should not be changed. They're
* designed to fit with the standard menu keyboard shortcuts shown in the
* user's platform.
*
* For example, on MacOS menu shortcuts will place Shift before Command, but
* on Windows Control will usually come first. So don't provide your own
* shortcut combos directly to keyboardShortcut().
*/
export const BACKSPACE = 8;
export const TAB = 9;
export const ENTER = 13;
Expand All @@ -10,3 +20,117 @@ export const DOWN = 40;
export const DELETE = 46;

export const F10 = 121;

export const ALT = 'alt';
export const CTRL = 'ctrl';
export const PRIMARY = 'mod';
export const META = 'meta';
export const SHIFT = 'shift';

/**
* Return true if platform is MacOS.
*
* @param {Object} _window window object by default; used for DI testing.
*
* @return {boolean} True if MacOS; false otherwise.
*/
export function isMacOS( _window = window ) {
return _window.navigator.platform.indexOf( 'Mac' ) !== -1;
}

/**
* Create a keyboard shortcut based on a string of modifiers + key(s).
*
* This function is not intended to be used directly by developers.
* Instead, use primaryShortcut(), accessShortcut(), etc.
*
* @param {string} keys Modifier and keyboard keys, seperated by "+".
* @param {Object} _isMacOS isMacOS function by default; used for DI testing.
*
* @return {string} The keyboard shortcut.
*/
export function keyboardShortcut( keys, _isMacOS = isMacOS ) {
const isMac = _isMacOS();

const alt = isMac ? '⌥option' : 'Alt';
const meta = isMac ? '⌃control' : '⊞';
const primary = isMac ? '⌘' : 'Ctrl';
const shift = isMac ? '⇧shift' : 'Shift';

const replacementKeyMap = {
[ ALT ]: alt,
[ META ]: meta,
[ PRIMARY ]: primary,
[ SHIFT ]: shift,
};

return keys
.replace( /\s/g, '' )
.split( '+' )
.map( ( key ) => {
return replacementKeyMap.hasOwnProperty( key ) ?
replacementKeyMap[ key ] : key;
} )
.join( '+' )
// Because we use just the clover symbol for MacOS's "command" key, remove
// the key join character ("+") between it and the final character if that
// final character is alphanumeric. ⌘S looks nicer than ⌘+S.
.replace( /\+([a-zA-Z0-9])$/g, '⌘$1' );
}

/**
* Create an access key shortcut based on a single character.
*
* Access key combo is:
* - Control+Alt on MacOS.
* - Shift+Alt on Windows/everywhere else.
*
* @param {string} character The character for the access combination.
* @param {Object} _isMacOS isMacOS function by default; used for DI testing.
*
* @return {string} The keyboard shortcut.
*/
export function accessShortcut( character, _isMacOS = isMacOS ) {
return keyboardShortcut( accessKeyCode( character.toUpperCase(), _isMacOS ), _isMacOS );
}

export function accessKeyCode( character, _isMacOS = isMacOS ) {
const keyCombo = _isMacOS() ? `${ META }+${ ALT }` : `${ SHIFT }+${ ALT }`;
return `${ keyCombo }+${ character }`;
}

/**
* Create a modifier shortcut based on a single character.
*
* This will output Ctrl+G on Windows when "G" is supplied as an argument.
* This will output Command+G on MacOS when "G" is supplied as an argument.
*
* @param {string} character The character for the key command.
* @param {Object} _isMacOS isMacOS function by default; used for DI testing.
*
* @return {string} The keyboard shortcut.
*/
export function primaryShortcut( character, _isMacOS = isMacOS ) {
return keyboardShortcut( `${ PRIMARY }+${ character.toUpperCase() }`, _isMacOS );
}

/**
* Create an access key + primary key shortcut based on a single character.
*
* Access key combo is:
* - Control+Alt+Command on MacOS.
* - Control+Shift+Alt on Windows/everywhere else.
*
* @param {string} character The character for the access combination.
* @param {Object} _isMacOS isMacOS function by default; used for DI testing.
*
* @return {string} The keyboard shortcut.
*/
export function secondaryShortcut( character, _isMacOS = isMacOS ) {
return keyboardShortcut( secondaryKeyCode( character.toUpperCase(), _isMacOS ), _isMacOS );
}

export function secondaryKeyCode( character, _isMacOS = isMacOS ) {
const keyCombo = _isMacOS() ? `${ SHIFT }+${ ALT }+${ PRIMARY }` : `${ PRIMARY }+${ SHIFT }+${ ALT }`;
return `${ keyCombo }+${ character }`;
}
Loading

0 comments on commit c37a222

Please sign in to comment.