Skip to content

Commit

Permalink
Block editor: add a keyboard shortcut to create group from the select…
Browse files Browse the repository at this point in the history
…ed blocks (#46972)

* Block editor: add a keyboard shortcut to create group from the selected blocks

* Change modifier key

* Update faq.md

* Display shortcut in the block toolbar options menu

* Add spoken message

* Enable shortcut in the list view

* Keep focus within the list view

Co-authored-by: t-hamano <[email protected]>
Co-authored-by: jasmussen <[email protected]>
Co-authored-by: talldan <[email protected]>
Co-authored-by: andrewserong <[email protected]>
Co-authored-by: jameskoster <[email protected]>
Co-authored-by: bacoords <[email protected]>
Co-authored-by: alexstine <[email protected]>
Co-authored-by: pagelab <[email protected]>
Co-authored-by: bissy <[email protected]>
  • Loading branch information
10 people authored May 10, 2024
1 parent f67dcee commit f4cf64a
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 5 deletions.
5 changes: 5 additions & 0 deletions docs/getting-started/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,11 @@ This is the canonical list of keyboard shortcuts:
<td><kbd>/</kbd></td>
<td><kbd>/</kbd></td>
</tr>
<tr>
<td>Create a group block from the selected multiple blocks.</td>
<td><kbd>Ctrl</kbd>+<kbd>G</kbd></td>
<td><kbd>⌘</kbd><kbd>⇧</kbd><kbd>G</kbd></td>
</tr>
<tr>
<td>Remove multiple selected blocks.</td>
<td><kbd>del</kbd><kbd>backspace</kbd></td>
Expand Down
27 changes: 24 additions & 3 deletions packages/block-editor/src/components/block-tools/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { isTextField } from '@wordpress/dom';
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';
import { speak } from '@wordpress/a11y';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
Expand Down Expand Up @@ -64,9 +67,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,
Expand All @@ -76,6 +83,7 @@ export default function BlockTools( {
const {
duplicateBlocks,
removeBlocks,
replaceBlocks,
insertAfterBlock,
insertBeforeBlock,
selectBlock,
Expand Down Expand Up @@ -159,6 +167,19 @@ export default function BlockTools( {
}
event.preventDefault();
expandBlock( clientId );
} 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 );
speak( __( 'Selected blocks are grouped.' ) );
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import { MenuItem } from '@wordpress/components';
import { _x } from '@wordpress/i18n';
import { switchToBlockType } from '@wordpress/blocks';
import { useDispatch } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { displayShortcut } from '@wordpress/keycodes';

/**
* Internal dependencies
Expand All @@ -22,6 +23,7 @@ function ConvertToGroupButton( {
groupingBlockName,
onClose = () => {},
} ) {
const { getSelectedBlockClientIds } = useSelect( blockEditorStore );
const { replaceBlocks } = useDispatch( blockEditorStore );
const onConvertToGroup = () => {
// Activate the `transform` on the Grouping Block which does the conversion.
Expand Down Expand Up @@ -52,10 +54,17 @@ function ConvertToGroupButton( {
return null;
}

const selectedBlockClientIds = getSelectedBlockClientIds();

return (
<>
{ isGroupable && (
<MenuItem
shortcut={
selectedBlockClientIds.length > 1
? displayShortcut.primary( 'g' )
: undefined
}
onClick={ () => {
onConvertToGroup();
onClose();
Expand Down
12 changes: 12 additions & 0 deletions packages/block-editor/src/components/keyboard-shortcuts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ function KeyboardShortcutsRegister() {
character: 'l',
},
} );

registerShortcut( {
name: 'core/block-editor/group',
category: 'block',
description: __(
'Create a group block from the selected multiple blocks.'
),
keyCombination: {
modifier: 'primary',
character: 'g',
},
} );
}, [ registerShortcut ] );

return null;
Expand Down
27 changes: 26 additions & 1 deletion packages/block-editor/src/components/list-view/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { hasBlockSupport } from '@wordpress/blocks';
import {
hasBlockSupport,
switchToBlockType,
store as blocksStore,
} from '@wordpress/blocks';
import {
__experimentalTreeGridCell as TreeGridCell,
__experimentalTreeGridItem as TreeGridItem,
Expand All @@ -25,6 +29,7 @@ import { __ } from '@wordpress/i18n';
import { BACKSPACE, DELETE } from '@wordpress/keycodes';
import isShallowEqual from '@wordpress/is-shallow-equal';
import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from '@wordpress/keyboard-shortcuts';
import { speak } from '@wordpress/a11y';

/**
* Internal dependencies
Expand Down Expand Up @@ -85,6 +90,7 @@ function ListViewBlock( {
toggleBlockHighlight,
duplicateBlocks,
multiSelect,
replaceBlocks,
removeBlocks,
insertAfterBlock,
insertBeforeBlock,
Expand All @@ -100,7 +106,9 @@ function ListViewBlock( {
getBlockParents,
getBlocksByClientId,
canRemoveBlocks,
isGroupable,
} = useSelect( blockEditorStore );
const { getGroupingBlockName } = useSelect( blocksStore );

const blockInformation = useBlockDisplayInformation( clientId );

Expand Down Expand Up @@ -324,6 +332,23 @@ function ListViewBlock( {
collapseAll();
// Expand all parents of the current block.
expand( blockParents );
} else if ( isMatch( 'core/block-editor/group', event ) ) {
const { blocksToUpdate } = getBlocksToUpdate();
if ( blocksToUpdate.length > 1 && isGroupable( blocksToUpdate ) ) {
event.preventDefault();
const blocks = getBlocksByClientId( blocksToUpdate );
const groupingBlockName = getGroupingBlockName();
const newBlocks = switchToBlockType(
blocks,
groupingBlockName
);
replaceBlocks( blocksToUpdate, newBlocks );
speak( __( 'Selected blocks are grouped.' ) );
const newlySelectedBlocks = getSelectedBlockClientIds();
// Focus the first block of the newly inserted blocks, to keep focus within the list view.
setOpenedBlockSettingsMenu( undefined );
updateFocusAndSelection( newlySelectedBlocks[ 0 ], false );
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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( 'primary+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( 'primary+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' },
},
] );
} );
} );
} );
39 changes: 39 additions & 0 deletions test/e2e/specs/editor/various/list-view.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,45 @@ test.describe( 'List View', () => {
] );
} );

test( 'should create a group block from the selected multiple blocks', async ( {
editor,
pageUtils,
listViewUtils,
} ) => {
// Insert some blocks of different types.
await editor.insertBlock( { name: 'core/paragraph' } );
await editor.insertBlock( { name: 'core/heading' } );
await editor.insertBlock( { name: 'core/file' } );

await listViewUtils.openListView();

// Group Heading and File blocks.
await pageUtils.pressKeys( 'shift+ArrowUp' );
await pageUtils.pressKeys( 'primary+g' );
await expect
.poll( listViewUtils.getBlocksWithA11yAttributes )
.toMatchObject( [
{ name: 'core/paragraph', selected: false, focused: false },
{
name: 'core/group',
selected: true,
focused: true,
innerBlocks: [
{
name: 'core/heading',
selected: false,
focused: false,
},
{
name: 'core/file',
selected: false,
focused: false,
},
],
},
] );
} );

test( 'block settings dropdown menu', async ( {
editor,
page,
Expand Down

0 comments on commit f4cf64a

Please sign in to comment.