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 Hooks: Set ignoredHookedBlocks metada attr upon insertion #58553

Merged
merged 24 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
48 changes: 48 additions & 0 deletions docs/reference-guides/data/data-core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,54 @@ _Returns_

- `string?`: Name of the block for handling the grouping of blocks.

### getHookedBlocks

Returns the hooked blocks for a given anchor block.

Given an anchor block name, returns an object whose keys are relative positions, and whose values are arrays of block names that are hooked to the anchor block at that relative position.

_Usage_

```js
import { store as blocksStore } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';

const ExampleComponent = () => {
const hookedBlockNames = useSelect(
( select ) =>
select( blocksStore ).getHookedBlocks( 'core/navigation' ),
[]
);

return (
<ul>
{ Object.keys( hookedBlockNames ).length &&
Object.keys( hookedBlockNames ).map( ( relativePosition ) => (
<li key={ relativePosition }>
{ relativePosition }>
<ul>
{ hookedBlockNames[ relativePosition ].map(
( hookedBlock ) => (
<li key={ hookedBlock }>{ hookedBlock }</li>
)
) }
</ul>
</li>
) ) }
</ul>
);
};
```

_Parameters_

- _state_ `Object`: Data state.
- _blockName_ `string`: Anchor block type name.

_Returns_

- `Object`: Lists of hooked block names for each relative position.

### getUnregisteredFallbackBlockName

Returns the name of the block for handling unregistered blocks.
Expand Down
10 changes: 9 additions & 1 deletion packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getBlockType,
getBlockTypes,
getBlockVariations,
getHookedBlocks,
hasBlockSupport,
getPossibleBlockTransformations,
parse,
Expand Down Expand Up @@ -1936,9 +1937,16 @@ const buildBlockTypeItem =
blockType.name,
'inserter'
);

const ignoredHookedBlocks = [
...new Set( Object.values( getHookedBlocks( id ) ).flat() ),
];

return {
...blockItemBase,
initialAttributes: {},
initialAttributes: ignoredHookedBlocks.length
? { metadata: { ignoredHookedBlocks } }
: {},
description: blockType.description,
category: blockType.category,
keywords: blockType.keywords,
Expand Down
14 changes: 14 additions & 0 deletions packages/blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,20 @@ _Returns_

- `?string`: Block name.

### getHookedBlocks

Returns the hooked blocks for a given anchor block.

Given an anchor block name, returns an object whose keys are relative positions, and whose values are arrays of block names that are hooked to the anchor block at that relative position.

_Parameters_

- _name_ `string`: Anchor block name.

_Returns_

- `Object`: Lists of hooked block names for each relative position.

### getPhrasingContentSchema

Undocumented declaration.
Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export {
getBlockTypes,
getBlockSupport,
hasBlockSupport,
getHookedBlocks,
getBlockVariations,
isReusableBlock,
isTemplatePart,
Expand Down
15 changes: 15 additions & 0 deletions packages/blocks/src/api/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,21 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) {
);
}

/**
* Returns the hooked blocks for a given anchor block.
*
* Given an anchor block name, returns an object whose keys are relative positions,
* and whose values are arrays of block names that are hooked to the anchor block
* at that relative position.
*
* @param {string} name Anchor block name.
*
* @return {Object} Lists of hooked block names for each relative position.
*/
export function getHookedBlocks( name ) {
return select( blocksStore ).getHookedBlocks( name );
}

/**
* Determines whether or not the given block is a reusable block. This is a
* special block type that is used to point to a global block stored via the
Expand Down
31 changes: 30 additions & 1 deletion packages/blocks/src/api/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { renderToString } from '@wordpress/element';
*/
import { convertLegacyBlockNameAndAttributes } from './parser/convert-legacy-block';
import { createBlock } from './factory';
import { getBlockType } from './registration';
import { getBlockType, getHookedBlocks } from './registration';

