From 44959c0d13ecbc82efadf7599b0f79e2ad5776b1 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 1 Feb 2024 11:59:43 +0100 Subject: [PATCH 01/24] Block Hooks: Set ignoredHookedBlocks metada attr upon insertion --- packages/block-editor/src/store/selectors.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 3475e2b5351c80..431073f1d2b0af 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1936,9 +1936,26 @@ const buildBlockTypeItem = blockType.name, 'inserter' ); + + let initialAttributes = {}; + + const hookedBlocksForCurrentBlock = getBlockTypes()?.filter( + ( { blockHooks } ) => blockHooks && id in blockHooks + ); + + if ( hookedBlocksForCurrentBlock?.length ) { + initialAttributes = { + metadata: { + ignoredHookedBlocks: hookedBlocksForCurrentBlock.map( + ( { name } ) => name + ), + }, + }; + } + return { ...blockItemBase, - initialAttributes: {}, + initialAttributes, description: blockType.description, category: blockType.category, keywords: blockType.keywords, From 63628e071db0796003c5dcf078884c919ee59bb5 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 1 Feb 2024 15:36:19 +0100 Subject: [PATCH 02/24] Add ignoredHookedBlocks attr to inner blocks --- packages/blocks/src/api/templates.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index bc76218892688a..42250e52fea163 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -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, getBlockTypes } from './registration'; /** * Checks whether a list of blocks matches a template by comparing the block names. @@ -115,6 +115,23 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { normalizedAttributes ); + const hookedBlocksForCurrentBlock = getBlockTypes()?.filter( + ( { blockHooks } ) => blockHooks && blockName in blockHooks + ); + + if ( hookedBlocksForCurrentBlock?.length ) { + const { metadata, ...otherAttributes } = blockAttributes; + blockAttributes = { + metadata: { + ignoredHookedBlocks: hookedBlocksForCurrentBlock.map( + ( hookedBlock ) => hookedBlock.name + ), + ...blockAttributes.metadata, + }, + ...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 ) ) { From c1c9adb4138ffab461a5b11f1935c010afbf41c3 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Fri, 2 Feb 2024 09:49:04 +0100 Subject: [PATCH 03/24] Introduce getHookedBlockNames selector --- .../reference-guides/data/data-core-blocks.md | 37 +++++++++++++++++ .../block-editor/src/hooks/block-hooks.js | 16 ++------ packages/blocks/src/store/selectors.js | 41 +++++++++++++++++++ 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md index 084c9c1d7a5fbc..feadf69cc2e6d1 100644 --- a/docs/reference-guides/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -504,6 +504,43 @@ _Returns_ - `string?`: Name of the block for handling the grouping of blocks. +### getHookedBlockNames + +Returns an array with the hooked blocks for a given anchor block. + +_Usage_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const hookedBlockNames = useSelect( + ( select ) => + select( blocksStore ).getHookedBlockNames( 'core/navigation' ), + [] + ); + + return ( +
    + { hookedBlockNames && + hookedBlockNames.map( ( hookedBlock ) => ( +
  • { hookedBlock }
  • + ) ) } +
+ ); +}; +``` + +_Parameters_ + +- _state_ `Object`: Data state. +- _blockName_ `string`: Anchor block type name. + +_Returns_ + +- `Array`: Array of hooked block names. + ### getUnregisteredFallbackBlockName Returns the name of the block for handling unregistered blocks. diff --git a/packages/block-editor/src/hooks/block-hooks.js b/packages/block-editor/src/hooks/block-hooks.js index 1e0b8e894d2067..89fa8ec22334c8 100644 --- a/packages/block-editor/src/hooks/block-hooks.js +++ b/packages/block-editor/src/hooks/block-hooks.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment, useMemo } from '@wordpress/element'; +import { Fragment } from '@wordpress/element'; import { __experimentalHStack as HStack, PanelBody, @@ -20,17 +20,9 @@ import { store as blockEditorStore } from '../store'; const EMPTY_OBJECT = {}; function BlockHooksControlPure( { name, clientId } ) { - const blockTypes = useSelect( - ( select ) => select( blocksStore ).getBlockTypes(), - [] - ); - - const hookedBlocksForCurrentBlock = useMemo( - () => - blockTypes?.filter( - ( { blockHooks } ) => blockHooks && name in blockHooks - ), - [ blockTypes, name ] + const hookedBlocksForCurrentBlock = useSelect( + ( select ) => select( blocksStore ).getHookedBlockNames( name ), + [ name ] ); const { blockIndex, rootClientId, innerBlocksLength } = useSelect( diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index b2b8ab8106f097..6e04abb1883888 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -106,6 +106,47 @@ export function getBlockType( state, name ) { return state.blockTypes[ name ]; } +/** + * Returns an array with the hooked blocks for a given anchor block. + * + * @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 ).getHookedBlockNames( 'core/navigation' ), + * [] + * ); + * + * return ( + *
    + * { hookedBlockNames && + * hookedBlockNames.map( ( hookedBlock ) => ( + *
  • { hookedBlock }
  • + * ) ) } + *
+ * ); + * }; + * ``` + * + * @return {Array} Array of hooked block names. + */ +export const getHookedBlockNames = createSelector( + ( state, blockName ) => { + return getBlockTypes( state ) + .filter( + ( { blockHooks } ) => blockHooks && blockName in blockHooks + ) + .map( ( { name } ) => name ); + }, + ( state ) => [ state.blockTypes ] +); + /** * Returns block styles by block name. * From 9b09b8fa6aa3270e137891e2826bb224627b8661 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Fri, 2 Feb 2024 10:23:12 +0100 Subject: [PATCH 04/24] Use new getHookedBlockNames selector to insert attribute --- packages/blocks/src/api/registration.js | 11 +++++++++++ packages/blocks/src/api/templates.js | 9 ++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 6633adf40050c5..306c4465efd74c 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -514,6 +514,17 @@ export function getBlockTypes() { return select( blocksStore ).getBlockTypes(); } +/** + * Returns all hooked blocks for a given anchor block. + * + * @param {string} name Anchor block name. + * + * @return {Array} List of blocks hooked to the anchor block. + */ +export function getHookedBlockNames( name ) { + return select( blocksStore ).getHookedBlockNames( name ); +} + /** * Returns the block support value for a feature, if defined. * diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 42250e52fea163..748196832402e3 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -8,7 +8,7 @@ import { renderToString } from '@wordpress/element'; */ import { convertLegacyBlockNameAndAttributes } from './parser/convert-legacy-block'; import { createBlock } from './factory'; -import { getBlockType, getBlockTypes } from './registration'; +import { getBlockType, getHookedBlockNames } from './registration'; /** * Checks whether a list of blocks matches a template by comparing the block names. @@ -115,11 +115,10 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { normalizedAttributes ); - const hookedBlocksForCurrentBlock = getBlockTypes()?.filter( - ( { blockHooks } ) => blockHooks && blockName in blockHooks - ); + const hookedBlocksForCurrentBlock = + getHookedBlockNames( blockName ); - if ( hookedBlocksForCurrentBlock?.length ) { + if ( getHookedBlockNames( blockName )?.length ) { const { metadata, ...otherAttributes } = blockAttributes; blockAttributes = { metadata: { From 484a6a37ac1c4fa0b2d606bcd86d4ecb02bbdfc5 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Fri, 2 Feb 2024 12:09:47 +0100 Subject: [PATCH 05/24] Use getHookedBlockNames in buildBlockTypeItem --- packages/block-editor/src/store/selectors.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 431073f1d2b0af..8905e86ec8ce43 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -10,6 +10,7 @@ import { getBlockType, getBlockTypes, getBlockVariations, + getHookedBlockNames, hasBlockSupport, getPossibleBlockTransformations, parse, @@ -1939,9 +1940,7 @@ const buildBlockTypeItem = let initialAttributes = {}; - const hookedBlocksForCurrentBlock = getBlockTypes()?.filter( - ( { blockHooks } ) => blockHooks && id in blockHooks - ); + const hookedBlocksForCurrentBlock = getHookedBlockNames( id ); if ( hookedBlocksForCurrentBlock?.length ) { initialAttributes = { From d3613bbc0ff82377beb5cd3956dac36af89483fc Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 5 Feb 2024 13:30:27 +0100 Subject: [PATCH 06/24] Add unit test coverage for getChildBlockNames --- packages/blocks/src/store/test/selectors.js | 75 +++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 1fda11d72311a3..4bb50bedc45996 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -12,6 +12,7 @@ import { getBlockVariations, getDefaultBlockVariation, getGroupingBlockName, + getHookedBlockNames, isMatchingSearchTerm, getCategories, getActiveBlockVariation, @@ -228,6 +229,80 @@ describe( 'selectors', () => { } ); } ); + describe( 'getHookedBlockNames', () => { + it( 'should return an empty array if state is empty', () => { + const state = { + blockTypes: {}, + }; + + expect( getHookedBlockNames( state, 'anchor' ) ).toHaveLength( 0 ); + } ); + + it( 'should return an empty array if the anchor block is not found', () => { + const state = { + blockTypes: { + anchor: { + name: 'anchor', + }, + hookedBlock: { + name: 'hookedBlock', + blockHooks: { + anchor: 'after', + }, + }, + }, + }; + + expect( getChildBlockNames( state, 'otherAnchor' ) ).toHaveLength( + 0 + ); + } ); + + it( "should return the anchor block name even if the anchor block doesn't exist", () => { + const state = { + blockTypes: { + hookedBlock: { + name: 'hookedBlock', + blockHooks: { + anchor: 'after', + }, + }, + }, + }; + + expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( [ + 'hookedBlock', + ] ); + } ); + + it( 'should return an array with the hooked block names', () => { + const state = { + blockTypes: { + anchor: { + name: 'anchor', + }, + hookedBlock1: { + name: 'hookedBlock1', + blockHooks: { + anchor: 'after', + }, + }, + hookedBlock2: { + name: 'hookedBlock2', + blockHooks: { + anchor: 'before', + }, + }, + }, + }; + + expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( [ + 'hookedBlock1', + 'hookedBlock2', + ] ); + } ); + } ); + describe( 'Testing block variations selectors', () => { const blockName = 'block/name'; const createBlockVariationsState = ( variations ) => { From 1cfc071d66a6905d0d6360b3fda3f564d5221558 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 5 Feb 2024 15:13:43 +0100 Subject: [PATCH 07/24] Move definition down a bit --- packages/blocks/src/api/registration.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 306c4465efd74c..a42ef88b579caf 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -514,17 +514,6 @@ export function getBlockTypes() { return select( blocksStore ).getBlockTypes(); } -/** - * Returns all hooked blocks for a given anchor block. - * - * @param {string} name Anchor block name. - * - * @return {Array} List of blocks hooked to the anchor block. - */ -export function getHookedBlockNames( name ) { - return select( blocksStore ).getHookedBlockNames( name ); -} - /** * Returns the block support value for a feature, if defined. * @@ -561,6 +550,17 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { ); } +/** + * Returns all hooked blocks for a given anchor block. + * + * @param {string} name Anchor block name. + * + * @return {Array} List of blocks hooked to the anchor block. + */ +export function getHookedBlockNames( name ) { + return select( blocksStore ).getHookedBlockNames( 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 From 6858fba5443b6423af74ff65c87c2f2ac28beeb0 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 5 Feb 2024 15:16:25 +0100 Subject: [PATCH 08/24] Actually export --- packages/blocks/README.md | 12 ++++++++++++ packages/blocks/src/api/index.js | 1 + 2 files changed, 13 insertions(+) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 8e6fdc9d900dbb..7e0384373fdf9d 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -234,6 +234,18 @@ _Returns_ - `?string`: Block name. +### getHookedBlockNames + +Returns all hooked blocks for a given anchor block. + +_Parameters_ + +- _name_ `string`: Anchor block name. + +_Returns_ + +- `Array`: List of blocks hooked to the anchor block. + ### getPhrasingContentSchema Undocumented declaration. diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 2ddeb3a60f0abb..d7dd27ecd66a06 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -124,6 +124,7 @@ export { getBlockTypes, getBlockSupport, hasBlockSupport, + getHookedBlockNames, getBlockVariations, isReusableBlock, isTemplatePart, From c687f8a35f37de58c815806f45bfb34cd21278a6 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 5 Feb 2024 15:58:59 +0100 Subject: [PATCH 09/24] Revert change to block-hooks.js --- packages/block-editor/src/hooks/block-hooks.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/hooks/block-hooks.js b/packages/block-editor/src/hooks/block-hooks.js index 89fa8ec22334c8..1e0b8e894d2067 100644 --- a/packages/block-editor/src/hooks/block-hooks.js +++ b/packages/block-editor/src/hooks/block-hooks.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; +import { Fragment, useMemo } from '@wordpress/element'; import { __experimentalHStack as HStack, PanelBody, @@ -20,9 +20,17 @@ import { store as blockEditorStore } from '../store'; const EMPTY_OBJECT = {}; function BlockHooksControlPure( { name, clientId } ) { - const hookedBlocksForCurrentBlock = useSelect( - ( select ) => select( blocksStore ).getHookedBlockNames( name ), - [ name ] + const blockTypes = useSelect( + ( select ) => select( blocksStore ).getBlockTypes(), + [] + ); + + const hookedBlocksForCurrentBlock = useMemo( + () => + blockTypes?.filter( + ( { blockHooks } ) => blockHooks && name in blockHooks + ), + [ blockTypes, name ] ); const { blockIndex, rootClientId, innerBlocksLength } = useSelect( From f2d552df3915040ac20538e16ba0de5bbc288006 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 5 Feb 2024 16:30:47 +0100 Subject: [PATCH 10/24] Typo --- packages/blocks/src/store/test/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 4bb50bedc45996..eccf0392683166 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -253,7 +253,7 @@ describe( 'selectors', () => { }, }; - expect( getChildBlockNames( state, 'otherAnchor' ) ).toHaveLength( + expect( getHookedBlockNames( state, 'otherAnchor' ) ).toHaveLength( 0 ); } ); From f0bb40721d818906f706c78ea8410aff7138ba4a Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Mon, 5 Feb 2024 16:43:36 +0100 Subject: [PATCH 11/24] Tweak selector --- packages/blocks/src/store/selectors.js | 21 ++++++-- packages/blocks/src/store/test/selectors.js | 57 ++++++++++++++++----- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index 6e04abb1883888..df27117e31a61e 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -138,11 +138,22 @@ export function getBlockType( state, name ) { */ export const getHookedBlockNames = createSelector( ( state, blockName ) => { - return getBlockTypes( state ) - .filter( - ( { blockHooks } ) => blockHooks && blockName in blockHooks - ) - .map( ( { name } ) => name ); + 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 ] ); diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index eccf0392683166..82ba5000e402c9 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -230,15 +230,15 @@ describe( 'selectors', () => { } ); describe( 'getHookedBlockNames', () => { - it( 'should return an empty array if state is empty', () => { + it( 'should return an empty object if state is empty', () => { const state = { blockTypes: {}, }; - expect( getHookedBlockNames( state, 'anchor' ) ).toHaveLength( 0 ); + expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( {} ); } ); - it( 'should return an empty array if the anchor block is not found', () => { + it( 'should return an empty object if the anchor block is not found', () => { const state = { blockTypes: { anchor: { @@ -253,9 +253,7 @@ describe( 'selectors', () => { }, }; - expect( getHookedBlockNames( state, 'otherAnchor' ) ).toHaveLength( - 0 - ); + expect( getHookedBlockNames( state, 'otherAnchor' ) ).toEqual( {} ); } ); it( "should return the anchor block name even if the anchor block doesn't exist", () => { @@ -270,9 +268,9 @@ describe( 'selectors', () => { }, }; - expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( [ - 'hookedBlock', - ] ); + expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( { + after: [ 'hookedBlock' ], + } ); } ); it( 'should return an array with the hooked block names', () => { @@ -296,10 +294,43 @@ describe( 'selectors', () => { }, }; - expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( [ - 'hookedBlock1', - 'hookedBlock2', - ] ); + expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( { + after: [ 'hookedBlock1' ], + before: [ 'hookedBlock2' ], + } ); + } ); + + it( 'should return an array with the hooked block names, even if multiple blocks are in the same relative position', () => { + const state = { + blockTypes: { + anchor: { + name: 'anchor', + }, + hookedBlock1: { + name: 'hookedBlock1', + blockHooks: { + anchor: 'after', + }, + }, + hookedBlock2: { + name: 'hookedBlock2', + blockHooks: { + anchor: 'before', + }, + }, + hookedBlock3: { + name: 'hookedBlock3', + blockHooks: { + anchor: 'after', + }, + }, + }, + }; + + expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( { + after: [ 'hookedBlock1', 'hookedBlock3' ], + before: [ 'hookedBlock2' ], + } ); } ); } ); From fe7d9d4dfd7f457b98e1b4b29c31d602af095157 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 6 Feb 2024 14:19:54 +0100 Subject: [PATCH 12/24] Update buildBlockTypeItem --- packages/block-editor/src/store/selectors.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 8905e86ec8ce43..ac2907e266b44b 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1940,14 +1940,14 @@ const buildBlockTypeItem = let initialAttributes = {}; - const hookedBlocksForCurrentBlock = getHookedBlockNames( id ); + const ignoredHookedBlocks = Object.values( + getHookedBlockNames( id ) + ).flat(); - if ( hookedBlocksForCurrentBlock?.length ) { + if ( ignoredHookedBlocks.length ) { initialAttributes = { metadata: { - ignoredHookedBlocks: hookedBlocksForCurrentBlock.map( - ( { name } ) => name - ), + ignoredHookedBlocks, }, }; } From 3a025b005177492935ad29a4f8e100422aa4e573 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 6 Feb 2024 14:35:05 +0100 Subject: [PATCH 13/24] Update synchronizeBlocksWithTemplate --- packages/blocks/src/api/templates.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 748196832402e3..dcb0820e38b69e 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -115,16 +115,15 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { normalizedAttributes ); - const hookedBlocksForCurrentBlock = - getHookedBlockNames( blockName ); + const ignoredHookedBlocks = Object.values( + getHookedBlockNames( blockName ) + ).flat(); - if ( getHookedBlockNames( blockName )?.length ) { + if ( ignoredHookedBlocks.length ) { const { metadata, ...otherAttributes } = blockAttributes; blockAttributes = { metadata: { - ignoredHookedBlocks: hookedBlocksForCurrentBlock.map( - ( hookedBlock ) => hookedBlock.name - ), + ignoredHookedBlocks, ...blockAttributes.metadata, }, ...otherAttributes, From 512cf6f0d43a0d5b1c7fdbcc42df638ad6437a43 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 6 Feb 2024 14:42:29 +0100 Subject: [PATCH 14/24] Rename to getHookedBlocks --- docs/reference-guides/data/data-core-blocks.md | 4 ++-- packages/block-editor/src/store/selectors.js | 4 ++-- packages/blocks/README.md | 2 +- packages/blocks/src/api/index.js | 2 +- packages/blocks/src/api/registration.js | 4 ++-- packages/blocks/src/api/templates.js | 4 ++-- packages/blocks/src/store/selectors.js | 4 ++-- packages/blocks/src/store/test/selectors.js | 14 +++++++------- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md index feadf69cc2e6d1..da82c75f2d4d7c 100644 --- a/docs/reference-guides/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -504,7 +504,7 @@ _Returns_ - `string?`: Name of the block for handling the grouping of blocks. -### getHookedBlockNames +### getHookedBlocks Returns an array with the hooked blocks for a given anchor block. @@ -517,7 +517,7 @@ import { useSelect } from '@wordpress/data'; const ExampleComponent = () => { const hookedBlockNames = useSelect( ( select ) => - select( blocksStore ).getHookedBlockNames( 'core/navigation' ), + select( blocksStore ).getHookedBlocks( 'core/navigation' ), [] ); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index ac2907e266b44b..4141419eb7ea4f 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -10,7 +10,7 @@ import { getBlockType, getBlockTypes, getBlockVariations, - getHookedBlockNames, + getHookedBlocks, hasBlockSupport, getPossibleBlockTransformations, parse, @@ -1941,7 +1941,7 @@ const buildBlockTypeItem = let initialAttributes = {}; const ignoredHookedBlocks = Object.values( - getHookedBlockNames( id ) + getHookedBlocks( id ) ).flat(); if ( ignoredHookedBlocks.length ) { diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 7e0384373fdf9d..66ca9b94d42856 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -234,7 +234,7 @@ _Returns_ - `?string`: Block name. -### getHookedBlockNames +### getHookedBlocks Returns all hooked blocks for a given anchor block. diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index d7dd27ecd66a06..92738c6e16fbc3 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -124,7 +124,7 @@ export { getBlockTypes, getBlockSupport, hasBlockSupport, - getHookedBlockNames, + getHookedBlocks, getBlockVariations, isReusableBlock, isTemplatePart, diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index a42ef88b579caf..c6d55a5f6ce6fc 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -557,8 +557,8 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { * * @return {Array} List of blocks hooked to the anchor block. */ -export function getHookedBlockNames( name ) { - return select( blocksStore ).getHookedBlockNames( name ); +export function getHookedBlocks( name ) { + return select( blocksStore ).getHookedBlocks( name ); } /** diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index dcb0820e38b69e..277f728e5efe7c 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -8,7 +8,7 @@ import { renderToString } from '@wordpress/element'; */ import { convertLegacyBlockNameAndAttributes } from './parser/convert-legacy-block'; import { createBlock } from './factory'; -import { getBlockType, getHookedBlockNames } from './registration'; +import { getBlockType, getHookedBlocks } from './registration'; /** * Checks whether a list of blocks matches a template by comparing the block names. @@ -116,7 +116,7 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { ); const ignoredHookedBlocks = Object.values( - getHookedBlockNames( blockName ) + getHookedBlocks( blockName ) ).flat(); if ( ignoredHookedBlocks.length ) { diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index df27117e31a61e..af6b95ad5b75f4 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -119,7 +119,7 @@ export function getBlockType( state, name ) { * * const ExampleComponent = () => { * const hookedBlockNames = useSelect( ( select ) => - * select( blocksStore ).getHookedBlockNames( 'core/navigation' ), + * select( blocksStore ).getHookedBlocks( 'core/navigation' ), * [] * ); * @@ -136,7 +136,7 @@ export function getBlockType( state, name ) { * * @return {Array} Array of hooked block names. */ -export const getHookedBlockNames = createSelector( +export const getHookedBlocks = createSelector( ( state, blockName ) => { const hookedBlockTypes = getBlockTypes( state ).filter( ( { blockHooks } ) => blockHooks && blockName in blockHooks diff --git a/packages/blocks/src/store/test/selectors.js b/packages/blocks/src/store/test/selectors.js index 82ba5000e402c9..0dcdde3c07bf10 100644 --- a/packages/blocks/src/store/test/selectors.js +++ b/packages/blocks/src/store/test/selectors.js @@ -12,7 +12,7 @@ import { getBlockVariations, getDefaultBlockVariation, getGroupingBlockName, - getHookedBlockNames, + getHookedBlocks, isMatchingSearchTerm, getCategories, getActiveBlockVariation, @@ -229,13 +229,13 @@ describe( 'selectors', () => { } ); } ); - describe( 'getHookedBlockNames', () => { + describe( 'getHookedBlocks', () => { it( 'should return an empty object if state is empty', () => { const state = { blockTypes: {}, }; - expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( {} ); + expect( getHookedBlocks( state, 'anchor' ) ).toEqual( {} ); } ); it( 'should return an empty object if the anchor block is not found', () => { @@ -253,7 +253,7 @@ describe( 'selectors', () => { }, }; - expect( getHookedBlockNames( state, 'otherAnchor' ) ).toEqual( {} ); + expect( getHookedBlocks( state, 'otherAnchor' ) ).toEqual( {} ); } ); it( "should return the anchor block name even if the anchor block doesn't exist", () => { @@ -268,7 +268,7 @@ describe( 'selectors', () => { }, }; - expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( { + expect( getHookedBlocks( state, 'anchor' ) ).toEqual( { after: [ 'hookedBlock' ], } ); } ); @@ -294,7 +294,7 @@ describe( 'selectors', () => { }, }; - expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( { + expect( getHookedBlocks( state, 'anchor' ) ).toEqual( { after: [ 'hookedBlock1' ], before: [ 'hookedBlock2' ], } ); @@ -327,7 +327,7 @@ describe( 'selectors', () => { }, }; - expect( getHookedBlockNames( state, 'anchor' ) ).toEqual( { + expect( getHookedBlocks( state, 'anchor' ) ).toEqual( { after: [ 'hookedBlock1', 'hookedBlock3' ], before: [ 'hookedBlock2' ], } ); From e9edc8da41f3627882476e910058bc72ae61f157 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 6 Feb 2024 15:12:01 +0100 Subject: [PATCH 15/24] Update JSDoc for getHookedBlocks selector --- .../reference-guides/data/data-core-blocks.md | 21 ++++++++++++++----- packages/blocks/src/store/selectors.js | 20 +++++++++++++----- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/docs/reference-guides/data/data-core-blocks.md b/docs/reference-guides/data/data-core-blocks.md index da82c75f2d4d7c..a25a521931e25a 100644 --- a/docs/reference-guides/data/data-core-blocks.md +++ b/docs/reference-guides/data/data-core-blocks.md @@ -506,7 +506,9 @@ _Returns_ ### getHookedBlocks -Returns an array with the hooked blocks for a given anchor block. +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_ @@ -523,9 +525,18 @@ const ExampleComponent = () => { return (
    - { hookedBlockNames && - hookedBlockNames.map( ( hookedBlock ) => ( -
  • { hookedBlock }
  • + { Object.keys( hookedBlockNames ).length && + Object.keys( hookedBlockNames ).map( ( relativePosition ) => ( +
  • + { relativePosition }> +
      + { hookedBlockNames[ relativePosition ].map( + ( hookedBlock ) => ( +
    • { hookedBlock }
    • + ) + ) } +
    +
  • ) ) }
); @@ -539,7 +550,7 @@ _Parameters_ _Returns_ -- `Array`: Array of hooked block names. +- `Object`: Lists of hooked block names for each relative position. ### getUnregisteredFallbackBlockName diff --git a/packages/blocks/src/store/selectors.js b/packages/blocks/src/store/selectors.js index af6b95ad5b75f4..9eda135d0d6999 100644 --- a/packages/blocks/src/store/selectors.js +++ b/packages/blocks/src/store/selectors.js @@ -107,7 +107,11 @@ export function getBlockType( state, name ) { } /** - * Returns an array with the hooked blocks for a given anchor block. + * 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. @@ -125,16 +129,22 @@ export function getBlockType( state, name ) { * * return ( *
    - * { hookedBlockNames && - * hookedBlockNames.map( ( hookedBlock ) => ( - *
  • { hookedBlock }
  • + * { Object.keys( hookedBlockNames ).length && + * Object.keys( hookedBlockNames ).map( ( relativePosition ) => ( + *
  • { relativePosition }> + *
      + * { hookedBlockNames[ relativePosition ].map( ( hookedBlock ) => ( + *
    • { hookedBlock }
    • + * ) ) } + *
    + *
  • * ) ) } *
* ); * }; * ``` * - * @return {Array} Array of hooked block names. + * @return {Object} Lists of hooked block names for each relative position. */ export const getHookedBlocks = createSelector( ( state, blockName ) => { From 84a37476dd4e3da7c98548014d8f6f3d8c9bcf33 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 6 Feb 2024 15:21:03 +0100 Subject: [PATCH 16/24] Update JSDoc for getHookedBlocks in API --- packages/blocks/README.md | 6 ++++-- packages/blocks/src/api/registration.js | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/blocks/README.md b/packages/blocks/README.md index 66ca9b94d42856..eda3ac629bef87 100644 --- a/packages/blocks/README.md +++ b/packages/blocks/README.md @@ -236,7 +236,9 @@ _Returns_ ### getHookedBlocks -Returns all hooked blocks for a given anchor block. +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_ @@ -244,7 +246,7 @@ _Parameters_ _Returns_ -- `Array`: List of blocks hooked to the anchor block. +- `Object`: Lists of hooked block names for each relative position. ### getPhrasingContentSchema diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index c6d55a5f6ce6fc..71e59949d51d17 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -551,11 +551,15 @@ export function hasBlockSupport( nameOrType, feature, defaultSupports ) { } /** - * Returns all hooked blocks for a given anchor block. + * 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 {Array} List of blocks hooked to the anchor block. + * @return {Object} Lists of hooked block names for each relative position. */ export function getHookedBlocks( name ) { return select( blocksStore ).getHookedBlocks( name ); From cdfc8bbd3c4ed2e4473b74c1bc13ef1d2071bb56 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 6 Feb 2024 22:52:00 +0100 Subject: [PATCH 17/24] Use in block-hooks.js --- .../block-editor/src/hooks/block-hooks.js | 129 +++++++++--------- 1 file changed, 63 insertions(+), 66 deletions(-) diff --git a/packages/block-editor/src/hooks/block-hooks.js b/packages/block-editor/src/hooks/block-hooks.js index 1e0b8e894d2067..130687ab0ac3a9 100644 --- a/packages/block-editor/src/hooks/block-hooks.js +++ b/packages/block-editor/src/hooks/block-hooks.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment, useMemo } from '@wordpress/element'; +import { Fragment } from '@wordpress/element'; import { __experimentalHStack as HStack, PanelBody, @@ -20,17 +20,20 @@ import { store as blockEditorStore } from '../store'; const EMPTY_OBJECT = {}; function BlockHooksControlPure( { name, clientId } ) { - const blockTypes = useSelect( - ( select ) => select( blocksStore ).getBlockTypes(), - [] - ); - - const hookedBlocksForCurrentBlock = useMemo( - () => - blockTypes?.filter( - ( { blockHooks } ) => blockHooks && name in blockHooks - ), - [ blockTypes, name ] + const hookedBlocksForCurrentBlock = useSelect( + ( select ) => { + const hookedBlocks = select( blocksStore ).getHookedBlocks( name ); + const _hookedBlocksForCurrentBlock = {}; + for ( const relativePosition in hookedBlocks ) { + _hookedBlocksForCurrentBlock[ relativePosition ] = hookedBlocks[ + relativePosition + ].map( ( blockName ) => + select( blocksStore ).getBlockType( blockName ) + ); + } + return _hookedBlocksForCurrentBlock; + }, + [ name ] ); const { blockIndex, rootClientId, innerBlocksLength } = useSelect( @@ -52,54 +55,49 @@ function BlockHooksControlPure( { name, clientId } ) { const { getBlock, getGlobalBlockCount } = select( blockEditorStore ); - const _hookedBlockClientIds = hookedBlocksForCurrentBlock.reduce( - ( clientIds, block ) => { - // If the block doesn't exist anywhere in the block tree, - // we know that we have to set the toggle to disabled. - if ( getGlobalBlockCount( block.name ) === 0 ) { - return clientIds; - } - - const relativePosition = block?.blockHooks?.[ name ]; - let candidates; - - switch ( relativePosition ) { - case 'before': - case 'after': - // Any of the current block's siblings (with the right block type) qualifies - // as a hooked block (inserted `before` or `after` the current one), as the block - // might've been automatically inserted and then moved around a bit by the user. - candidates = getBlock( rootClientId )?.innerBlocks; - break; - - case 'first_child': - case 'last_child': - // Any of the current block's child blocks (with the right block type) qualifies - // as a hooked first or last child block, as the block might've been automatically - // inserted and then moved around a bit by the user. - candidates = getBlock( clientId ).innerBlocks; - break; + const _hookedBlockClientIds = {}; + for ( const relativePosition in hookedBlocksForCurrentBlock ) { + hookedBlocksForCurrentBlock[ relativePosition ].forEach( + ( block ) => { + // If the block doesn't exist anywhere in the block tree, + // we know that we have to set the toggle to disabled. + if ( getGlobalBlockCount( block.name ) === 0 ) { + return; + } + + let candidates; + switch ( relativePosition ) { + case 'before': + case 'after': + // Any of the current block's siblings (with the right block type) qualifies + // as a hooked block (inserted `before` or `after` the current one), as the block + // might've been automatically inserted and then moved around a bit by the user. + candidates = + getBlock( rootClientId )?.innerBlocks; + break; + + case 'first_child': + case 'last_child': + // Any of the current block's child blocks (with the right block type) qualifies + // as a hooked first or last child block, as the block might've been automatically + // inserted and then moved around a bit by the user. + candidates = getBlock( clientId ).innerBlocks; + break; + } + + const hookedBlock = candidates?.find( + ( candidate ) => candidate.name === block.name + ); + + // If the block exists in the designated location, we consider it hooked + // and show the toggle as enabled. + if ( hookedBlock ) { + _hookedBlockClientIds[ block.name ] = + hookedBlock.clientId; + } } - - const hookedBlock = candidates?.find( - ( candidate ) => candidate.name === block.name - ); - - // If the block exists in the designated location, we consider it hooked - // and show the toggle as enabled. - if ( hookedBlock ) { - return { - ...clientIds, - [ block.name ]: hookedBlock.clientId, - }; - } - - // If no hooked block was found in any of its designated locations, - // we set the toggle to disabled. - return clientIds; - }, - {} - ); + ); + } if ( Object.values( _hookedBlockClientIds ).length > 0 ) { return _hookedBlockClientIds; @@ -107,27 +105,26 @@ function BlockHooksControlPure( { name, clientId } ) { return EMPTY_OBJECT; }, - [ hookedBlocksForCurrentBlock, name, clientId, rootClientId ] + [ hookedBlocksForCurrentBlock, clientId, rootClientId ] ); const { insertBlock, removeBlock } = useDispatch( blockEditorStore ); - if ( ! hookedBlocksForCurrentBlock.length ) { + if ( ! Object.keys( hookedBlocksForCurrentBlock ).length ) { return null; } // Group by block namespace (i.e. prefix before the slash). - const groupedHookedBlocks = hookedBlocksForCurrentBlock.reduce( - ( groups, block ) => { + const groupedHookedBlocks = Object.values( hookedBlocksForCurrentBlock ) + .flat() + .reduce( ( groups, block ) => { const [ namespace ] = block.name.split( '/' ); if ( ! groups[ namespace ] ) { groups[ namespace ] = []; } groups[ namespace ].push( block ); return groups; - }, - {} - ); + }, {} ); const insertBlockIntoDesignatedLocation = ( block, relativePosition ) => { switch ( relativePosition ) { From 4042445740577a2df2d345ec2f22a33a3462d5b6 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Tue, 6 Feb 2024 22:54:00 +0100 Subject: [PATCH 18/24] Revert "Use in block-hooks.js" This reverts commit 6f973f9859b8522306268c6a5d6740bd8eec66b8. --- .../block-editor/src/hooks/block-hooks.js | 129 +++++++++--------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/packages/block-editor/src/hooks/block-hooks.js b/packages/block-editor/src/hooks/block-hooks.js index 130687ab0ac3a9..1e0b8e894d2067 100644 --- a/packages/block-editor/src/hooks/block-hooks.js +++ b/packages/block-editor/src/hooks/block-hooks.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Fragment } from '@wordpress/element'; +import { Fragment, useMemo } from '@wordpress/element'; import { __experimentalHStack as HStack, PanelBody, @@ -20,20 +20,17 @@ import { store as blockEditorStore } from '../store'; const EMPTY_OBJECT = {}; function BlockHooksControlPure( { name, clientId } ) { - const hookedBlocksForCurrentBlock = useSelect( - ( select ) => { - const hookedBlocks = select( blocksStore ).getHookedBlocks( name ); - const _hookedBlocksForCurrentBlock = {}; - for ( const relativePosition in hookedBlocks ) { - _hookedBlocksForCurrentBlock[ relativePosition ] = hookedBlocks[ - relativePosition - ].map( ( blockName ) => - select( blocksStore ).getBlockType( blockName ) - ); - } - return _hookedBlocksForCurrentBlock; - }, - [ name ] + const blockTypes = useSelect( + ( select ) => select( blocksStore ).getBlockTypes(), + [] + ); + + const hookedBlocksForCurrentBlock = useMemo( + () => + blockTypes?.filter( + ( { blockHooks } ) => blockHooks && name in blockHooks + ), + [ blockTypes, name ] ); const { blockIndex, rootClientId, innerBlocksLength } = useSelect( @@ -55,49 +52,54 @@ function BlockHooksControlPure( { name, clientId } ) { const { getBlock, getGlobalBlockCount } = select( blockEditorStore ); - const _hookedBlockClientIds = {}; - for ( const relativePosition in hookedBlocksForCurrentBlock ) { - hookedBlocksForCurrentBlock[ relativePosition ].forEach( - ( block ) => { - // If the block doesn't exist anywhere in the block tree, - // we know that we have to set the toggle to disabled. - if ( getGlobalBlockCount( block.name ) === 0 ) { - return; - } - - let candidates; - switch ( relativePosition ) { - case 'before': - case 'after': - // Any of the current block's siblings (with the right block type) qualifies - // as a hooked block (inserted `before` or `after` the current one), as the block - // might've been automatically inserted and then moved around a bit by the user. - candidates = - getBlock( rootClientId )?.innerBlocks; - break; - - case 'first_child': - case 'last_child': - // Any of the current block's child blocks (with the right block type) qualifies - // as a hooked first or last child block, as the block might've been automatically - // inserted and then moved around a bit by the user. - candidates = getBlock( clientId ).innerBlocks; - break; - } - - const hookedBlock = candidates?.find( - ( candidate ) => candidate.name === block.name - ); - - // If the block exists in the designated location, we consider it hooked - // and show the toggle as enabled. - if ( hookedBlock ) { - _hookedBlockClientIds[ block.name ] = - hookedBlock.clientId; - } + const _hookedBlockClientIds = hookedBlocksForCurrentBlock.reduce( + ( clientIds, block ) => { + // If the block doesn't exist anywhere in the block tree, + // we know that we have to set the toggle to disabled. + if ( getGlobalBlockCount( block.name ) === 0 ) { + return clientIds; } - ); - } + + const relativePosition = block?.blockHooks?.[ name ]; + let candidates; + + switch ( relativePosition ) { + case 'before': + case 'after': + // Any of the current block's siblings (with the right block type) qualifies + // as a hooked block (inserted `before` or `after` the current one), as the block + // might've been automatically inserted and then moved around a bit by the user. + candidates = getBlock( rootClientId )?.innerBlocks; + break; + + case 'first_child': + case 'last_child': + // Any of the current block's child blocks (with the right block type) qualifies + // as a hooked first or last child block, as the block might've been automatically + // inserted and then moved around a bit by the user. + candidates = getBlock( clientId ).innerBlocks; + break; + } + + const hookedBlock = candidates?.find( + ( candidate ) => candidate.name === block.name + ); + + // If the block exists in the designated location, we consider it hooked + // and show the toggle as enabled. + if ( hookedBlock ) { + return { + ...clientIds, + [ block.name ]: hookedBlock.clientId, + }; + } + + // If no hooked block was found in any of its designated locations, + // we set the toggle to disabled. + return clientIds; + }, + {} + ); if ( Object.values( _hookedBlockClientIds ).length > 0 ) { return _hookedBlockClientIds; @@ -105,26 +107,27 @@ function BlockHooksControlPure( { name, clientId } ) { return EMPTY_OBJECT; }, - [ hookedBlocksForCurrentBlock, clientId, rootClientId ] + [ hookedBlocksForCurrentBlock, name, clientId, rootClientId ] ); const { insertBlock, removeBlock } = useDispatch( blockEditorStore ); - if ( ! Object.keys( hookedBlocksForCurrentBlock ).length ) { + if ( ! hookedBlocksForCurrentBlock.length ) { return null; } // Group by block namespace (i.e. prefix before the slash). - const groupedHookedBlocks = Object.values( hookedBlocksForCurrentBlock ) - .flat() - .reduce( ( groups, block ) => { + const groupedHookedBlocks = hookedBlocksForCurrentBlock.reduce( + ( groups, block ) => { const [ namespace ] = block.name.split( '/' ); if ( ! groups[ namespace ] ) { groups[ namespace ] = []; } groups[ namespace ].push( block ); return groups; - }, {} ); + }, + {} + ); const insertBlockIntoDesignatedLocation = ( block, relativePosition ) => { switch ( relativePosition ) { From 2043998e590a4e41f8b078c9ae0921c3fc4672df Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 7 Feb 2024 12:24:48 +0100 Subject: [PATCH 19/24] Rearrange slightly --- packages/block-editor/src/store/selectors.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 4141419eb7ea4f..bc2fadad3ffa5a 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1938,23 +1938,15 @@ const buildBlockTypeItem = 'inserter' ); - let initialAttributes = {}; - const ignoredHookedBlocks = Object.values( getHookedBlocks( id ) ).flat(); - if ( ignoredHookedBlocks.length ) { - initialAttributes = { - metadata: { - ignoredHookedBlocks, - }, - }; - } - return { ...blockItemBase, - initialAttributes, + initialAttributes: ignoredHookedBlocks + ? { metadata: { ignoredHookedBlocks } } + : {}, description: blockType.description, category: blockType.category, keywords: blockType.keywords, From c804c3e7a3bde8441b583595424e5bc06551e5b8 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 7 Feb 2024 13:10:37 +0100 Subject: [PATCH 20/24] Add test coverage for synchronizeBlocksWithTemplate --- packages/blocks/src/api/test/templates.js | 38 ++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/blocks/src/api/test/templates.js b/packages/blocks/src/api/test/templates.js index 0a23505f0ac036..cfba4b6b93056e 100644 --- a/packages/blocks/src/api/test/templates.js +++ b/packages/blocks/src/api/test/templates.js @@ -28,7 +28,11 @@ describe( 'templates', () => { beforeEach( () => { registerBlockType( 'core/test-block', { - attributes: {}, + attributes: { + metadata: { + type: 'object', + }, + }, save: noop, category: 'text', title: 'test block', @@ -132,6 +136,38 @@ 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( 'should create nested blocks', () => { const template = [ [ 'core/test-block', {}, [ [ 'core/test-block-2' ] ] ], From 1a5ed9258e85467644bb5fad29458243d14a23d0 Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Wed, 7 Feb 2024 13:27:02 +0100 Subject: [PATCH 21/24] Don't add emtpy metadata.ignoredHookedBlocks attr --- packages/block-editor/src/store/selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index bc2fadad3ffa5a..f3ec1d52682af8 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1944,7 +1944,7 @@ const buildBlockTypeItem = return { ...blockItemBase, - initialAttributes: ignoredHookedBlocks + initialAttributes: ignoredHookedBlocks.length ? { metadata: { ignoredHookedBlocks } } : {}, description: blockType.description, From 52980a8be6b6ad63691ca81c90c2fd1f985c0f2c Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 8 Feb 2024 15:18:16 +0100 Subject: [PATCH 22/24] Deduplicate ignored hooked blocks --- packages/block-editor/src/store/selectors.js | 6 +++--- packages/blocks/src/api/templates.js | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index f3ec1d52682af8..373611cd3bd8e8 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1938,9 +1938,9 @@ const buildBlockTypeItem = 'inserter' ); - const ignoredHookedBlocks = Object.values( - getHookedBlocks( id ) - ).flat(); + const ignoredHookedBlocks = [ + ...new Set( Object.values( getHookedBlocks( id ) ).flat() ), + ]; return { ...blockItemBase, diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 277f728e5efe7c..107c9f4401fab0 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -115,9 +115,11 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { normalizedAttributes ); - const ignoredHookedBlocks = Object.values( - getHookedBlocks( blockName ) - ).flat(); + const ignoredHookedBlocks = [ + ...new Set( + Object.values( getHookedBlocks( blockName ) ).flat() + ), + ]; if ( ignoredHookedBlocks.length ) { const { metadata, ...otherAttributes } = blockAttributes; From dfae816728166e5b6653ef70e5b0b4e9168b95aa Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 8 Feb 2024 15:30:11 +0100 Subject: [PATCH 23/24] Simplify a bit --- packages/blocks/src/api/templates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 107c9f4401fab0..5b9e34cf3c0c22 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -126,7 +126,7 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { blockAttributes = { metadata: { ignoredHookedBlocks, - ...blockAttributes.metadata, + ...metadata, }, ...otherAttributes, }; From c257a6608a16cc0fc4eec8344657247e86634e3f Mon Sep 17 00:00:00 2001 From: Bernie Reiter Date: Thu, 8 Feb 2024 16:11:42 +0100 Subject: [PATCH 24/24] Retain previous ignoredHookedBlocks --- packages/blocks/src/api/templates.js | 18 ++++++++-- packages/blocks/src/api/test/templates.js | 42 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/packages/blocks/src/api/templates.js b/packages/blocks/src/api/templates.js index 5b9e34cf3c0c22..34e6954a9ff33f 100644 --- a/packages/blocks/src/api/templates.js +++ b/packages/blocks/src/api/templates.js @@ -122,11 +122,23 @@ export function synchronizeBlocksWithTemplate( blocks = [], template ) { ]; if ( ignoredHookedBlocks.length ) { - const { metadata, ...otherAttributes } = blockAttributes; + const { metadata = {}, ...otherAttributes } = blockAttributes; + const { + ignoredHookedBlocks: ignoredHookedBlocksFromTemplate = [], + ...otherMetadata + } = metadata; + + const newIgnoredHookedBlocks = [ + ...new Set( [ + ...ignoredHookedBlocks, + ...ignoredHookedBlocksFromTemplate, + ] ), + ]; + blockAttributes = { metadata: { - ignoredHookedBlocks, - ...metadata, + ignoredHookedBlocks: newIgnoredHookedBlocks, + ...otherMetadata, }, ...otherAttributes, }; diff --git a/packages/blocks/src/api/test/templates.js b/packages/blocks/src/api/test/templates.js index cfba4b6b93056e..8ee031aedbeefc 100644 --- a/packages/blocks/src/api/test/templates.js +++ b/packages/blocks/src/api/test/templates.js @@ -168,6 +168,48 @@ describe( 'templates', () => { ] ); } ); + 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' ] ] ],