Skip to content

Commit

Permalink
prep build 01/16
Browse files Browse the repository at this point in the history
  • Loading branch information
bph committed Jan 16, 2024
2 parents d8076be + 33b6e64 commit c86cb85
Show file tree
Hide file tree
Showing 56 changed files with 1,594 additions and 2,606 deletions.
2 changes: 1 addition & 1 deletion docs/getting-started/quick-start-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This guide is designed to demonstrate the basic principles of block development in WordPress using a hands-on approach. Following the steps below, you will create a custom block plugin that uses modern JavaScript (ESNext and JSX) in a matter of minutes. The example block displays the copyright symbol (©) and the current year, the perfect addition to any website's footer. You can see these steps in action through this short video demonstration.

<div style="position:relative;overflow:hidden;padding-top:56.25%;"><iframe src="https://www.youtube.com/embed/nrut8SfXA44?si=YxvmHmAoYx-BDCog" title="WordPress Block Development: Quick Start Guide Video" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="true" style="position:absolute;top:0;left:0;width:100%;max-width:960px;height:100%;max-height:540px;"></iframe></div>
<iframe width="960" height="540" src="https://www.youtube.com/embed/nrut8SfXA44?si=YxvmHmAoYx-BDCog" title="WordPress Block Development: Quick Start Guide Video" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="true"></iframe>

## Scaffold the block plugin

