From 5c6d77b127ec2d41d29f48386e5cee129f8d93db Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Fri, 19 May 2023 12:14:54 +0200 Subject: [PATCH] [RNMobile] Add Group and Ungroup block actions (#50693) * Add `useConvertToGroupButtons` hook Most of the logic of this hook has been extracted from `ConvertToGroupButton` component. The main difference is that we return the configuration for the block actions instead of a component. * Add Group and Ungroup options to block actions menu * Remove `canUnwrap` option from `getBlockTransformOptions` test helper * Update tests for Group, Quote and Columns blocks --- .../block-actions-menu.native.js | 23 ++++++ .../convert-to-group-buttons/index.native.js | 80 ++++++++++++++++++- .../__snapshots__/transforms.native.js.snap | 2 +- .../src/columns/test/transforms.native.js | 8 +- .../__snapshots__/transforms.native.js.snap | 2 +- .../src/group/test/transforms.native.js | 8 +- .../__snapshots__/transforms.native.js.snap | 2 +- .../src/quote/test/transforms.native.js | 8 +- .../get-block-transform-options.js | 30 ++----- 9 files changed, 120 insertions(+), 43 deletions(-) diff --git a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js b/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js index 0183fca510af7c..08b5672738a2b7 100644 --- a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js +++ b/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js @@ -39,6 +39,10 @@ import { store as coreStore } from '@wordpress/core-data'; import { getMoversSetup } from '../block-mover/mover-description'; import { store as blockEditorStore } from '../../store'; import BlockTransformationsMenu from '../block-switcher/block-transformations-menu'; +import { + useConvertToGroupButtons, + useConvertToGroupButtonProps, +} from '../convert-to-group-buttons'; const BlockActionsMenu = ( { // Select. @@ -55,6 +59,7 @@ const BlockActionsMenu = ( { rootClientId, selectedBlockClientId, selectedBlockPossibleTransformations, + canRemove, // Dispatch. createSuccessNotice, convertToRegularBlocks, @@ -93,6 +98,17 @@ const BlockActionsMenu = ( { }, } = getMoversSetup( isStackedHorizontally, moversOptions ); + // Check if selected block is Groupable and/or Ungroupable. + const convertToGroupButtonProps = useConvertToGroupButtonProps( [ + selectedBlockClientId, + ] ); + const { isGroupable, isUngroupable } = convertToGroupButtonProps; + const showConvertToGroupButton = + ( isGroupable || isUngroupable ) && canRemove; + const convertToGroupButtons = useConvertToGroupButtons( { + ...convertToGroupButtonProps, + } ); + const allOptions = { settings: { id: 'settingsOption', @@ -229,6 +245,10 @@ const BlockActionsMenu = ( { canDuplicate && allOptions.cutButton, canDuplicate && isPasteEnabled && allOptions.pasteButton, canDuplicate && allOptions.duplicateButton, + showConvertToGroupButton && isGroupable && convertToGroupButtons.group, + showConvertToGroupButton && + isUngroupable && + convertToGroupButtons.ungroup, isReusableBlockType && innerBlockCount > 0 && allOptions.convertToRegularBlocks, @@ -327,6 +347,7 @@ export default compose( getSelectedBlockClientIds, canInsertBlockType, getTemplateLock, + canRemoveBlock, } = select( blockEditorStore ); const block = getBlock( clientId ); const blockName = getBlockName( clientId ); @@ -363,6 +384,7 @@ export default compose( const selectedBlockPossibleTransformations = selectedBlock ? getBlockTransformItems( selectedBlock, rootClientId ) : EMPTY_BLOCK_LIST; + const canRemove = canRemoveBlock( selectedBlockClientId ); const isReusableBlockType = block ? isReusableBlock( block ) : false; const reusableBlock = isReusableBlockType @@ -388,6 +410,7 @@ export default compose( rootClientId, selectedBlockClientId, selectedBlockPossibleTransformations, + canRemove, }; } ), withDispatch( diff --git a/packages/block-editor/src/components/convert-to-group-buttons/index.native.js b/packages/block-editor/src/components/convert-to-group-buttons/index.native.js index 461f67a0a4bcbe..1a85d43ff01ed0 100644 --- a/packages/block-editor/src/components/convert-to-group-buttons/index.native.js +++ b/packages/block-editor/src/components/convert-to-group-buttons/index.native.js @@ -1 +1,79 @@ -export default () => null; +/** + * WordPress dependencies + */ +import { __, _x } from '@wordpress/i18n'; +import { switchToBlockType } from '@wordpress/blocks'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; + +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../../store'; +import useConvertToGroupButtonProps from './use-convert-to-group-button-props'; + +function useConvertToGroupButtons( { + clientIds, + onUngroup, + blocksSelection, + groupingBlockName, +} ) { + const { replaceBlocks } = useDispatch( blockEditorStore ); + const { createSuccessNotice } = useDispatch( noticesStore ); + const onConvertToGroup = () => { + // Activate the `transform` on the Grouping Block which does the conversion. + const newBlocks = switchToBlockType( + blocksSelection, + groupingBlockName + ); + if ( newBlocks ) { + replaceBlocks( clientIds, newBlocks ); + } + }; + + const onConvertFromGroup = () => { + let innerBlocks = blocksSelection[ 0 ].innerBlocks; + if ( ! innerBlocks.length ) { + return; + } + if ( onUngroup ) { + innerBlocks = onUngroup( + blocksSelection[ 0 ].attributes, + blocksSelection[ 0 ].innerBlocks + ); + } + replaceBlocks( clientIds, innerBlocks ); + }; + + return { + group: { + id: 'groupButtonOption', + label: _x( 'Group', 'verb' ), + value: 'groupButtonOption', + onSelect: () => { + onConvertToGroup(); + createSuccessNotice( + // translators: displayed right after the block is grouped + __( 'Block grouped' ) + ); + }, + }, + ungroup: { + id: 'ungroupButtonOption', + label: _x( + 'Ungroup', + 'Ungrouping blocks from within a grouping block back into individual blocks within the Editor' + ), + value: 'ungroupButtonOption', + onSelect: () => { + onConvertFromGroup(); + createSuccessNotice( + // translators: displayed right after the block is ungrouped. + __( 'Block ungrouped' ) + ); + }, + }, + }; +} + +export { useConvertToGroupButtons, useConvertToGroupButtonProps }; diff --git a/packages/block-library/src/columns/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/columns/test/__snapshots__/transforms.native.js.snap index ee8eb3d1a2b164..939638c4c579e1 100644 --- a/packages/block-library/src/columns/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/columns/test/__snapshots__/transforms.native.js.snap @@ -34,7 +34,7 @@ exports[`Columns block transforms to Group block 1`] = ` " `; -exports[`Columns block transforms unwraps content 1`] = ` +exports[`Columns block transforms ungroups block 1`] = ` "

Built with modern technology.

diff --git a/packages/block-library/src/columns/test/transforms.native.js b/packages/block-library/src/columns/test/transforms.native.js index 12ae9d9c0829f2..a2103bdb86aa00 100644 --- a/packages/block-library/src/columns/test/transforms.native.js +++ b/packages/block-library/src/columns/test/transforms.native.js @@ -60,14 +60,13 @@ describe( `${ block } block transforms`, () => { expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'unwraps content', async () => { + it( 'ungroups block', async () => { const screen = await initializeEditor( { initialHtml } ); const { getByText } = screen; fireEvent.press( getBlock( screen, block ) ); await openBlockActionsMenu( screen ); - fireEvent.press( getByText( 'Transform block…' ) ); - fireEvent.press( getByText( 'Unwrap' ) ); + fireEvent.press( getByText( 'Ungroup' ) ); // The first block created is the content of the Paragraph block. const paragraph = getBlock( screen, 'Paragraph', 0 ); @@ -83,8 +82,7 @@ describe( `${ block } block transforms`, () => { const screen = await initializeEditor( { initialHtml } ); const transformOptions = await getBlockTransformOptions( screen, - block, - { canUnwrap: true } + block ); expect( transformOptions ).toHaveLength( blockTransforms.length ); } ); diff --git a/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap index 174bb9b52c2e11..ca0bd5b36ee0c4 100644 --- a/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/group/test/__snapshots__/transforms.native.js.snap @@ -20,7 +20,7 @@ exports[`Group block transforms to Columns block 1`] = ` " `; -exports[`Group block transforms unwraps content 1`] = ` +exports[`Group block transforms ungroups block 1`] = ` "

One.

diff --git a/packages/block-library/src/group/test/transforms.native.js b/packages/block-library/src/group/test/transforms.native.js index d9540dc5c51d5f..9293482f119055 100644 --- a/packages/block-library/src/group/test/transforms.native.js +++ b/packages/block-library/src/group/test/transforms.native.js @@ -44,14 +44,13 @@ describe( `${ block } block transforms`, () => { expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'unwraps content', async () => { + it( 'ungroups block', async () => { const screen = await initializeEditor( { initialHtml } ); const { getByText } = screen; fireEvent.press( getBlock( screen, block ) ); await openBlockActionsMenu( screen ); - fireEvent.press( getByText( 'Transform block…' ) ); - fireEvent.press( getByText( 'Unwrap' ) ); + fireEvent.press( getByText( 'Ungroup' ) ); // The first block created is the content of the Paragraph block. const paragraph = getBlock( screen, 'Paragraph', 0 ); @@ -67,8 +66,7 @@ describe( `${ block } block transforms`, () => { const screen = await initializeEditor( { initialHtml } ); const transformOptions = await getBlockTransformOptions( screen, - block, - { canUnwrap: true } + block ); expect( transformOptions ).toHaveLength( blockTransforms.length ); } ); diff --git a/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap index bc337691dd27a7..5b5df918f2beeb 100644 --- a/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/quote/test/__snapshots__/transforms.native.js.snap @@ -28,7 +28,7 @@ exports[`Quote block transforms to Pullquote block 1`] = ` " `; -exports[`Quote block transforms unwraps content 1`] = ` +exports[`Quote block transforms ungroups block 1`] = ` "

"This will make running your own blog a viable alternative again."

diff --git a/packages/block-library/src/quote/test/transforms.native.js b/packages/block-library/src/quote/test/transforms.native.js index 75cb887a4872be..46c4eb2b6f9727 100644 --- a/packages/block-library/src/quote/test/transforms.native.js +++ b/packages/block-library/src/quote/test/transforms.native.js @@ -36,14 +36,13 @@ describe( `${ block } block transforms`, () => { expect( getEditorHtml() ).toMatchSnapshot(); } ); - it( 'unwraps content', async () => { + it( 'ungroups block', async () => { const screen = await initializeEditor( { initialHtml } ); const { getByText } = screen; fireEvent.press( getBlock( screen, block ) ); await openBlockActionsMenu( screen ); - fireEvent.press( getByText( 'Transform block…' ) ); - fireEvent.press( getByText( 'Unwrap' ) ); + fireEvent.press( getByText( 'Ungroup' ) ); // The first block created is the content of the Paragraph block. const paragraph = getBlock( screen, 'Paragraph', 0 ); @@ -59,8 +58,7 @@ describe( `${ block } block transforms`, () => { const screen = await initializeEditor( { initialHtml } ); const transformOptions = await getBlockTransformOptions( screen, - block, - { canUnwrap: true } + block ); expect( transformOptions ).toHaveLength( blockTransforms.length ); } ); diff --git a/test/native/integration-test-helpers/get-block-transform-options.js b/test/native/integration-test-helpers/get-block-transform-options.js index e9bd35261ed740..02ca81a1d5ccc0 100644 --- a/test/native/integration-test-helpers/get-block-transform-options.js +++ b/test/native/integration-test-helpers/get-block-transform-options.js @@ -12,36 +12,18 @@ import { openBlockActionsMenu } from './open-block-actions-menu'; /** * Transforms the selected block to a specified block. * - * @param {import('@testing-library/react-native').RenderAPI} screen A Testing Library screen. - * @param {string} blockName Name of the block. - * @param {Object} [options] Configuration options. - * @param {number} [options.canUnwrap] True if the block can be unwrapped. + * @param {import('@testing-library/react-native').RenderAPI} screen A Testing Library screen. + * @param {string} blockName Name of the block. * @return {[import('react-test-renderer').ReactTestInstance]} Block transform options. */ -export const getBlockTransformOptions = async ( - screen, - blockName, - { canUnwrap = false } = {} -) => { +export const getBlockTransformOptions = async ( screen, blockName ) => { const { getByTestId, getByText } = screen; fireEvent.press( getBlock( screen, blockName ) ); await openBlockActionsMenu( screen ); fireEvent.press( getByText( 'Transform block…' ) ); - let blockTransformButtons = within( - getByTestId( 'block-transformations-menu' ) - ).getAllByRole( 'button' ); - - // Remove Unwrap option as it's not a direct block transformation. - if ( canUnwrap ) { - const unwrapButton = within( - getByTestId( 'block-transformations-menu' ) - ).getByLabelText( 'Unwrap' ); - blockTransformButtons = blockTransformButtons.filter( - ( button ) => button !== unwrapButton - ); - } - - return blockTransformButtons; + return within( getByTestId( 'block-transformations-menu' ) ).getAllByRole( + 'button' + ); };