Skip to content

Commit

Permalink
Add new block commands (#52509)
Browse files Browse the repository at this point in the history
* inital attempt to bring block commands and transforms

* don't register commands if no blocks are selected

* move to top level editor components

* cleanup copy pasta from the block editor components

* fix rebase artefact

* update icons to match the action, remove 'block' suffix for command names, use add instead of insert

* remove conflict marker

* packages update

* fix copy command

* Revert "fix copy command"

This reverts commit e6d70b2.

* remove copy and paste styles actions

---------

Co-authored-by: scruffian <[email protected]>
  • Loading branch information
draganescu and scruffian committed Aug 14, 2023
1 parent 5a238f9 commit 5a97d8e
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 1 deletion.
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,10 @@ _Related_

Private @wordpress/block-editor APIs.

### ReusableBlocksRenameHint

Undocumented declaration.

### RichText

_Related_
Expand Down Expand Up @@ -789,6 +793,10 @@ _Related_

- <https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/url-popover/README.md>

### useBlockCommands

Undocumented declaration.

### useBlockDisplayInformation

Hook used to try to find a matching block variation and return the appropriate information for display reasons. In order to to try to find a match we need to things: 1. Block's client id to extract it's current attributes. 2. A block variation should have set `isActive` prop to a proper function.
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@wordpress/api-fetch": "file:../api-fetch",
"@wordpress/blob": "file:../blob",
"@wordpress/blocks": "file:../blocks",
"@wordpress/commands": "file:../commands",
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
"@wordpress/data": "file:../data",
Expand Down
6 changes: 6 additions & 0 deletions packages/block-editor/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,9 @@ export { useBlockEditingMode } from './block-editing-mode';

export { default as BlockEditorProvider } from './provider';
export { default as useSetting } from './use-setting';
export { useBlockCommands } from './use-block-commands';

/*
* The following rename hint component can be removed in 6.4.
*/
export { default as ReusableBlocksRenameHint } from './inserter/reusable-block-rename-hint';
284 changes: 284 additions & 0 deletions packages/block-editor/src/components/use-block-commands/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import {
hasBlockSupport,
store as blocksStore,
switchToBlockType,
isTemplatePart,
} from '@wordpress/blocks';
import { useSelect, useDispatch } from '@wordpress/data';
import { useCommandLoader } from '@wordpress/commands';
import {
copy,
edit as remove,
create as add,
group,
ungroup,
moveTo as move,
} from '@wordpress/icons';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';

export const useTransformCommands = () => {
const { clientIds } = useSelect( ( select ) => {
const { getSelectedBlockClientIds } = select( blockEditorStore );
const selectedBlockClientIds = getSelectedBlockClientIds();

return {
clientIds: selectedBlockClientIds,
};
}, [] );
const blocks = useSelect(
( select ) =>
select( blockEditorStore ).getBlocksByClientId( clientIds ),
[ clientIds ]
);
const { replaceBlocks, multiSelect } = useDispatch( blockEditorStore );
const { possibleBlockTransformations, canRemove } = useSelect(
( select ) => {
const {
getBlockRootClientId,
getBlockTransformItems,
canRemoveBlocks,
} = select( blockEditorStore );
const rootClientId = getBlockRootClientId(
Array.isArray( clientIds ) ? clientIds[ 0 ] : clientIds
);
return {
possibleBlockTransformations: getBlockTransformItems(
blocks,
rootClientId
),
canRemove: canRemoveBlocks( clientIds, rootClientId ),
};
},
[ clientIds, blocks ]
);

const isTemplate = blocks.length === 1 && isTemplatePart( blocks[ 0 ] );

function selectForMultipleBlocks( insertedBlocks ) {
if ( insertedBlocks.length > 1 ) {
multiSelect(
insertedBlocks[ 0 ].clientId,
insertedBlocks[ insertedBlocks.length - 1 ].clientId
);
}
}

// Simple block tranformation based on the `Block Transforms` API.
function onBlockTransform( name ) {
const newBlocks = switchToBlockType( blocks, name );
replaceBlocks( clientIds, newBlocks );
selectForMultipleBlocks( newBlocks );
}

/**
* The `isTemplate` check is a stopgap solution here.
* Ideally, the Transforms API should handle this
* by allowing to exclude blocks from wildcard transformations.
*/
const hasPossibleBlockTransformations =
!! possibleBlockTransformations.length && canRemove && ! isTemplate;

if (
! clientIds ||
clientIds.length < 1 ||
! hasPossibleBlockTransformations
) {
return { isLoading: false, commands: [] };
}

const commands = possibleBlockTransformations.map( ( transformation ) => {
const { name, title, icon } = transformation;
return {
name: 'core/block-editor/transform-to-' + name.replace( '/', '-' ),
// translators: %s: block title/name.
label: sprintf( __( 'Transform to %s' ), title ),
icon: icon.src,
callback: ( { close } ) => {
onBlockTransform( name );
close();
},
};
} );

return { isLoading: false, commands };
};

const useActionsCommands = () => {
const { clientIds } = useSelect( ( select ) => {
const { getSelectedBlockClientIds } = select( blockEditorStore );
const selectedBlockClientIds = getSelectedBlockClientIds();

return {
clientIds: selectedBlockClientIds,
};
}, [] );
const {
canInsertBlockType,
getBlockRootClientId,
getBlocksByClientId,
canMoveBlocks,
canRemoveBlocks,
} = useSelect( blockEditorStore );
const { getDefaultBlockName, getGroupingBlockName } =
useSelect( blocksStore );

const blocks = getBlocksByClientId( clientIds );
const rootClientId = getBlockRootClientId( clientIds[ 0 ] );

const canDuplicate = blocks.every( ( block ) => {
return (
!! block &&
hasBlockSupport( block.name, 'multiple', true ) &&
canInsertBlockType( block.name, rootClientId )
);
} );

const canInsertDefaultBlock = canInsertBlockType(
getDefaultBlockName(),
rootClientId
);

const canMove = canMoveBlocks( clientIds, rootClientId );
const canRemove = canRemoveBlocks( clientIds, rootClientId );

const {
removeBlocks,
replaceBlocks,
duplicateBlocks,
insertAfterBlock,
insertBeforeBlock,
setBlockMovingClientId,
setNavigationMode,
selectBlock,
} = useDispatch( blockEditorStore );

const onDuplicate = () => {
if ( ! canDuplicate ) {
return;
}
return duplicateBlocks( clientIds, true );
};
const onRemove = () => {
if ( ! canRemove ) {
return;
}
return removeBlocks( clientIds, true );
};
const onAddBefore = () => {
if ( ! canInsertDefaultBlock ) {
return;
}
const clientId = Array.isArray( clientIds ) ? clientIds[ 0 ] : clientId;
insertBeforeBlock( clientId );
};
const onAddAfter = () => {
if ( ! canInsertDefaultBlock ) {
return;
}
const clientId = Array.isArray( clientIds )
? clientIds[ clientIds.length - 1 ]
: clientId;
insertAfterBlock( clientId );
};
const onMoveTo = () => {
if ( ! canMove ) {
return;
}
setNavigationMode( true );
selectBlock( clientIds[ 0 ] );
setBlockMovingClientId( clientIds[ 0 ] );
};
const onGroup = () => {
if ( ! blocks.length ) {
return;
}

const groupingBlockName = getGroupingBlockName();

// Activate the `transform` on `core/group` which does the conversion.
const newBlocks = switchToBlockType( blocks, groupingBlockName );

if ( ! newBlocks ) {
return;
}
replaceBlocks( clientIds, newBlocks );
};
const onUngroup = () => {
if ( ! blocks.length ) {
return;
}

const innerBlocks = blocks[ 0 ].innerBlocks;

if ( ! innerBlocks.length ) {
return;
}

replaceBlocks( clientIds, innerBlocks );
};

if ( ! clientIds || clientIds.length < 1 ) {
return { isLoading: false, commands: [] };
}

const icons = {
ungroup,
group,
move,
add,
remove,
duplicate: copy,
};

const commands = [
onUngroup,
onGroup,
onMoveTo,
onAddAfter,
onAddBefore,
onRemove,
onDuplicate,
].map( ( callback ) => {
const action = callback.name
.replace( 'on', '' )
.replace( /([a-z])([A-Z])/g, '$1 $2' );

return {
name: 'core/block-editor/action-' + callback.name,
// translators: %s: type of the command.
label: action,
icon: icons[
callback.name
.replace( 'on', '' )
.match( /[A-Z]{1}[a-z]*/ )
.toString()
.toLowerCase()
],
callback: ( { close } ) => {
callback();
close();
},
};
} );

return { isLoading: false, commands };
};

export const useBlockCommands = () => {
useCommandLoader( {
name: 'core/block-editor/blockTransforms',
hook: useTransformCommands,
} );
useCommandLoader( {
name: 'core/block-editor/blockActions',
hook: useActionsCommands,
} );
};
2 changes: 2 additions & 0 deletions packages/edit-post/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '@wordpress/editor';
import { useSelect, useDispatch } from '@wordpress/data';
import {
useBlockCommands,
BlockBreadcrumb,
privateApis as blockEditorPrivateApis,
} from '@wordpress/block-editor';
Expand Down Expand Up @@ -72,6 +73,7 @@ const interfaceLabels = {
};

function Layout() {
useBlockCommands();
const isMobileViewport = useViewportMatch( 'medium', '<' );
const isHugeViewport = useViewportMatch( 'huge', '>=' );
const isLargeViewport = useViewportMatch( 'large' );
Expand Down
6 changes: 5 additions & 1 deletion packages/edit-site/src/components/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import {
privateApis as commandsPrivateApis,
} from '@wordpress/commands';
import { store as preferencesStore } from '@wordpress/preferences';
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
import {
privateApis as blockEditorPrivateApis,
useBlockCommands,
} from '@wordpress/block-editor';
import { privateApis as routerPrivateApis } from '@wordpress/router';
import { privateApis as coreCommandsPrivateApis } from '@wordpress/core-commands';

Expand Down Expand Up @@ -66,6 +69,7 @@ export default function Layout() {
useCommands();
useEditModeCommands();
useCommonCommands();
useBlockCommands();

const hubRef = useRef();
const { params } = useLocation();
Expand Down

1 comment on commit 5a97d8e

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 5a97d8e.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/5855920381
📝 Reported issues:

Please sign in to comment.