diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index 95ddf3b0c616ee..e0847ba9b29aec 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -224,6 +224,11 @@ This is the canonical list of keyboard shortcuts: / / + + Create a group block from the selected block(s). + Ctrl+Alt+G + G + Remove multiple selected blocks. delbackspace diff --git a/packages/block-editor/src/components/block-tools/index.js b/packages/block-editor/src/components/block-tools/index.js index 3959257ecf4e86..b17a8ba2b02de5 100644 --- a/packages/block-editor/src/components/block-tools/index.js +++ b/packages/block-editor/src/components/block-tools/index.js @@ -5,6 +5,7 @@ import { useSelect, useDispatch } from '@wordpress/data'; import { Popover } from '@wordpress/components'; import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts'; import { useRef } from '@wordpress/element'; +import { switchToBlockType, store as blocksStore } from '@wordpress/blocks'; /** * Internal dependencies @@ -62,9 +63,13 @@ export default function BlockTools( { [] ); const isMatch = useShortcutEventMatch(); - const { getSelectedBlockClientIds, getBlockRootClientId } = - useSelect( blockEditorStore ); - + const { + getBlocksByClientId, + getSelectedBlockClientIds, + getBlockRootClientId, + isGroupable, + } = useSelect( blockEditorStore ); + const { getGroupingBlockName } = useSelect( blocksStore ); const { showEmptyBlockSideInserter, showBreadcrumb, @@ -74,6 +79,7 @@ export default function BlockTools( { const { duplicateBlocks, removeBlocks, + replaceBlocks, insertAfterBlock, insertBeforeBlock, selectBlock, @@ -140,6 +146,18 @@ export default function BlockTools( { // In effect, to the user this feels like deselecting the multi-selection. selectBlock( clientIds[ 0 ] ); } + } else if ( isMatch( 'core/block-editor/group', event ) ) { + const clientIds = getSelectedBlockClientIds(); + if ( clientIds.length > 1 && isGroupable( clientIds ) ) { + event.preventDefault(); + const blocks = getBlocksByClientId( clientIds ); + const groupingBlockName = getGroupingBlockName(); + const newBlocks = switchToBlockType( + blocks, + groupingBlockName + ); + replaceBlocks( clientIds, newBlocks ); + } } } diff --git a/packages/block-editor/src/components/keyboard-shortcuts/index.js b/packages/block-editor/src/components/keyboard-shortcuts/index.js index 0e0a57257becca..162ee97c56ebc4 100644 --- a/packages/block-editor/src/components/keyboard-shortcuts/index.js +++ b/packages/block-editor/src/components/keyboard-shortcuts/index.js @@ -132,6 +132,18 @@ function KeyboardShortcutsRegister() { character: 'y', }, } ); + + registerShortcut( { + name: 'core/block-editor/group', + category: 'block', + description: __( + 'Create a group block from the selected block(s).' + ), + keyCombination: { + modifier: 'primaryAlt', + character: 'g', + }, + } ); }, [ registerShortcut ] ); return null; diff --git a/test/e2e/specs/editor/various/block-editor-keyboard-shortcuts.spec.js b/test/e2e/specs/editor/various/block-editor-keyboard-shortcuts.spec.js index 0e8c5c8e7bf537..df6d1e793c2fb7 100644 --- a/test/e2e/specs/editor/various/block-editor-keyboard-shortcuts.spec.js +++ b/test/e2e/specs/editor/various/block-editor-keyboard-shortcuts.spec.js @@ -217,4 +217,68 @@ test.describe( 'Block editor keyboard shortcuts', () => { ] ); } ); } ); + + test.describe( 'create a group block from the selected blocks', () => { + test( 'should propagate properly if multiple blocks are selected.', async ( { + editor, + page, + pageUtils, + } ) => { + await addTestParagraphBlocks( { editor, page } ); + + // Multiselect via keyboard. + await pageUtils.pressKeys( 'primary+a', { times: 2 } ); + + await pageUtils.pressKeys( 'primaryAlt+g' ); // Keyboard shortcut for Insert before. + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/group', + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { content: '1st' }, + }, + { + name: 'core/paragraph', + attributes: { content: '2nd' }, + }, + { + name: 'core/paragraph', + attributes: { content: '3rd' }, + }, + ], + }, + ] ); + } ); + + test( 'should prevent if a single block is selected.', async ( { + editor, + page, + pageUtils, + } ) => { + await addTestParagraphBlocks( { editor, page } ); + const firstParagraphBlock = editor.canvas + .getByRole( 'document', { + name: 'Block: Paragraph', + } ) + .first(); + await editor.selectBlocks( firstParagraphBlock ); + await pageUtils.pressKeys( 'primaryAlt+g' ); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/paragraph', + attributes: { content: '1st' }, + }, + { + name: 'core/paragraph', + attributes: { content: '2nd' }, + }, + { + name: 'core/paragraph', + attributes: { content: '3rd' }, + }, + ] ); + } ); + } ); } );