Expand Down
53 changes: 13 additions & 40 deletions lib/experimental/interactivity-api/class-wp-directive-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,26 @@ class WP_Directive_Processor extends Gutenberg_HTML_Tag_Processor_6_5 {
*
* @var string
*/
public static $root_block = null;

/**
* Array containing the direct children of interactive blocks.
*
* @var array
*/
public static $children_of_interactive_block = array();
public static $interactive_root_block = null;

/**
* Sets the current root block.
*
* @param array $block The block to add.
*/
public static function mark_root_block( $block ) {
public static function mark_interactive_root_block( $block ) {
if ( null !== $block['blockName'] ) {
self::$root_block = $block['blockName'] . md5( serialize( $block ) );
self::$interactive_root_block = $block['blockName'] . md5( serialize( $block ) );
} else {
self::$root_block = md5( serialize( $block ) );
self::$interactive_root_block = md5( serialize( $block ) );
}
}

/**
* Resets the root block.
*/
public static function unmark_root_block() {
self::$root_block = null;
public static function unmark_interactive_root_block() {
self::$interactive_root_block = null;
}

/**
Expand All @@ -55,45 +48,25 @@ public static function unmark_root_block() {
* @param array $block The block to check.
* @return bool True if block is a root block, false otherwise.
*/
public static function is_marked_as_root_block( $block ) {
// If self::$root_block is null, is impossible that any block has been marked as root.
if ( is_null( self::$root_block ) ) {
public static function is_marked_as_interactive_root_block( $block ) {
// If self::$interactive_root_block is null, is impossible that any block has been marked as root.
if ( is_null( self::$interactive_root_block ) ) {
return false;
}
// Blocks whose blockName is null are specifically intended to convey - "this is a freeform HTML block."
if ( null !== $block['blockName'] ) {
return str_contains( self::$root_block, $block['blockName'] ) && $block['blockName'] . md5( serialize( $block ) ) === self::$root_block;
return str_contains( self::$interactive_root_block, $block['blockName'] ) && $block['blockName'] . md5( serialize( $block ) ) === self::$interactive_root_block;
}
return md5( serialize( $block ) ) === self::$root_block;
return md5( serialize( $block ) ) === self::$interactive_root_block;
}

/**
* Checks if a root block has already been defined.
*
* @return bool True if there is a root block, false otherwise.
*/
public static function has_root_block() {
return isset( self::$root_block );
}

/**
* Stores a reference to a direct children of an interactive block to be able
* to identify it later.
*
* @param array $block The block to add.
*/
public static function mark_children_of_interactive_block( $block ) {
self::$children_of_interactive_block[] = md5( serialize( $block ) );
}

/**
* Checks if block is marked as children of an interactive block.
*
* @param array $block The block to check.
* @return bool True if block is a children of an interactive block, false otherwise.
*/
public static function is_marked_as_children_of_interactive_block( $block ) {
return in_array( md5( serialize( $block ) ), self::$children_of_interactive_block, true );
public static function has_interactive_root_block() {
return isset( self::$interactive_root_block );
}

/**
Expand Down
187 changes: 19 additions & 168 deletions lib/experimental/interactivity-api/directive-processing.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,21 @@
* parent is null.
*
* @param array $parsed_block The parsed block.
* @param array $source_block The source block.
* @param array $parent_block The parent block.
*
* @return array The parsed block.
*/
function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block, $parent_block ) {
if ( ! isset( $parent_block ) && ! WP_Directive_Processor::has_root_block() ) {
WP_Directive_Processor::mark_root_block( $parsed_block );
function gutenberg_interactivity_mark_root_interactive_blocks( $parsed_block ) {
if ( ! WP_Directive_Processor::has_interactive_root_block() ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $parsed_block['blockName'] );
$is_interactive = isset( $block_type->supports['interactivity'] ) && $block_type->supports['interactivity'];
if ( $is_interactive ) {
WP_Directive_Processor::mark_interactive_root_block( $parsed_block );
}
}

return $parsed_block;
}
add_filter( 'render_block_data', 'gutenberg_interactivity_mark_root_blocks', 10, 3 );
add_filter( 'render_block_data', 'gutenberg_interactivity_mark_root_interactive_blocks', 10, 1 );

/**
* Processes the directives in the root blocks.
Expand All @@ -36,145 +38,16 @@ function gutenberg_interactivity_mark_root_blocks( $parsed_block, $source_block,
* @return string Filtered block content.
*/
function gutenberg_process_directives_in_root_blocks( $block_content, $block ) {
if ( WP_Directive_Processor::is_marked_as_root_block( $block ) ) {
WP_Directive_Processor::unmark_root_block();

// Parse our own block delimiters for interactive and non-interactive blocks.
$parsed_blocks = parse_blocks( $block_content );
$context = new WP_Directive_Context();
$processed_content = '';
$namespace_stack = array();

foreach ( $parsed_blocks as $parsed_block ) {
if ( 'core/interactivity-wrapper' === $parsed_block['blockName'] ) {
$processed_content .= gutenberg_process_interactive_block( $parsed_block, $context, $namespace_stack );
} elseif ( 'core/non-interactivity-wrapper' === $parsed_block['blockName'] ) {
$processed_content .= gutenberg_process_non_interactive_block( $parsed_block, $context, $namespace_stack );
} else {
$processed_content .= $parsed_block['innerHTML'];
}
}
return $processed_content;
}

return $block_content;
}
add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 20, 2 );

/**
* Marks the block as a children of an interactive block.
*
* @param array $parsed_block The parsed block.
* @param array $source_block The source block.
* @param WP_Block $parent_block The parent block.
*/
function gutenberg_mark_chidren_of_interactive_block( $parsed_block, $source_block, $parent_block ) {
if (
isset( $parent_block ) &&
isset( $parent_block->block_type->supports['interactivity'] ) &&
$parent_block->block_type->supports['interactivity']
) {
WP_Directive_Processor::mark_children_of_interactive_block( $source_block );
if ( WP_Directive_Processor::is_marked_as_interactive_root_block( $block ) ) {
WP_Directive_Processor::unmark_interactive_root_block();
$context = new WP_Directive_Context();
$namespace_stack = array();
return gutenberg_process_interactive_html( $block_content, $context, $namespace_stack );
}
return $parsed_block;
}
add_filter( 'render_block_data', 'gutenberg_mark_chidren_of_interactive_block', 100, 3 );

/**
* Adds a comment delimiter to mark if the block is interactive or not.
*
* @param string $block_content The block content.
* @param array $block The full block, including name and attributes.
* @param WP_Block $block_instance The block instance.
*/
function gutenberg_mark_block_interactivity( $block_content, $block, $block_instance ) {
if (
isset( $block_instance->block_type->supports['interactivity'] ) &&
$block_instance->block_type->supports['interactivity']
) {
// Wraps the interactive block with a comment delimiter to be able to
// process it later.
return get_comment_delimited_block_content(
'core/interactivity-wrapper',
array(),
$block_content
);
} elseif ( WP_Directive_Processor::is_marked_as_children_of_interactive_block( $block ) ) {
// Wraps the non-interactive block with a comment delimiter to be able to
// skip it later.
return get_comment_delimited_block_content(
'core/non-interactivity-wrapper',
array(),
$block_content
);
}
return $block_content;
}
add_filter( 'render_block', 'gutenberg_mark_block_interactivity', 10, 3 );

/**
* Traverses the HTML of an interactive block, searching for Interactivity API
* directives and processing them. For the inner blocks, it calls the
* corresponding function depending on the wrapper type.
*
* @param array $interactive_block The interactive block to process.
* @param WP_Directive_Context $context The context to use when processing.
* @param array $namespace_stack Stack of namespackes passed by reference.
*
* @return string The processed HTML.
*/
function gutenberg_process_interactive_block( $interactive_block, $context, &$namespace_stack ) {
$block_index = 0;
$content = '';
$interactive_inner_blocks = array();

foreach ( $interactive_block['innerContent'] as $inner_content ) {
if ( is_string( $inner_content ) ) {
$content .= $inner_content;
} else {
// This is an inner block. It may be an interactive block or a
// non-interactive block.
$content .= '<wp-inner-blocks-' . $block_index . '></wp-inner-blocks-' . $block_index . '>';
$interactive_inner_blocks[] = $interactive_block['innerBlocks'][ $block_index++ ];
}
}

return gutenberg_process_interactive_html( $content, $context, $interactive_inner_blocks, $namespace_stack );
}

/**
* Returns the HTML of a non-interactive block without processing the
* directives. For the inner blocks, it calls the corresponding function
* depending on the wrapper type.
*
* @param array $non_interactive_block The non-interactive block to process.
* @param WP_Directive_Context $context The context to use when processing.
* @param array $namespace_stack Stack of namespackes passed by reference.
*
* @return string The processed HTML.
*/
function gutenberg_process_non_interactive_block( $non_interactive_block, $context, &$namespace_stack ) {
$block_index = 0;
$content = '';
foreach ( $non_interactive_block['innerContent'] as $inner_content ) {
if ( is_string( $inner_content ) ) {
// This content belongs to a non interactive block and therefore it cannot
// contain directives. We add the HTML directly to the final output.
$content .= $inner_content;
} else {
// This is an inner block. It may be an interactive block or a
// non-interactive block.
$inner_block = $non_interactive_block['innerBlocks'][ $block_index++ ];

if ( 'core/interactivity-wrapper' === $inner_block['blockName'] ) {
$content .= gutenberg_process_interactive_block( $inner_block, $context, $namespace_stack );
} elseif ( 'core/non-interactivity-wrapper' === $inner_block['blockName'] ) {
$content .= gutenberg_process_non_interactive_block( $inner_block, $context, $namespace_stack );
}
}
}
return $content;
}
add_filter( 'render_block', 'gutenberg_process_directives_in_root_blocks', 10, 2 );

/**
* Processes interactive HTML by applying directives to the HTML tags.
Expand All @@ -191,7 +64,7 @@ function gutenberg_process_non_interactive_block( $non_interactive_block, $conte
*
* @return string The processed HTML.
*/
function gutenberg_process_interactive_html( $html, $context, $inner_blocks = array(), &$namespace_stack = array() ) {
function gutenberg_process_interactive_html( $html, $context, &$namespace_stack = array() ) {
static $directives = array(
'data-wp-interactive' => 'gutenberg_interactivity_process_wp_interactive',
'data-wp-context' => 'gutenberg_interactivity_process_wp_context',
Expand All @@ -201,22 +74,12 @@ function gutenberg_process_interactive_html( $html, $context, $inner_blocks = ar
'data-wp-text' => 'gutenberg_interactivity_process_wp_text',
);

$tags = new WP_Directive_Processor( $html );
$prefix = 'data-wp-';
$tag_stack = array();
$inner_processed_blocks = array();
$inner_blocks_index = 0;
$tags = new WP_Directive_Processor( $html );
$prefix = 'data-wp-';
$tag_stack = array();
while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag_name = $tags->get_tag();

// Processes the inner blocks.
if ( str_contains( $tag_name, 'WP-INNER-BLOCKS' ) && ! empty( $inner_blocks ) && ! $tags->is_tag_closer() ) {
if ( 'core/interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) {
$inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $namespace_stack );
} elseif ( 'core/non-interactivity-wrapper' === $inner_blocks[ $inner_blocks_index ]['blockName'] ) {
$inner_processed_blocks[ strtolower( $tag_name ) ] = gutenberg_process_non_interactive_block( $inner_blocks[ $inner_blocks_index++ ], $context, $namespace_stack );
}
}
if ( $tags->is_tag_closer() ) {
if ( 0 === count( $tag_stack ) ) {
continue;
Expand Down Expand Up @@ -287,19 +150,7 @@ function gutenberg_process_interactive_html( $html, $context, $inner_blocks = ar
}
}

$processed_html = $tags->get_updated_html();

// Replaces the inner block tags with the content of each inner block
// processed.
if ( ! empty( $inner_processed_blocks ) ) {
foreach ( $inner_processed_blocks as $inner_block_tag => $inner_block_content ) {
if ( str_contains( $processed_html, $inner_block_tag ) ) {
$processed_html = str_replace( '<' . $inner_block_tag . '></' . $inner_block_tag . '>', $inner_block_content, $processed_html );
}
}
}

return $processed_html;
return $tags->get_updated_html();
}

/**
Expand Down
Loading

0 comments on commit c86cb85

Please sign in to comment.