From 7ac214938ba5b00eb8b53c11578e9c28489a409b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Fri, 8 Sep 2023 11:24:02 +0200 Subject: [PATCH 1/9] Block API: Stabilize Block Hooks feature --- .../block-api/block-metadata.md | 19 +++++++++++++- .../block-api/block-registration.md | 25 +++++++++++++++++++ lib/compat/wordpress-6.3/rest-api.php | 13 ---------- .../wordpress-6.4}/block-hooks.php | 4 +-- ...tenberg-rest-block-patterns-controller.php | 3 --- lib/compat/wordpress-6.4/rest-api.php | 9 +++++++ lib/experimental/editor-settings.php | 4 --- lib/experimental/rest-api.php | 11 -------- lib/experiments-page.php | 12 --------- lib/load.php | 8 ++---- .../block-editor/src/hooks/block-hooks.js | 12 ++++----- packages/block-library/src/pattern/index.php | 4 +-- packages/blocks/src/api/registration.js | 2 +- packages/blocks/src/store/reducer.js | 7 +++--- 14 files changed, 67 insertions(+), 66 deletions(-) rename lib/{experimental => compat/wordpress-6.4}/block-hooks.php (99%) rename lib/{experimental => compat/wordpress-6.4}/class-gutenberg-rest-block-patterns-controller.php (92%) diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index ba24ff0f584011..2f8c9aa5080a60 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -464,7 +464,7 @@ Plugins and Themes can also register [custom block style](/docs/reference-guides It provides structured example data for the block. This data is used to construct a preview for the block to be shown in the Inspector Help Panel when the user mouses over the block. -See the [the example documentation](/docs/reference-guides/block-api/block-registration.md#example-optional) for more details. +See the [Example documentation](/docs/reference-guides/block-api/block-registration.md#example-optional) for more details. ### Variations @@ -497,6 +497,23 @@ _Note: In JavaScript you can provide a function for the `isActive` property, and See the [the variations documentation](/docs/reference-guides/block-api/block-variations.md) for more details. +### Block Hooks + +- Type: `object` +- Optional +- Property: `blockHooks` +- Since: `WordPress 6.4.0` + +```json +{ + "blockHooks": { + "my-plugin/banner": "after" + } +} +``` + +Block Hooks is the API that allows a block to hook into the rendering of another block. It will enable a block to render its content before or after another block. It's also possible to hook into the rendering of a parent block and prepend or append the block to the list of child blocks. The key is the name of the block to hook into, and the value is the position to hook into. Take a look at the [Block Hooks documentation](/docs/reference-guides/block-api/block-registration.md#block-hooks-optional) for more info about the available configurations. + ### Editor Script - Type: `WPDefinedAsset`|`WPDefinedAsset[]` ([learn more](#wpdefinedasset)) diff --git a/docs/reference-guides/block-api/block-registration.md b/docs/reference-guides/block-api/block-registration.md index cecdb663e3d607..68e784c0f6e762 100644 --- a/docs/reference-guides/block-api/block-registration.md +++ b/docs/reference-guides/block-api/block-registration.md @@ -234,6 +234,7 @@ example: { #### variations (optional) - **Type:** `Object[]` +- **Since**: `WordPress 5.9.0` Similarly to how the block's styles can be declared, a block type can define block variations that the user can pick from. The difference is that, rather than changing only the visual appearance, this field provides a way to apply initial custom attributes and inner blocks at the time when a block is inserted. See the [Block Variations API](/docs/reference-guides/block-api/block-variations.md) for more details. @@ -265,6 +266,7 @@ parent: [ 'core/columns' ], #### ancestor (optional) - **Type:** `Array` +- **Since**: `WordPress 6.0.0` The `ancestor` property makes a block available inside the specified block types at any position of the ancestor block subtree. That allows, for example, to place a 'Comment Content' block inside a 'Column' block, as long as 'Column' is somewhere within a 'Comment Template' block. In comparison to the `parent` property blocks that specify their `ancestor` can be placed anywhere in the subtree whilst blocks with a specified `parent` need to be direct children. @@ -273,6 +275,29 @@ The `ancestor` property makes a block available inside the specified block types ancestor: [ 'core/columns' ], ``` +#### Block Hooks (optional) + +- **Type:** `Object` +- **Since**: `WordPress 6.4.0` + +Block Hooks is the API that allows a block to hook into the rendering of another block or multiple blocks. It will enable a block to render its content before or after another block. Alternatively, it's possible to hook into the rendering of a parent block and prepend or append the block to the list of inner blocks. The key is the name of the block (`string`) to hook into, and the value is the position to hook into (`string`). Allowed target values are: + +- `before` – inject before the target block. +- `after` - inject after the target block. +- `firstChild` - inject before the first inner block of the target container block. +- `lastChild` - inject after the last inner block of the target container block. + +```js +{ + blockHooks: { + 'core/verse': 'before' + 'core/spacer': 'after', + 'core/column': 'firstChild', + 'core/group': 'lastChild', + } +} +``` + ## Block Collections ## `registerBlockCollection` diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php index db90a2575d9eea..fc0c295bdaaf83 100644 --- a/lib/compat/wordpress-6.3/rest-api.php +++ b/lib/compat/wordpress-6.3/rest-api.php @@ -85,19 +85,6 @@ function add_modified_wp_template_schema() { } add_filter( 'rest_api_init', 'add_modified_wp_template_schema' ); -// If the Block Hooks experiment is enabled, we load the block patterns -// controller in lib/experimental/rest-api.php instead. -if ( ! gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) { - /** - * Registers the block patterns REST API routes. - */ - function gutenberg_register_rest_block_patterns() { - $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_3(); - $block_patterns->register_routes(); - } - add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); -} - /** * Registers the Navigation Fallbacks REST API routes. */ diff --git a/lib/experimental/block-hooks.php b/lib/compat/wordpress-6.4/block-hooks.php similarity index 99% rename from lib/experimental/block-hooks.php rename to lib/compat/wordpress-6.4/block-hooks.php index 0586b989608133..cb663122f0e0cc 100644 --- a/lib/experimental/block-hooks.php +++ b/lib/compat/wordpress-6.4/block-hooks.php @@ -13,10 +13,10 @@ * @return array Updated settings array. */ function gutenberg_add_hooked_blocks( $settings, $metadata ) { - if ( ! isset( $metadata['__experimentalBlockHooks'] ) ) { + if ( ! isset( $metadata['blockHooks'] ) ) { return $settings; } - $block_hooks = $metadata['__experimentalBlockHooks']; + $block_hooks = $metadata['blockHooks']; /** * Map the camelCased position string from block.json to the snake_cased block type position diff --git a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php similarity index 92% rename from lib/experimental/class-gutenberg-rest-block-patterns-controller.php rename to lib/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php index e4ac5581910e00..9f686adc82d1df 100644 --- a/lib/experimental/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php @@ -26,9 +26,6 @@ class Gutenberg_REST_Block_Patterns_Controller extends Gutenberg_REST_Block_Patt */ public function prepare_item_for_response( $item, $request ) { $response = parent::prepare_item_for_response( $item, $request ); - if ( ! gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) { - return $response; - } $data = $response->get_data(); diff --git a/lib/compat/wordpress-6.4/rest-api.php b/lib/compat/wordpress-6.4/rest-api.php index 903a2795f20893..7c81a6a274c03a 100644 --- a/lib/compat/wordpress-6.4/rest-api.php +++ b/lib/compat/wordpress-6.4/rest-api.php @@ -10,6 +10,15 @@ die( 'Silence is golden.' ); } +/** + * Registers the block patterns REST API routes. + */ +function gutenberg_register_rest_block_patterns_routes() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); + $block_patterns->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns_routes' ); + /** * Registers the Global Styles Revisions REST API routes. */ diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index eb36b5b49b8e9a..c09b5cde0f16bc 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -30,10 +30,6 @@ function gutenberg_enable_experiments() { if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) { wp_add_inline_script( 'wp-block-library', 'window.__experimentalDisableTinymce = true', 'before' ); } - - if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-hooks', $gutenberg_experiments ) ) { - wp_add_inline_script( 'wp-block-editor', 'window.__experimentalBlockHooks = true', 'before' ); - } } add_action( 'admin_init', 'gutenberg_enable_experiments' ); diff --git a/lib/experimental/rest-api.php b/lib/experimental/rest-api.php index f8c5a3041d3120..7c6a9bf74d7395 100644 --- a/lib/experimental/rest-api.php +++ b/lib/experimental/rest-api.php @@ -10,17 +10,6 @@ die( 'Silence is golden.' ); } -if ( gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) { - /** - * Registers the block patterns REST API routes. - */ - function gutenberg_register_rest_block_patterns() { - $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); - $block_patterns->register_routes(); - } - add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); -} - /** * Registers the customizer nonces REST API routes. */ diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 6a022963807e38..133d968ba2cb76 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -103,18 +103,6 @@ function gutenberg_initialize_experiments_settings() { ) ); - add_settings_field( - 'gutenberg-block-hooks', - __( 'Block hooks', 'gutenberg' ), - 'gutenberg_display_experiment_field', - 'gutenberg-experiments', - 'gutenberg_experiments_section', - array( - 'label' => __( 'Block hooks allow automatically inserting a block in a position relative to another.', 'gutenberg' ), - 'id' => 'gutenberg-block-hooks', - ) - ); - add_settings_field( 'gutenberg-custom-fields', __( 'Connections', 'gutenberg' ), diff --git a/lib/load.php b/lib/load.php index 95cf413da68533..c0b69e2ab2ca02 100644 --- a/lib/load.php +++ b/lib/load.php @@ -52,6 +52,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.4 compat. require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-global-styles-revisions-controller-6-4.php'; + require_once __DIR__ . '/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php'; require_once __DIR__ . '/compat/wordpress-6.4/rest-api.php'; require_once __DIR__ . '/compat/wordpress-6.4/theme-previews.php'; @@ -64,9 +65,6 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/experimental/class-wp-rest-customizer-nonces.php'; } require_once __DIR__ . '/experimental/class-gutenberg-rest-template-revision-count.php'; - if ( gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) { - require_once __DIR__ . '/experimental/class-gutenberg-rest-block-patterns-controller.php'; - } require_once __DIR__ . '/experimental/rest-api.php'; } @@ -96,6 +94,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.4 compat. require __DIR__ . '/compat/wordpress-6.4/blocks.php'; +require __DIR__ . '/compat/wordpress-6.4/block-hooks.php'; require __DIR__ . '/compat/wordpress-6.4/block-patterns.php'; require __DIR__ . '/compat/wordpress-6.4/script-loader.php'; @@ -111,9 +110,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/disable-tinymce.php'; } -if ( gutenberg_is_experiment_enabled( 'gutenberg-block-hooks' ) ) { - require __DIR__ . '/experimental/block-hooks.php'; -} require __DIR__ . '/experimental/interactivity-api/class-wp-interactivity-store.php'; require __DIR__ . '/experimental/interactivity-api/store.php'; require __DIR__ . '/experimental/interactivity-api/scripts.php'; diff --git a/packages/block-editor/src/hooks/block-hooks.js b/packages/block-editor/src/hooks/block-hooks.js index 678f8ee06d9713..3285854db6c790 100644 --- a/packages/block-editor/src/hooks/block-hooks.js +++ b/packages/block-editor/src/hooks/block-hooks.js @@ -250,10 +250,8 @@ export const withBlockHooks = createHigherOrderComponent( ( BlockEdit ) => { }; }, 'withBlockHooks' ); -if ( window?.__experimentalBlockHooks ) { - addFilter( - 'editor.BlockEdit', - 'core/block-hooks/with-inspector-control', - withBlockHooks - ); -} +addFilter( + 'editor.BlockEdit', + 'core/block-hooks/with-inspector-control', + withBlockHooks +); diff --git a/packages/block-library/src/pattern/index.php b/packages/block-library/src/pattern/index.php index 50687220992e2f..b117e31e125cf2 100644 --- a/packages/block-library/src/pattern/index.php +++ b/packages/block-library/src/pattern/index.php @@ -43,8 +43,8 @@ function render_block_core_pattern( $attributes ) { $pattern = $registry->get_registered( $slug ); $content = _inject_theme_attribute_in_block_template_content( $pattern['content'] ); - $gutenberg_experiments = get_option( 'gutenberg-experiments' ); - if ( $gutenberg_experiments && ! empty( $gutenberg_experiments['gutenberg-block-hooks'] ) ) { + // This can be removed when the minimum supported WordPress is >= 6.4. + if ( defined( 'IS_GUTENBERG_PLUGIN' ) && IS_GUTENBERG_PLUGIN ) { // TODO: In the long run, we'd likely want to have a filter in the `WP_Block_Patterns_Registry` class // instead to allow us plugging in code like this. $blocks = parse_blocks( $content ); diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 44776ed7e49925..818a26fdb4e5c4 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -167,7 +167,7 @@ function getBlockSettingsFromMetadata( { textdomain, ...metadata } ) { 'styles', 'example', 'variations', - '__experimentalBlockHooks', + 'blockHooks', ]; const settings = Object.fromEntries( diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index db01f06bb7db67..c16c1a6087efbf 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -83,14 +83,13 @@ function bootstrappedBlockTypes( state = {}, action ) { // definitions and needs to be polyfilled. This can be removed when the // minimum supported WordPress is >= 6.4. if ( - serverDefinition.__experimentalBlockHooks === undefined && - blockType.__experimentalBlockHooks + serverDefinition.blockHooks === undefined && + blockType.blockHooks ) { newDefinition = { ...serverDefinition, ...newDefinition, - __experimentalBlockHooks: - blockType.__experimentalBlockHooks, + blockHooks: blockType.blockHooks, }; } } else { From 7221cee1c482b5ac86d372c765464af31e17efb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Fri, 8 Sep 2023 12:44:37 +0200 Subject: [PATCH 2/9] Improve the API handling for block patterns REST API endpoing --- .../class-gutenberg-rest-block-patterns-controller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php index 9f686adc82d1df..8128934b9b011c 100644 --- a/lib/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.4/class-gutenberg-rest-block-patterns-controller.php @@ -29,6 +29,10 @@ public function prepare_item_for_response( $item, $request ) { $data = $response->get_data(); + if ( empty( $data['content'] ) ) { + return $response; + } + $blocks = parse_blocks( $data['content'] ); $data['content'] = gutenberg_serialize_blocks( $blocks ); // Serialize or render? From e350a1762f8b04aac64c887341e031d5224dd9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Tue, 12 Sep 2023 11:31:07 +0200 Subject: [PATCH 3/9] Add the default value for `blockHooks` during block registration --- packages/blocks/src/api/test/registration.js | 25 +++++++++++++++++++ .../blocks/src/store/process-block-type.js | 1 + 2 files changed, 26 insertions(+) diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 877c9fdc4a038a..1bd64c6e57740c 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -133,6 +133,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, save: noop, category: 'text', title: 'block title', @@ -280,6 +281,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, save: expect.any( Function ), } ); } ); @@ -317,6 +319,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -350,6 +353,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -362,6 +366,9 @@ describe( 'blocks', () => { fontSize: 'fontSize', }, uses_context: [ 'textColor' ], + block_hooks: { + 'tests/my-block': 'after', + }, }, } ); @@ -385,6 +392,9 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: { + 'tests/my-block': 'after', + }, } ); } ); @@ -421,6 +431,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -490,6 +501,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -522,6 +534,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -568,6 +581,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -628,6 +642,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -655,6 +670,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -742,6 +758,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, save: () => null, ...blockSettingsWithDeprecations, }, @@ -916,6 +933,7 @@ describe( 'blocks', () => { keywords: [ 'variation' ], }, ], + blockHooks: {}, edit: Edit, save: noop, } ); @@ -988,6 +1006,7 @@ describe( 'blocks', () => { keywords: [ 'variation (translated)' ], }, ], + blockHooks: {}, edit: Edit, save: noop, } ); @@ -1042,6 +1061,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, }, ] ); const oldBlock = unregisterBlockType( 'core/test-block' ); @@ -1060,6 +1080,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); expect( getBlockTypes() ).toEqual( [] ); } ); @@ -1140,6 +1161,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); @@ -1166,6 +1188,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, } ); } ); } ); @@ -1199,6 +1222,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, }, { name: 'core/test-block-with-settings', @@ -1215,6 +1239,7 @@ describe( 'blocks', () => { supports: {}, styles: [], variations: [], + blockHooks: {}, }, ] ); } ); diff --git a/packages/blocks/src/store/process-block-type.js b/packages/blocks/src/store/process-block-type.js index aab198af6c66fb..d69f9f8e5810fe 100644 --- a/packages/blocks/src/store/process-block-type.js +++ b/packages/blocks/src/store/process-block-type.js @@ -55,6 +55,7 @@ export const processBlockType = supports: {}, styles: [], variations: [], + blockHooks: {}, save: () => null, ...select.getBootstrappedBlockType( name ), ...blockSettings, From 316d01bad3b57a6d832237eedad5b53382a7441c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Tue, 12 Sep 2023 11:56:39 +0200 Subject: [PATCH 4/9] Explain when Block Hooks get wired with the site --- docs/reference-guides/block-api/block-registration.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference-guides/block-api/block-registration.md b/docs/reference-guides/block-api/block-registration.md index 68e784c0f6e762..aa9f4021a865a2 100644 --- a/docs/reference-guides/block-api/block-registration.md +++ b/docs/reference-guides/block-api/block-registration.md @@ -298,6 +298,8 @@ Block Hooks is the API that allows a block to hook into the rendering of another } ``` +It’s crucial to emphasize that the Block Hooks feature is designed to work only with static templates, template parts, and patterns loaded from HTML files shipped with the theme. It also integrates with patterns registered on the server by the site. The modification will never apply to the post content or patterns crafted by the user, nor theme templates and template parts modified by the user. + ## Block Collections ## `registerBlockCollection` From 20c6e7206a40272bed90dbedc4cd0d955c715022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Wed, 13 Sep 2023 09:16:11 +0200 Subject: [PATCH 5/9] Update docs/reference-guides/block-api/block-metadata.md Co-authored-by: Bernie Reiter <96308+ockham@users.noreply.github.com> --- docs/reference-guides/block-api/block-metadata.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index 2f8c9aa5080a60..c0c34dd066cc2f 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -512,7 +512,9 @@ See the [the variations documentation](/docs/reference-guides/block-api/block-va } ``` -Block Hooks is the API that allows a block to hook into the rendering of another block. It will enable a block to render its content before or after another block. It's also possible to hook into the rendering of a parent block and prepend or append the block to the list of child blocks. The key is the name of the block to hook into, and the value is the position to hook into. Take a look at the [Block Hooks documentation](/docs/reference-guides/block-api/block-registration.md#block-hooks-optional) for more info about the available configurations. +Block Hooks is an API that allows a block to automatically insert itself next to all instances of a given block type, in a relative position also specified by the "hooked" block. That is, a block can opt to be inserted before or after a given block type, or as its first or last child (i.e. to be prepended or appended to the list of its child blocks, respectively). Hooked blocks will appear both on the frontend and in the editor (to allow for customization by the user). + +The key is the name of the block to hook into, and the value is the position to hook into. Take a look at the [Block Hooks documentation](/docs/reference-guides/block-api/block-registration.md#block-hooks-optional) for more info about available configurations. ### Editor Script From 4de6ad5cdac5b1088cbc0d5be58a1c2a6524ccf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Wed, 13 Sep 2023 09:19:21 +0200 Subject: [PATCH 6/9] Update the explanation which parts of the site are impacted by the Block Hooks feature Props to @fabiankaegy, @ndiego and @colorful-tones for helping with that part. --- docs/reference-guides/block-api/block-registration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/reference-guides/block-api/block-registration.md b/docs/reference-guides/block-api/block-registration.md index aa9f4021a865a2..6144e06b985f64 100644 --- a/docs/reference-guides/block-api/block-registration.md +++ b/docs/reference-guides/block-api/block-registration.md @@ -298,7 +298,9 @@ Block Hooks is the API that allows a block to hook into the rendering of another } ``` -It’s crucial to emphasize that the Block Hooks feature is designed to work only with static templates, template parts, and patterns loaded from HTML files shipped with the theme. It also integrates with patterns registered on the server by the site. The modification will never apply to the post content or patterns crafted by the user, nor theme templates and template parts modified by the user. +It’s crucial to emphasize that the Block Hooks feature is only designed to work with _static_ block-based templates, template parts, and patterns. For patterns, this includes those provided by the theme, from [Block Pattern Directory](https://wordpress.org/patterns/), or from calls to [`register_block_pattern`](https://developer.wordpress.org/reference/functions/register_block_pattern/). + +Block Hooks will not work with post content or patterns crafted by the user, such as synced patterns, or theme templates and template parts that have been modified by the user. ## Block Collections From 02fd52765de7670941fb1d79f2ac6e893f5ac02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Wed, 13 Sep 2023 09:25:31 +0200 Subject: [PATCH 7/9] Rephrase the description of Block Hooks in the docs Props to @ockham for proposing edits. --- docs/reference-guides/block-api/block-metadata.md | 2 +- docs/reference-guides/block-api/block-registration.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index c0c34dd066cc2f..22d9d66b4f4626 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -514,7 +514,7 @@ See the [the variations documentation](/docs/reference-guides/block-api/block-va Block Hooks is an API that allows a block to automatically insert itself next to all instances of a given block type, in a relative position also specified by the "hooked" block. That is, a block can opt to be inserted before or after a given block type, or as its first or last child (i.e. to be prepended or appended to the list of its child blocks, respectively). Hooked blocks will appear both on the frontend and in the editor (to allow for customization by the user). -The key is the name of the block to hook into, and the value is the position to hook into. Take a look at the [Block Hooks documentation](/docs/reference-guides/block-api/block-registration.md#block-hooks-optional) for more info about available configurations. +The key is the name of the block (`string`) to hook into, and the value is the position to hook into (`string`). Take a look at the [Block Hooks documentation](/docs/reference-guides/block-api/block-registration.md#block-hooks-optional) for more info about available configurations. ### Editor Script diff --git a/docs/reference-guides/block-api/block-registration.md b/docs/reference-guides/block-api/block-registration.md index 6144e06b985f64..0fcaaa510b81a2 100644 --- a/docs/reference-guides/block-api/block-registration.md +++ b/docs/reference-guides/block-api/block-registration.md @@ -280,7 +280,9 @@ ancestor: [ 'core/columns' ], - **Type:** `Object` - **Since**: `WordPress 6.4.0` -Block Hooks is the API that allows a block to hook into the rendering of another block or multiple blocks. It will enable a block to render its content before or after another block. Alternatively, it's possible to hook into the rendering of a parent block and prepend or append the block to the list of inner blocks. The key is the name of the block (`string`) to hook into, and the value is the position to hook into (`string`). Allowed target values are: +Block Hooks is an API that allows a block to automatically insert itself next to all instances of a given block type, in a relative position also specified by the "hooked" block. That is, a block can opt to be inserted before or after a given block type, or as its first or last child (i.e. to be prepended or appended to the list of its child blocks, respectively). Hooked blocks will appear both on the frontend and in the editor (to allow for customization by the user). + +The key is the name of the block (`string`) to hook into, and the value is the position to hook into (`string`). Allowed target values are: - `before` – inject before the target block. - `after` - inject after the target block. From d8e7b0d46d352993fdf087035880163846b694ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Wed, 13 Sep 2023 10:58:13 +0200 Subject: [PATCH 8/9] Add tests for polyfilling blockHooks when not exposed on the server --- packages/blocks/src/api/test/registration.js | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 1bd64c6e57740c..6347912c0402cd 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -435,6 +435,48 @@ describe( 'blocks', () => { } ); } ); + // This test can be removed once the polyfill for blockHooks gets removed. + it( 'should polyfill blockHooks using metadata on the client when not set on the server', () => { + const blockName = 'tests/hooked-block'; + unstable__bootstrapServerSideBlockDefinitions( { + [ blockName ]: { + category: 'widgets', + }, + } ); + + const blockType = { + title: 'block title', + }; + registerBlockType( + { + name: blockName, + blockHooks: { + 'tests/block': 'firstChild', + }, + category: 'ignored', + }, + blockType + ); + expect( getBlockType( blockName ) ).toEqual( { + name: blockName, + save: expect.any( Function ), + title: 'block title', + category: 'widgets', + icon: { src: BLOCK_ICON_DEFAULT }, + attributes: {}, + providesContext: {}, + usesContext: [], + keywords: [], + selectors: {}, + supports: {}, + styles: [], + variations: [], + blockHooks: { + 'tests/block': 'firstChild', + }, + } ); + } ); + it( 'should validate the icon', () => { const blockType = { save: noop, From c3b2ab2d55bf196807dd970aec853e7d39c11047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Wed, 13 Sep 2023 11:30:44 +0200 Subject: [PATCH 9/9] Extend block.json schema to include block hooks --- schemas/json/block.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/schemas/json/block.json b/schemas/json/block.json index b181bf2d9ab403..44ce5709127670 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -706,6 +706,16 @@ } } }, + "blockHooks": { + "description": "Block Hooks allow a block to automatically insert itself next to all instances of a given block type.\n\nSee the Block Hooks documentation at https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#block-hooks-optional for more details.", + "type": "object", + "patternProperties": { + "^[a-z][a-z0-9-]*/[a-z][a-z0-9-]*$": { + "enum": [ "before", "after", "firstChild", "lastChild" ] + } + }, + "additionalProperties": false + }, "editorScript": { "description": "Block type editor script definition. It will only be enqueued in the context of the editor.", "oneOf": [