-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Experiment: Auto-inserting blocks in the editor (via REST API)
- Loading branch information
Showing
11 changed files
with
295 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
<?php | ||
/** | ||
* Auto-inserting blocks. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Return a function that auto-inserts blocks relative to a given block. | ||
* | ||
* @param string $anchor_block The block to insert relative to. | ||
* @param string $relative_position The position relative to the given block. | ||
* @param array $inserted_block The block to insert. | ||
* @return callable A function that accepts a block's content and returns the content with the inserted block. | ||
*/ | ||
function gutenberg_auto_insert_block( $anchor_block, $relative_position, $inserted_block ) { | ||
return function( $block ) use ( $anchor_block, $relative_position, $inserted_block ) { | ||
if ( $anchor_block === $block['blockName'] ) { | ||
if ( 'first_child' === $relative_position ) { | ||
array_unshift( $block['innerBlocks'], $inserted_block ); | ||
// Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) | ||
// when rendering blocks, we also need to prepend a value (`null`, to mark a block | ||
// location) to that array. | ||
array_unshift( $block['innerContent'], null ); | ||
} elseif ( 'last_child' === $relative_position ) { | ||
array_push( $block['innerBlocks'], $inserted_block ); | ||
// Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) | ||
// when rendering blocks, we also need to prepend a value (`null`, to mark a block | ||
// location) to that array. | ||
array_push( $block['innerContent'], null ); | ||
} | ||
return $block; | ||
} | ||
|
||
$anchor_block_index = array_search( $anchor_block, array_column( $block['innerBlocks'], 'blockName' ), true ); | ||
if ( false !== $anchor_block_index && ( 'after' === $relative_position || 'before' === $relative_position ) ) { | ||
if ( 'after' === $relative_position ) { | ||
$anchor_block_index++; | ||
} | ||
array_splice( $block['innerBlocks'], $anchor_block_index, 0, array( $inserted_block ) ); | ||
|
||
// Find matching `innerContent` chunk index. | ||
$chunk_index = 0; | ||
while ( $anchor_block_index > 0 ) { | ||
if ( ! is_string( $block['innerContent'][ $chunk_index ] ) ) { | ||
$anchor_block_index--; | ||
} | ||
$chunk_index++; | ||
} | ||
// Since WP_Block::render() iterates over `inner_content` (rather than `inner_blocks`) | ||
// when rendering blocks, we also need to insert a value (`null`, to mark a block | ||
// location) into that array. | ||
array_splice( $block['innerContent'], $chunk_index, 0, array( null ) ); | ||
} | ||
return $block; | ||
}; | ||
} | ||
|
||
function gutenberg_register_auto_inserted_blocks( $settings, $metadata ) { | ||
if ( ! isset( $metadata['__experimentalAutoInsert'] ) ) { | ||
return $settings; | ||
} | ||
$auto_insert = $metadata['__experimentalAutoInsert']; | ||
|
||
$property_mappings = array( | ||
'before' => 'before', | ||
'after' => 'after', | ||
'firstChild' => 'first_child', | ||
'lastChild' => 'last_child', | ||
); | ||
|
||
$inserted_block_name = $metadata['name']; | ||
foreach ( $auto_insert as $anchor_block_name => $position ) { | ||
// Avoid infinite recursion (auto-inserting into self). | ||
if ( $inserted_block_name === $anchor_block_name ) { | ||
_doing_it_wrong( | ||
__METHOD__, | ||
__( 'Cannot auto-insert block next to itself.', 'gutenberg' ), | ||
'6.4.0' | ||
); | ||
continue; | ||
} | ||
|
||
if ( ! isset( $property_mappings[ $position ] ) ) { | ||
continue; | ||
} | ||
|
||
$mapped_position = $property_mappings[ $position ]; | ||
|
||
$inserted_block = array( | ||
'blockName' => $inserted_block_name, | ||
'attrs' => array(), | ||
'innerHTML' => '', | ||
'innerContent' => array(), | ||
'innerBlocks' => array(), | ||
); | ||
|
||
// TODO: In the long run, we'd likely want some sort of registry for auto-inserted blocks. | ||
|
||
// Auto-insert sibling and child blocks into the editor (via the templates and patterns | ||
// REST API endpoints), and auto-insert sibling blocks on the frontend. | ||
$inserter = gutenberg_auto_insert_block( $anchor_block_name, $mapped_position, $inserted_block ); | ||
add_filter( 'gutenberg_serialize_block', $inserter, 10, 1 ); | ||
|
||
$settings['auto_insert'][ $anchor_block_name ] = $mapped_position; | ||
} | ||
|
||
return $settings; | ||
} | ||
add_filter( 'block_type_metadata_settings', 'gutenberg_register_auto_inserted_blocks', 10, 2 ); | ||
|
||
function gutenberg_parse_and_serialize_block_templates( $query_result ) { | ||
foreach ( $query_result as $block_template ) { | ||
if ( 'theme' !== $block_template->source ) { | ||
continue; | ||
} | ||
$blocks = parse_blocks( $block_template->content ); | ||
$block_template->content = gutenberg_serialize_blocks( $blocks ); | ||
} | ||
|
||
return $query_result; | ||
} | ||
add_filter( 'get_block_templates', 'gutenberg_parse_and_serialize_block_templates', 10, 1 ); | ||
|
||
/** | ||
* Filters the block template object after it has been (potentially) fetched from the theme file. | ||
* | ||
* @param WP_Block_Template|null $block_template The found block template, or null if there is none. | ||
*/ | ||
function gutenberg_parse_and_serialize_blocks( $block_template ) { | ||
|
||
$blocks = parse_blocks( $block_template->content ); | ||
$block_template->content = gutenberg_serialize_blocks( $blocks ); | ||
|
||
return $block_template; | ||
} | ||
add_filter( 'get_block_file_template', 'gutenberg_parse_and_serialize_blocks', 10, 1 ); | ||
|
||
/** | ||
* Filterable version of `serialize_blocks()`. | ||
* | ||
* This function is identical to `serialize_blocks()`, except that it applies | ||
* the `gutenberg_serialize_block` filter to each block before it is serialized. | ||
* | ||
* @param array $block The block to be serialized. | ||
* @return string The serialized block. | ||
* | ||
* @see serialize_blocks() | ||
*/ | ||
function gutenberg_serialize_block( $block ) { | ||
$block_content = ''; | ||
|
||
/** | ||
* Filters a block before it is serialized. | ||
* | ||
* @param array $block The block to be serialized. | ||
*/ | ||
$block = apply_filters( 'gutenberg_serialize_block', $block ); | ||
|
||
$index = 0; | ||
foreach ( $block['innerContent'] as $chunk ) { | ||
if ( is_string( $chunk ) ) { | ||
$block_content .= $chunk; | ||
} else { // Compare to WP_Block::render(). | ||
$inner_block = $block['innerBlocks'][ $index++ ]; | ||
$block_content .= gutenberg_serialize_block( $inner_block ); | ||
} | ||
} | ||
|
||
if ( ! is_array( $block['attrs'] ) ) { | ||
$block['attrs'] = array(); | ||
} | ||
|
||
return get_comment_delimited_block_content( | ||
$block['blockName'], | ||
$block['attrs'], | ||
$block_content | ||
); | ||
} | ||
|
||
function gutenberg_serialize_blocks( $blocks ) { | ||
return implode( '', array_map( 'gutenberg_serialize_block', $blocks ) ); | ||
} |
40 changes: 40 additions & 0 deletions
40
lib/experimental/class-gutenberg-rest-block-patterns-controller.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
/** | ||
* REST API: Gutenberg_REST_Block_Patterns_Controller class | ||
* | ||
* @package Gutenberg | ||
* @subpackage REST_API | ||
*/ | ||
|
||
/** | ||
* Core class used to access block patterns via the REST API. | ||
* | ||
* @since 6.4.0 | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class Gutenberg_REST_Block_Patterns_Controller extends Gutenberg_REST_Block_Patterns_Controller_6_2 { | ||
/** | ||
* Prepare a raw block pattern before it gets output in a REST API response. | ||
* | ||
* @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. | ||
* | ||
* @param array $item Raw pattern as registered, before any changes. | ||
* @param WP_REST_Request $request Request object. | ||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. | ||
*/ | ||
public function prepare_item_for_response( $item, $request ) { | ||
$response = parent::prepare_item_for_response( $item, $request ); | ||
if ( ! gutenberg_is_experiment_enabled( 'gutenberg-auto-inserting-blocks' ) ) { | ||
return $response; | ||
} | ||
|
||
$data = $response->get_data(); | ||
|
||
$blocks = parse_blocks( $data['content'] ); | ||
$data['content'] = gutenberg_serialize_blocks( $blocks ); // Serialize or render? | ||
return rest_ensure_response( $data ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters