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

Enables Block Grouping/UnGrouping using core/group #14908

Merged
merged 76 commits into from
Jun 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
ec83e29
Experiment with rudity mentory mechanic to allow Block Grouping
getdave Apr 10, 2019
84cedd1
Migrate to `core/group` due to renaming of container Block from `core…
getdave Apr 12, 2019
4499172
Adds conditionals to hide Group btn if selection is only a single `co…
getdave Apr 12, 2019
ec38f09
Adds transform and updates Group button implementation to mirror
getdave Apr 12, 2019
6a340cc
Adds and applies Group icon
getdave Apr 12, 2019
9250dfe
Adds editor shortcut for Grouping Blocks
getdave Apr 12, 2019
81bbde6
Add test to check for blocks before attempting replace
getdave Apr 12, 2019
5417118
Adds wildcard Block transforms
getdave Apr 12, 2019
10cc373
Reinstate missing createBlock dep after rebase
getdave Apr 30, 2019
0d0d259
Fix to avoid allowing single Group Block to be Grouped
getdave May 1, 2019
febf5b0
Update to use more appropriate logical negation operator for comparison
getdave May 1, 2019
6bcfe33
Extracts key transform test logic into dedicated methods
getdave May 1, 2019
b7691dc
Extract method to test for wildcard block transforms
getdave May 1, 2019
be26e72
Moves logic to allow for wildcard transform into central transform ch…
getdave May 1, 2019
f05f7bb
Adds UnGrouping mechanic
getdave May 1, 2019
322ec73
Adds UnGroup Icon
getdave May 1, 2019
68a16f2
Adds e2e test to cover basic grouping for single and multiple blocks
getdave May 3, 2019
824a168
Fix edge case with test to detect a single group container Block
getdave May 6, 2019
1effcde
Adds UnGroup keyboard shortcut
getdave May 6, 2019
45b7b80
Adds more e2e test coverage
getdave May 6, 2019
1666e97
Adds check for group block availability before displaying grouping UI
getdave May 6, 2019
0e4a2d3
Updates misnamned components
getdave May 6, 2019
d6cb0cc
Updates to preserve widest width alignment of child block on group co…
getdave May 6, 2019
3e76175
Updates to DRY out tests
getdave May 6, 2019
2627694
Updates to simplify test setup
getdave May 10, 2019
29b5ed4
Updates to simplify test assertion
getdave May 10, 2019
fd257ff
Combines test cases to simplify and reduce number of test required
getdave May 10, 2019
4bdce11
Updates to combine test for grouping and ungrouping via options menu
getdave May 10, 2019
27143f9
Adds keyboard shortcut to global keyboard shortcuts modal
getdave May 15, 2019
77a4268
Ensure correct case for group shortcut (ie: lower)
getdave May 15, 2019
2ef8e03
Add shortcut to Block settings menu dropdown items
getdave May 15, 2019
2c8adfd
Adds translation context to Group actions in menus
getdave May 16, 2019
96c11ba
Update Block Transforms fixtures to show all Blocks can transform to …
getdave May 16, 2019
607404e
Updates Keyboard Shortcut test snapshot to include Group/UnGroup
getdave May 16, 2019
2328b54
Fix to ensure Group block accounted for
getdave May 16, 2019
a99d98f
Fix Block deletion test failure via keyboard workaround
getdave May 16, 2019
0f51ec5
Fixes Remove Block button helper to be more resilient to change
getdave May 22, 2019
7b95fab
Rename function to better convey intent
getdave May 22, 2019
1ba77c0
Fixes to catch transforms which are invalid for blocks of different t…
getdave May 22, 2019
6d2d3d7
Removes redundant snapshots
getdave May 22, 2019
7328bbf
Fixes Transforms test to not over-test Group transforms
getdave May 24, 2019
c950c2e
Fixes e2e test by reinstating helper lost during rebase
getdave May 24, 2019
16c20c6
Fix to make Group transform snapshot tests more resilient to change
getdave May 24, 2019
587ec4b
Updates to DRY out test and reduce test redundancy and performance
getdave May 24, 2019
8074131
Adds unit tests to cover new conditionals added for wildcard blocks t…
getdave May 31, 2019
024cdae
Fixes capitalisation of UnGroup to Ungroup
getdave May 31, 2019
7453ef2
Updates doc block to reflect possible nullable return type
getdave May 31, 2019
1b3f357
Updates Readme with doc update
getdave May 31, 2019
dbac411
Updates to remove unwanted comments
getdave May 31, 2019
36fda5a
Updates capitalisatoin of “wildcard”
getdave May 31, 2019
358bc1c
Update comment with more context for future maintainers
getdave May 31, 2019
2b229fc
Updates to remove unwanted level of context on the translation
getdave May 31, 2019
c3bdf0f
Adds tests to cover isWildcardBlockTransform
getdave May 31, 2019
b2519fa
Adds tests for isContainerGroupBlock function
getdave May 31, 2019
b474df5
Fixes logic around multi blocks of same type and adds tests
getdave May 31, 2019
8c6ccd3
Adds new generator based API signature to Block transforms
getdave Jun 3, 2019
a30367d
Adds new apply option method to transform API
getdave Jun 3, 2019
1dc6c6d
Updates Block Reg docs to cover wildcard transforms
getdave Jun 3, 2019
1981661
Updates changelog to document wildcards and transform apply option
getdave Jun 3, 2019
e8f7506
Fix linting error introduce in rebase
getdave Jun 3, 2019
5570049
Fixes test util to avoid infinite loops
getdave Jun 4, 2019
8cb7c2f
Renames apply to convert to avoid confusion with Func.apply
getdave Jun 4, 2019
4254769
Fixes unecessary additional var introduced during debugging
getdave Jun 4, 2019
60bb8a7
Fix convert API to match established patterns for consistency
getdave Jun 4, 2019
91f1868
Fixes error in docs
getdave Jun 4, 2019
a579c5f
Fixes doc blocks to match coding style
getdave Jun 4, 2019
b6f7f55
Fixes icon size and color in dropdown for Group/Ungroup
getdave Jun 4, 2019
4707009
Updates to remove keyboard shortcuts
getdave Jun 4, 2019
afec594
Updates snapshot to account for removing keyboard shortcuts
getdave Jun 4, 2019
5dd3452
Removes e2e tests covering keyboard shortcuts
getdave Jun 4, 2019
abbcd4e
Fixes unwanted check introduced during debugging
getdave Jun 5, 2019
b9350e2
Updates to collapse spaces in doc blocks
getdave Jun 5, 2019
efa7ac6
Fixes isWildcardBlockTransform to test for Array-ness
getdave Jun 5, 2019
c6ee214
Fixes incorrect capitalisation of “UnGroup” to “Ungroup”
getdave Jun 5, 2019
f9fd251
Updates to remove redundant isMultiBlockSelection util function
getdave Jun 5, 2019
1a938ad
Reinstate missing Ungroup icon
getdave Jun 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,40 @@ transforms: {
```
{% end %}

In addition to accepting an array of known block types, the `blocks` option also accepts a "wildcard" (`"*"`). This allows for transformations which apply to _all_ block types (eg: all blocks can transform into `core/group`):

{% codetabs %}
{% ES5 %}
```js
transforms: {
from: [
{
type: 'block',
blocks: [ '*' ], // wildcard - match any block
transform: function( attributes, innerBlocks ) {
getdave marked this conversation as resolved.
Show resolved Hide resolved
// transform logic here
},
},
],
},
```
{% ESNext %}
```js
transforms: {
from: [
{
type: 'block',
blocks: [ '*' ], // wildcard - match any block
transform: ( attributes, innerBlocks ) => {
// transform logic here
},
},
],
},
```
{% end %}


A block with innerBlocks can also be transformed from and to another block with innerBlocks.

{% codetabs %}
Expand Down
40 changes: 39 additions & 1 deletion packages/block-editor/src/components/block-actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { castArray, first, last, every } from 'lodash';
*/
import { compose } from '@wordpress/compose';
import { withSelect, withDispatch } from '@wordpress/data';
import { cloneBlock, hasBlockSupport } from '@wordpress/blocks';
import { cloneBlock, hasBlockSupport, switchToBlockType } from '@wordpress/blocks';

function BlockActions( {
onDuplicate,
onRemove,
onInsertBefore,
onInsertAfter,
onGroup,
onUngroup,
isLocked,
canDuplicate,
children,
Expand All @@ -24,6 +26,8 @@ function BlockActions( {
onRemove,
onInsertAfter,
onInsertBefore,
onGroup,
onUngroup,
isLocked,
canDuplicate,
} );
Expand Down Expand Up @@ -65,6 +69,7 @@ export default compose( [
multiSelect,
removeBlocks,
insertDefaultBlock,
replaceBlocks,
} = dispatch( 'core/block-editor' );

return {
Expand Down Expand Up @@ -107,6 +112,39 @@ export default compose( [
insertDefaultBlock( {}, rootClientId, lastSelectedIndex + 1 );
}
},
onGroup() {
if ( ! blocks.length ) {
return;
}

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

if ( ! newBlocks ) {
return;
}
replaceBlocks(
clientIds,
newBlocks
);
},

onUngroup() {
if ( ! blocks.length ) {
return;
}

const innerBlocks = blocks[ 0 ].innerBlocks;

if ( ! innerBlocks.length ) {
return;
}

replaceBlocks(
clientIds,
innerBlocks
);
},
};
} ),
] )( BlockActions );
40 changes: 40 additions & 0 deletions packages/block-library/src/group/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { createBlock } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -25,6 +26,45 @@ export const settings = {
anchor: true,
html: false,
},

transforms: {
getdave marked this conversation as resolved.
Show resolved Hide resolved
from: [
{
type: 'block',
isMultiBlock: true,
blocks: [ '*' ],
convert( blocks ) {
// Avoid transforming a single `core/group` Block
if ( blocks.length === 1 && blocks[ 0 ].name === 'core/group' ) {
return;
}

const alignments = [ 'wide', 'full' ];

// Determine the widest setting of all the blocks to be grouped
const widestAlignment = blocks.reduce( ( result, block ) => {
const { align } = block.attributes;
return alignments.indexOf( align ) > alignments.indexOf( result ) ? align : result;
}, undefined );

// Clone the Blocks to be Grouped
// Failing to create new block references causes the original blocks
// to be replaced in the switchToBlockType call thereby meaning they
// are removed both from their original location and within the
// new group block.
const groupInnerBlocks = blocks.map( ( block ) => {
return createBlock( block.name, block.attributes, block.innerBlocks );
} );

return createBlock( 'core/group', {
align: widestAlignment,
}, groupInnerBlocks );
},
},

],
},

edit,
save,
};
2 changes: 2 additions & 0 deletions packages/blocks/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
### New Feature

- Added a default implementation for `save` setting in `registerBlockType` which saves no markup in the post content.
- Added wildcard block transforms which allows for transforming all/any blocks in another block.
- Added `convert()` method option to `transforms` definition. It receives complete block object(s) as it's argument(s). It is now preferred over the older `transform()` (note that `transform()` is still fully supported).

## 6.1.0 (2019-03-06)

Expand Down
2 changes: 1 addition & 1 deletion packages/blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ _Parameters_

_Returns_

- `Array`: Array of blocks.
- `?Array`: Array of blocks or null.

<a name="synchronizeBlocksWithTemplate" href="#synchronizeBlocksWithTemplate">#</a> **synchronizeBlocksWithTemplate**

Expand Down
104 changes: 84 additions & 20 deletions packages/blocks/src/api/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
filter,
first,
flatMap,
has,
uniq,
isFunction,
isEmpty,
Expand Down Expand Up @@ -117,26 +118,41 @@ const isPossibleTransformForSource = ( transform, direction, blocks ) => {
return false;
getdave marked this conversation as resolved.
Show resolved Hide resolved
}

// If multiple blocks are selected, only multi block transforms are allowed.
// If multiple blocks are selected, only multi block transforms
// or wildcard transforms are allowed.
const isMultiBlock = blocks.length > 1;
const isValidForMultiBlocks = ! isMultiBlock || transform.isMultiBlock;
const firstBlockName = first( blocks ).name;
const isValidForMultiBlocks = isWildcardBlockTransform( transform ) || ! isMultiBlock || transform.isMultiBlock;
if ( ! isValidForMultiBlocks ) {
return false;
}

// Check non-wildcard transforms to ensure that transform is valid
// for a block selection of multiple blocks of different types
if ( ! isWildcardBlockTransform( transform ) && ! every( blocks, { name: firstBlockName } ) ) {
return false;
}

// Only consider 'block' type transforms as valid.
const isBlockType = transform.type === 'block';
if ( ! isBlockType ) {
return false;
}

// Check if the transform's block name matches the source block only if this is a transform 'from'.
// Check if the transform's block name matches the source block (or is a wildcard)
// only if this is a transform 'from'.
const sourceBlock = first( blocks );
const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1;
const hasMatchingName = direction !== 'from' || transform.blocks.indexOf( sourceBlock.name ) !== -1 || isWildcardBlockTransform( transform );
if ( ! hasMatchingName ) {
return false;
}

// Don't allow single 'core/group' blocks to be transformed into
getdave marked this conversation as resolved.
Show resolved Hide resolved
// a 'core/group' block.
if ( ! isMultiBlock && isContainerGroupBlock( sourceBlock.name ) && isContainerGroupBlock( transform.blockName ) ) {
return false;
}

// If the transform has a `isMatch` function specified, check that it returns true.
if ( isFunction( transform.isMatch ) ) {
const attributes = transform.isMultiBlock ? blocks.map( ( block ) => block.attributes ) : sourceBlock.attributes;
Expand Down Expand Up @@ -171,7 +187,9 @@ const getBlockTypesForPossibleFromTransforms = ( blocks ) => {

return !! findTransform(
fromTransforms,
( transform ) => isPossibleTransformForSource( transform, 'from', blocks )
( transform ) => {
return isPossibleTransformForSource( transform, 'from', blocks );
}
);
},
);
Expand Down Expand Up @@ -199,7 +217,9 @@ const getBlockTypesForPossibleToTransforms = ( blocks ) => {
// filter all 'to' transforms to find those that are possible.
const possibleTransforms = filter(
transformsTo,
( transform ) => isPossibleTransformForSource( transform, 'to', blocks )
( transform ) => {
return transform && isPossibleTransformForSource( transform, 'to', blocks );
}
);

// Build a list of block names using the possible 'to' transforms.
Expand All @@ -212,6 +232,45 @@ const getBlockTypesForPossibleToTransforms = ( blocks ) => {
return blockNames.map( ( name ) => getBlockType( name ) );
};

/**
* Determines whether transform is a "block" type
* and if so whether it is a "wildcard" transform
* ie: targets "any" block type
*
* @param {Object} t the Block transform object
*
* @return {boolean} whether transform is a wildcard transform
*/
export const isWildcardBlockTransform = ( t ) => t && t.type === 'block' && Array.isArray( t.blocks ) && t.blocks.includes( '*' );

/**
* Determines whether the given Block is the core Block which
* acts as a container Block for other Blocks as part of the
* Grouping mechanics
*
* @param {string} name the name of the Block to test against
*
* @return {boolean} whether or not the Block is the container Block type
*/
export const isContainerGroupBlock = ( name ) => name === 'core/group';

/**
* Determines whether the provided Blocks are of the same type
* (eg: all `core/paragraph`).
*
* @param {Array} blocksArray the Block definitions
getdave marked this conversation as resolved.
Show resolved Hide resolved
*
* @return {boolean} whether or not the given Blocks pass the criteria
*/
export const isBlockSelectionOfSameType = ( blocksArray = [] ) => {
if ( ! blocksArray.length ) {
return false;
}
const sourceName = blocksArray[ 0 ].name;

return every( blocksArray, [ 'name', sourceName ] );
};

/**
* Returns an array of block types that the set of blocks received as argument
* can be transformed into.
Expand All @@ -225,12 +284,6 @@ export function getPossibleBlockTransformations( blocks ) {
return [];
}

const sourceBlock = first( blocks );
const isMultiBlock = blocks.length > 1;
if ( isMultiBlock && ! every( blocks, { name: sourceBlock.name } ) ) {
return [];
}

const blockTypesForFromTransforms = getBlockTypesForPossibleFromTransforms( blocks );
const blockTypesForToTransforms = getBlockTypesForPossibleToTransforms( blocks );

Expand Down Expand Up @@ -313,30 +366,34 @@ export function getBlockTransforms( direction, blockTypeOrName ) {
* @param {Array|Object} blocks Blocks array or block object.
* @param {string} name Block name.
*
* @return {Array} Array of blocks.
* @return {?Array} Array of blocks or null.
*/
export function switchToBlockType( blocks, name ) {
const blocksArray = castArray( blocks );
const isMultiBlock = blocksArray.length > 1;
const firstBlock = blocksArray[ 0 ];
const sourceName = firstBlock.name;

if ( isMultiBlock && ! every( blocksArray, ( block ) => ( block.name === sourceName ) ) ) {
// Unless it's a `core/group` Block then for multi block selections
// check that all Blocks are of the same type otherwise
// we can't run a conversion
if ( ! isContainerGroupBlock( name ) && isMultiBlock && ! isBlockSelectionOfSameType( blocksArray ) ) {
return null;
}

// Find the right transformation by giving priority to the "to"
// transformation.
const transformationsFrom = getBlockTransforms( 'from', name );
const transformationsTo = getBlockTransforms( 'to', sourceName );

const transformation =
findTransform(
transformationsTo,
( t ) => t.type === 'block' && t.blocks.indexOf( name ) !== -1 && ( ! isMultiBlock || t.isMultiBlock )
( t ) => t.type === 'block' && ( ( isWildcardBlockTransform( t ) ) || t.blocks.indexOf( name ) !== -1 ) && ( ! isMultiBlock || t.isMultiBlock )
) ||
findTransform(
transformationsFrom,
( t ) => t.type === 'block' && t.blocks.indexOf( sourceName ) !== -1 && ( ! isMultiBlock || t.isMultiBlock )
( t ) => t.type === 'block' && ( ( isWildcardBlockTransform( t ) ) || t.blocks.indexOf( sourceName ) !== -1 ) && ( ! isMultiBlock || t.isMultiBlock )
);

// Stop if there is no valid transformation.
Expand All @@ -345,11 +402,18 @@ export function switchToBlockType( blocks, name ) {
}

let transformationResults;

if ( transformation.isMultiBlock ) {
transformationResults = transformation.transform(
blocksArray.map( ( currentBlock ) => currentBlock.attributes ),
blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks )
);
if ( has( transformation, 'convert' ) ) {
transformationResults = transformation.convert( blocksArray );
} else {
transformationResults = transformation.transform(
blocksArray.map( ( currentBlock ) => currentBlock.attributes ),
blocksArray.map( ( currentBlock ) => currentBlock.innerBlocks ),
);
}
} else if ( has( transformation, 'convert' ) ) {
transformationResults = transformation.convert( firstBlock );
} else {
transformationResults = transformation.transform( firstBlock.attributes, firstBlock.innerBlocks );
}
Expand Down
Loading