Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 12 commits into from
May 10, 2024
Merged
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
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 ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the check that the length is greater than one? If a single block is selected or focused, should we still be able to group it? In the editor canvas, I think it's good to only allow it in a multi-selection, but it could still be useful to allow it in the list view, otherwise this is what happens when using the shortcut over a focused item that isn't part of the selection:

2024-05-09.11.10.36.mp4

The above video demoes grouping two blocks, then moving up to a block outside the selection and pressing the keyboard shortcut.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for checking that the length is greater than 1 is to be consistent with the editor canvas. When I run the shortcut on a single item that has focus and is not in a selection, the browser's search window opens. I think this is the expected behavior for now, but is the same behavior happening in your environment?

browser-search

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's what's happening for me, too. All good to leave it as-is for now, and we can always revisit in follow-ups if folks run into any issues. The main value is to group multiple items together so I doubt that in practice anyone will really struggle with the current behaviour.

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
Loading