Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interactivity API: Implement wp_initial_state() #56698

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b545146
Initial commit
cbravobernal Nov 17, 2023
296f237
It works, but is stripping comments
cbravobernal Nov 18, 2023
4adc34c
Added an extra return for debugging
cbravobernal Nov 18, 2023
b7185a3
Refactor to use string instead of arrays to compare
cbravobernal Nov 18, 2023
4116193
Use hidden textarea to save comments in production html
cbravobernal Nov 18, 2023
585e8fd
Use divs as delimiters, comments not working for interactive innner c…
cbravobernal Nov 18, 2023
ca33458
Back to array for references and comment delimiters
cbravobernal Nov 28, 2023
0c4d2e2
Restore changes from `store()` migration PR
DAreRodz Nov 29, 2023
9e3f90f
Merge branch 'update/interactivity-mark-interactive-parts' into add/i…
DAreRodz Nov 29, 2023
d2aca77
Handle namespaces in Directive Processor
DAreRodz Nov 29, 2023
ad0cc8c
Add data-wp-interactive processor
DAreRodz Nov 29, 2023
ef45f11
Pass namespace to `evaluate_reference`
DAreRodz Nov 30, 2023
a4970ae
Use `$this` instead of `$tags`
DAreRodz Nov 30, 2023
ceb84d7
Execute directives by priority
DAreRodz Nov 30, 2023
56f77c4
Add context with its namespace
DAreRodz Nov 30, 2023
effeabc
Handle directive-level namespaces
DAreRodz Nov 30, 2023
4cb8ee1
Fix value_ns regex
DAreRodz Nov 30, 2023
8b7f2b0
Add initial state under `state`
DAreRodz Nov 30, 2023
1d15166
Change `evaluate_reference` arguments order
DAreRodz Nov 30, 2023
6d0ddef
Update `evaluate_reference` tests
DAreRodz Nov 30, 2023
e6ffd1d
Add tests for value namespaces
DAreRodz Nov 30, 2023
5af5ad9
Update directive processing tests
DAreRodz Nov 30, 2023
95e84f3
Parse wp-interactive JSON as array
DAreRodz Nov 30, 2023
68fcce4
Update wp-bind tests
DAreRodz Nov 30, 2023
defec0c
Fix wp-class tests
DAreRodz Nov 30, 2023
881c5e4
Update wp-style tests
DAreRodz Nov 30, 2023
976fea7
Update wp-text tests
DAreRodz Nov 30, 2023
6b721a0
Update wp-context tests
DAreRodz Nov 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 117 additions & 14 deletions lib/experimental/interactivity-api/class-wp-directive-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
class WP_Directive_Processor extends Gutenberg_HTML_Tag_Processor_6_4 {

/**
* An array of root blocks.
* A string containing the main root block.
*
* @var array
* @var string
*/
public static $root_block = null;

Expand Down Expand Up @@ -69,6 +69,76 @@ public static function has_root_block() {
return isset( self::$root_block );
}

/**
* An array containing the main children of interactive.
*
* @var array
*/
public static $children_of_interactive_block = array();

/**
* Add a root block to the variable.
*
* @param array $block The block to add.
*
* @return void
*/
public static function mark_children_of_interactive_block( $block ) {
self::$children_of_interactive_block[] = md5( serialize( $block ) );
}

/**
* Remove a root block to the variable.
*
* @param array $block The block to remove.
* @return void
*/
public static function unmark_children_of_interactive_block( $block ) {
self::$children_of_interactive_block = array_diff( self::$children_of_interactive_block, array( md5( serialize( $block ) ) ) );
}

/**
* Check if block is a 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_children_of_interactive_block( $block ) {
return in_array( md5( serialize( $block ) ), self::$children_of_interactive_block, true );
}

/**
* Stack of namespaces.
*
* @var string[]
*/
private $ns_stack = array();

/**
* Push a new namespace onto the namespace stack.
*
* @param string $ns The new namespace.
*/
public function push_namespace( string $ns ) {
$this->ns_stack[] = $ns;
}

/**
* Discard the current namespace.
*/
public function pop_namespace() {
array_pop( $this->ns_stack );
}

/**
* Return the current namespace, or false if no namespace is set.
*
* @return string|bool
*/
public function get_namespace() {
return end( $this->ns_stack );
}

/**
* Find the matching closing tag for an opening tag.
Expand Down Expand Up @@ -114,22 +184,25 @@ public function next_balanced_closer() {
* Traverses the HTML searching for Interactivity API directives and processing
* them.
*
* @param WP_Directive_Processor $tags An instance of the WP_Directive_Processor.
* @param string $prefix Attribute prefix.
* @param string[] $directives Directives.
* @param string $prefix Attribute prefix.
* @param string[] $directives Directives.
*
* @return WP_Directive_Processor The modified instance of the
* WP_Directive_Processor.
*/
public function process_rendered_html( $tags, $prefix, $directives ) {
public function process_rendered_html( $prefix, $directives ) {
$context = new WP_Directive_Context();
$tag_stack = array();

while ( $tags->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag_name = $tags->get_tag();
// Extract all directive names. They'll be used later on.
$directive_names = array_keys( $directives );
$directive_names_rev = array_reverse( $directive_names );

while ( $this->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
$tag_name = $this->get_tag();

// Is this a tag that closes the latest opening tag?
if ( $tags->is_tag_closer() ) {
if ( $this->is_tag_closer() ) {
if ( 0 === count( $tag_stack ) ) {
continue;
}
Expand All @@ -145,7 +218,7 @@ public function process_rendered_html( $tags, $prefix, $directives ) {
}
} else {
$attributes = array();
foreach ( $tags->get_attribute_names_with_prefix( $prefix ) as $name ) {
foreach ( $this->get_attribute_names_with_prefix( $prefix ) as $name ) {
/*
* Removes the part after the double hyphen before looking for
* the directive processor inside `$directives`, e.g., "wp-bind"
Expand All @@ -164,19 +237,32 @@ public function process_rendered_html( $tags, $prefix, $directives ) {
* encounter the matching closing tag.
*/
if (
! WP_Directive_Processor::is_html_void_element( $tags->get_tag() ) &&
! WP_Directive_Processor::is_html_void_element( $this->get_tag() ) &&
( 0 !== count( $attributes ) || 0 !== count( $tag_stack ) )
) {
$tag_stack[] = array( $tag_name, $attributes );
}
}

foreach ( $attributes as $attribute ) {
call_user_func( $directives[ $attribute ], $tags, $context );
/*
* Sort attributes by the order they appear in the `$directives`
* argument, considering it as the priority order in which
* directives should be processed. Note that the order is reversed
* for tag closers.
*/
$sorted_attrs = array_intersect(
$this->is_tag_closer()
? $directive_names_rev
: $directive_names,
$attributes
);

foreach ( $sorted_attrs as $attribute ) {
call_user_func( $directives[ $attribute ], $this, $context );
}
}

return $tags;
return $this;
}

/**
Expand Down Expand Up @@ -312,4 +398,21 @@ public static function is_html_void_element( $tag_name ) {
public static function parse_attribute_name( $name ) {
return explode( '--', $name, 2 );
}

/**
* Extract and return the namespace from the given directive value.
*
* @param string $value The directive value.
* @return array The resulting array
*/
public static function parse_value_ns( $value ) {
$matches = array();
$has_ns = preg_match( '/^([\w\-_\/]+)::(.+)$/', $value, $matches );

if ( $has_ns ) {
return array_slice( $matches, 1 );
} else {
return array( null, $value );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
/**
* WP_Interactivity_Initial_State class
*
* @package Gutenberg
* @subpackage Interactivity API
*/

if ( class_exists( 'WP_Interactivity_Initial_State' ) ) {
return;
}

/**
* Manages the initial state of the Interactivity API store in the server and
* its serialization so it can be restored in the browser upon hydration.
*
* @package Gutenberg
* @subpackage Interactivity API
*/
class WP_Interactivity_Initial_State {
/**
* Map of initial state by namespace.
*
* @var array
*/
private static $initial_state = array();

/**
* Get state from a given namespace.
*
* @param string $store_ns Namespace.
*
* @return array The requested state.
*/
public static function get_state( $store_ns = null ) {
if ( ! $store_ns ) {
return self::$initial_state;
}
return self::$initial_state[ $store_ns ] ?? array();
}

/**
* Merge data into the state with the given namespace.
*
* @param string $store_ns Namespace.
* @param array $data State to merge.
*
* @return void
*/
public static function merge_state( $store_ns, $data ) {
self::$initial_state[ $store_ns ] = array_replace_recursive(
self::get_state( $store_ns ),
$data
);
}

/**
* Get store data.
*
* @return array
*/
public static function get_data() {
return self::$initial_state;
}

/**
* Reset the initial state.
*/
public static function reset() {
self::$initial_state = array();
}

/**
* Render the initial state.
*/
public static function render() {
if ( empty( self::$initial_state ) ) {
return;
}
echo sprintf(
'<script id="wp-interactivity-initial-state" type="application/json">%s</script>',
wp_json_encode( self::$initial_state, JSON_HEX_TAG | JSON_HEX_AMP )
);
}
}

This file was deleted.

Loading
Loading