/**
* Checks whether a list of blocks matches a template by comparing the block names.
Expand Down Expand Up @@ -115,6 +115,35 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) {
normalizedAttributes
);

const ignoredHookedBlocks = [
...new Set(
Object.values( getHookedBlocks( blockName ) ).flat()
),
];

if ( ignoredHookedBlocks.length ) {
const { metadata = {}, ...otherAttributes } = blockAttributes;
const {
ignoredHookedBlocks: ignoredHookedBlocksFromTemplate = [],
...otherMetadata
} = metadata;

const newIgnoredHookedBlocks = [
...new Set( [
...ignoredHookedBlocks,
...ignoredHookedBlocksFromTemplate,
] ),
];

blockAttributes = {
metadata: {
ignoredHookedBlocks: newIgnoredHookedBlocks,
...otherMetadata,
},
...otherAttributes,
};
}

// If a Block is undefined at this point, use the core/missing block as
// a placeholder for a better user experience.
if ( undefined === getBlockType( blockName ) ) {
Expand Down
80 changes: 79 additions & 1 deletion packages/blocks/src/api/test/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ describe( 'templates', () => {

beforeEach( () => {
registerBlockType( 'core/test-block', {
attributes: {},
attributes: {
metadata: {
type: 'object',
},
},
save: noop,
category: 'text',
title: 'test block',
Expand Down Expand Up @@ -132,6 +136,80 @@ describe( 'templates', () => {
] );
} );

it( 'should set ignoredHookedBlocks metadata if a block has hooked blocks', () => {
registerBlockType( 'core/hooked-block', {
attributes: {},
save: noop,
category: 'text',
title: 'hooked block',
blockHooks: { 'core/test-block': 'after' },
} );

const template = [
[ 'core/test-block' ],
[ 'core/test-block-2' ],
[ 'core/test-block-2' ],
];
const blockList = [];

expect(
synchronizeBlocksWithTemplate( blockList, template )
).toMatchObject( [
{
name: 'core/test-block',
attributes: {
metadata: {
ignoredHookedBlocks: [ 'core/hooked-block' ],
},
},
},
{ name: 'core/test-block-2' },
{ name: 'core/test-block-2' },
] );
} );

it( 'retains previously set ignoredHookedBlocks metadata', () => {
registerBlockType( 'core/hooked-block', {
attributes: {},
save: noop,
category: 'text',
title: 'hooked block',
blockHooks: { 'core/test-block': 'after' },
} );

const template = [
[
'core/test-block',
{
metadata: {
ignoredHookedBlocks: [ 'core/other-hooked-block' ],
},
},
],
[ 'core/test-block-2' ],
[ 'core/test-block-2' ],
];
const blockList = [];

expect(
synchronizeBlocksWithTemplate( blockList, template )
).toMatchObject( [
{
name: 'core/test-block',
attributes: {
metadata: {
ignoredHookedBlocks: [
'core/hooked-block',
'core/other-hooked-block',
],
},
},
},
{ name: 'core/test-block-2' },
{ name: 'core/test-block-2' },
] );
} );

it( 'should create nested blocks', () => {
const template = [
[ 'core/test-block', {}, [ [ 'core/test-block-2' ] ] ],
Expand Down
62 changes: 62 additions & 0 deletions packages/blocks/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,68 @@ export function getBlockType( state, name ) {
return state.blockTypes[ name ];
}

/**
* Returns the hooked blocks for a given anchor block.
*
* Given an anchor block name, returns an object whose keys are relative positions,
* and whose values are arrays of block names that are hooked to the anchor block
* at that relative position.
*
* @param {Object} state Data state.
* @param {string} blockName Anchor block type name.
*
* @example
* ```js
* import { store as blocksStore } from '@wordpress/blocks';
* import { useSelect } from '@wordpress/data';
*
* const ExampleComponent = () => {
* const hookedBlockNames = useSelect( ( select ) =>
* select( blocksStore ).getHookedBlocks( 'core/navigation' ),
* []
* );
*
* return (
* <ul>
* { Object.keys( hookedBlockNames ).length &&
* Object.keys( hookedBlockNames ).map( ( relativePosition ) => (
* <li key={ relativePosition }>{ relativePosition }>
* <ul>
* { hookedBlockNames[ relativePosition ].map( ( hookedBlock ) => (
* <li key={ hookedBlock }>{ hookedBlock }</li>
* ) ) }
* </ul>
* </li>
* ) ) }
* </ul>
* );
* };
* ```
*
* @return {Object} Lists of hooked block names for each relative position.
*/
export const getHookedBlocks = createSelector(
( state, blockName ) => {
const hookedBlockTypes = getBlockTypes( state ).filter(
( { blockHooks } ) => blockHooks && blockName in blockHooks
);

let hookedBlocks = {};
for ( const blockType of hookedBlockTypes ) {
const relativePosition = blockType.blockHooks[ blockName ];
hookedBlocks = {
...hookedBlocks,
[ relativePosition ]: [
...( hookedBlocks[ relativePosition ] ?? [] ),
blockType.name,
],
};
}
return hookedBlocks;
},
( state ) => [ state.blockTypes ]
);

/**
* Returns block styles by block name.
*
Expand Down
Loading
Loading