diff --git a/assets/src/stories-editor/components/higher-order/with-keyboard-navigation-handler.js b/assets/src/stories-editor/components/higher-order/with-keyboard-navigation-handler.js new file mode 100644 index 00000000000..99469160335 --- /dev/null +++ b/assets/src/stories-editor/components/higher-order/with-keyboard-navigation-handler.js @@ -0,0 +1,118 @@ +/** + * WordPress dependencies + */ +import { withDispatch } from '@wordpress/data'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { UP, DOWN, RIGHT, LEFT } from '@wordpress/keycodes'; +import { KeyboardShortcuts } from '@wordpress/components'; +/** + * Internal dependencies + */ +import { ALLOWED_CHILD_BLOCKS, ALLOWED_MOVABLE_BLOCKS } from '../../constants'; + +const applyWithDispatch = withDispatch( ( dispatch, props, { select } ) => { + const { isReordering } = select( 'amp/story' ); + const { getSelectedBlock } = select( 'core/block-editor' ); + const { updateBlockAttributes, removeBlock } = dispatch( 'core/block-editor' ); + const selectedBlock = getSelectedBlock(); + + const onMoveBlock = ( event ) => { + const { keyCode, target } = event; + const { classList } = target; + + if ( ! selectedBlock ) { + return; + } + + if ( classList.contains( 'editor-rich-text__editable' ) && classList.contains( 'is-selected' ) ) { + return; + } + + let top = 0; + let left = 0; + switch ( keyCode ) { + case UP: + top = -1; + break; + case DOWN: + top = 1; + break; + case RIGHT: + left = 1; + break; + case LEFT: + left = -1; + break; + default: + break; + } + + if ( ALLOWED_MOVABLE_BLOCKS.includes( selectedBlock.name ) && ( left || top ) ) { + event.preventDefault(); + const newPositionTop = selectedBlock.attributes.positionTop + top; + const newPositionLeft = selectedBlock.attributes.positionLeft + left; + updateBlockAttributes( selectedBlock.clientId, { + positionTop: newPositionTop, + positionLeft: newPositionLeft, + } ); + } + }; + + const deleteSelectedBlocks = ( event ) => { + const { target } = event; + const { classList } = target; + if ( ! selectedBlock ) { + return; + } + if ( classList.contains( 'editor-rich-text__editable' ) && classList.contains( 'is-selected' ) ) { + return; + } + event.preventDefault(); + removeBlock( selectedBlock.clientId ); + }; + + return { + isReordering, + onMoveBlock, + deleteSelectedBlocks, + }; +} ); + +/** + * Higher-order component that adds right click handler to each inner block. + * + * @return {Function} Higher-order component. + */ +export default createHigherOrderComponent( + ( BlockEdit ) => { + return applyWithDispatch( ( props ) => { + const { name, onMoveBlock, isReordering, deleteSelectedBlocks } = props; + const isPageBlock = 'amp/amp-story-page' === name; + // Add for page block and inner blocks. + if ( ! isPageBlock && ! ALLOWED_CHILD_BLOCKS.includes( name ) ) { + return ; + } + + // Not relevant for reordering. + if ( isReordering() ) { + return ; + } + + const shortcuts = { + up: onMoveBlock, + right: onMoveBlock, + down: onMoveBlock, + left: onMoveBlock, + backspace: deleteSelectedBlocks, + del: deleteSelectedBlocks, + }; + + return ( + + + + ); + } ); + }, + 'withKeyboardNavigationHandler' +); diff --git a/assets/src/stories-editor/components/index.js b/assets/src/stories-editor/components/index.js index d6744a89c32..e145f7c6244 100644 --- a/assets/src/stories-editor/components/index.js +++ b/assets/src/stories-editor/components/index.js @@ -30,6 +30,7 @@ export { default as withCroppedFeaturedImage } from './with-cropped-featured-ima export { default as withHasSelectedInnerBlock } from './higher-order/with-has-selected-inner-block'; export { default as withPageNumber } from './higher-order/with-page-number'; export { default as withRightClickHandler } from './higher-order/with-right-click-handler'; +export { default as withKeyboardNavigation } from './higher-order/with-keyboard-navigation-handler'; export { default as withStoryFeaturedImageNotice } from './higher-order/with-story-featured-image-notice'; export { default as withEditFeaturedImage } from './with-edit-featured-image'; export { default as withCustomVideoBlockEdit } from './with-custom-video-block-edit'; diff --git a/assets/src/stories-editor/index.js b/assets/src/stories-editor/index.js index 69f98ba9f29..3f9cc9da32a 100644 --- a/assets/src/stories-editor/index.js +++ b/assets/src/stories-editor/index.js @@ -39,6 +39,7 @@ import { withCallToActionValidation, withCroppedFeaturedImage, withRightClickHandler, + withKeyboardNavigation, } from './components'; import { maybeEnqueueFontStyle, @@ -317,6 +318,7 @@ addFilter( 'blocks.registerBlockType', 'ampStoryEditorBlocks/deprecateCoreBlocks addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addStorySettings', withAmpStorySettings ); addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addPageNumber', withPageNumber ); addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/rightClickHandler', withRightClickHandler ); +addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/keyboardHandler', withKeyboardNavigation ); addFilter( 'editor.BlockEdit', 'ampStoryEditorBlocks/addEditFeaturedImage', withEditFeaturedImage ); addFilter( 'editor.BlockEdit', 'ampEditorBlocks/addVideoBlockPreview', withCustomVideoBlockEdit, 9 ); addFilter( 'editor.PostFeaturedImage', 'ampStoryEditorBlocks/addFeaturedImageNotice', withStoryFeaturedImageNotice );