diff --git a/.github/workflows/create-block.yml b/.github/workflows/create-block.yml
index ff1f1eb980347b..2a7ea040f8b493 100644
--- a/.github/workflows/create-block.yml
+++ b/.github/workflows/create-block.yml
@@ -20,7 +20,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: ['20', '21']
+ node: ['20', '22']
os: ['macos-latest', 'ubuntu-latest', 'windows-latest']
steps:
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index 570fd8477b5fed..a813e4d2d8f5ba 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -27,7 +27,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: ['20', '21']
+ node: ['20', '22']
shard: ['1/4', '2/4', '3/4', '4/4']
steps:
@@ -66,7 +66,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node: ['20', '21']
+ node: ['20', '22']
steps:
- name: Checkout repository
diff --git a/lib/compat/wordpress-6.5/blocks.php b/lib/compat/wordpress-6.5/blocks.php
index 5c5688e7cdf43e..b91e89103faa02 100644
--- a/lib/compat/wordpress-6.5/blocks.php
+++ b/lib/compat/wordpress-6.5/blocks.php
@@ -46,176 +46,179 @@ function gutenberg_register_metadata_attribute( $args ) {
}
add_filter( 'register_block_type_args', 'gutenberg_register_metadata_attribute' );
-/**
- * Depending on the block attribute name, replace its value in the HTML based on the value provided.
- *
- * @param string $block_content Block Content.
- * @param string $block_name The name of the block to process.
- * @param string $attribute_name The attribute name to replace.
- * @param mixed $source_value The value used to replace in the HTML.
- * @return string The modified block content.
- */
-function gutenberg_block_bindings_replace_html( $block_content, $block_name, string $attribute_name, $source_value ) {
- $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
- if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) {
- return $block_content;
- }
+// Only process block bindings if they are not processed in core.
+if ( ! class_exists( 'WP_Block_Bindings_Registry' ) ) {
+ /**
+ * Depending on the block attribute name, replace its value in the HTML based on the value provided.
+ *
+ * @param string $block_content Block Content.
+ * @param string $block_name The name of the block to process.
+ * @param string $attribute_name The attribute name to replace.
+ * @param mixed $source_value The value used to replace in the HTML.
+ * @return string The modified block content.
+ */
+ function gutenberg_block_bindings_replace_html( $block_content, $block_name, string $attribute_name, $source_value ) {
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block_name );
+ if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) {
+ return $block_content;
+ }
- // Depending on the attribute source, the processing will be different.
- switch ( $block_type->attributes[ $attribute_name ]['source'] ) {
- case 'html':
- case 'rich-text':
- $block_reader = new WP_HTML_Tag_Processor( $block_content );
-
- // TODO: Support for CSS selectors whenever they are ready in the HTML API.
- // In the meantime, support comma-separated selectors by exploding them into an array.
- $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] );
- // Add a bookmark to the first tag to be able to iterate over the selectors.
- $block_reader->next_tag();
- $block_reader->set_bookmark( 'iterate-selectors' );
-
- // TODO: This shouldn't be needed when the `set_inner_html` function is ready.
- // Store the parent tag and its attributes to be able to restore them later in the button.
- // The button block has a wrapper while the paragraph and heading blocks don't.
- if ( 'core/button' === $block_name ) {
- $button_wrapper = $block_reader->get_tag();
- $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
- $button_wrapper_attrs = array();
- foreach ( $button_wrapper_attribute_names as $name ) {
- $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name );
+ // Depending on the attribute source, the processing will be different.
+ switch ( $block_type->attributes[ $attribute_name ]['source'] ) {
+ case 'html':
+ case 'rich-text':
+ $block_reader = new WP_HTML_Tag_Processor( $block_content );
+
+ // TODO: Support for CSS selectors whenever they are ready in the HTML API.
+ // In the meantime, support comma-separated selectors by exploding them into an array.
+ $selectors = explode( ',', $block_type->attributes[ $attribute_name ]['selector'] );
+ // Add a bookmark to the first tag to be able to iterate over the selectors.
+ $block_reader->next_tag();
+ $block_reader->set_bookmark( 'iterate-selectors' );
+
+ // TODO: This shouldn't be needed when the `set_inner_html` function is ready.
+ // Store the parent tag and its attributes to be able to restore them later in the button.
+ // The button block has a wrapper while the paragraph and heading blocks don't.
+ if ( 'core/button' === $block_name ) {
+ $button_wrapper = $block_reader->get_tag();
+ $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
+ $button_wrapper_attrs = array();
+ foreach ( $button_wrapper_attribute_names as $name ) {
+ $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name );
+ }
}
- }
- foreach ( $selectors as $selector ) {
- // If the parent tag, or any of its children, matches the selector, replace the HTML.
- if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag(
- array(
- 'tag_name' => $selector,
- )
- ) ) {
- $block_reader->release_bookmark( 'iterate-selectors' );
-
- // TODO: Use `set_inner_html` method whenever it's ready in the HTML API.
- // Until then, it is hardcoded for the paragraph, heading, and button blocks.
- // Store the tag and its attributes to be able to restore them later.
- $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
- $selector_attrs = array();
- foreach ( $selector_attribute_names as $name ) {
- $selector_attrs[ $name ] = $block_reader->get_attribute( $name );
- }
- $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . "$selector>";
- $amended_content = new WP_HTML_Tag_Processor( $selector_markup );
- $amended_content->next_tag();
- foreach ( $selector_attrs as $attribute_key => $attribute_value ) {
- $amended_content->set_attribute( $attribute_key, $attribute_value );
- }
- if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) {
- return $amended_content->get_updated_html();
- }
- if ( 'core/button' === $block_name ) {
- $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}$button_wrapper>";
- $amended_button = new WP_HTML_Tag_Processor( $button_markup );
- $amended_button->next_tag();
- foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) {
- $amended_button->set_attribute( $attribute_key, $attribute_value );
+ foreach ( $selectors as $selector ) {
+ // If the parent tag, or any of its children, matches the selector, replace the HTML.
+ if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag(
+ array(
+ 'tag_name' => $selector,
+ )
+ ) ) {
+ $block_reader->release_bookmark( 'iterate-selectors' );
+
+ // TODO: Use `set_inner_html` method whenever it's ready in the HTML API.
+ // Until then, it is hardcoded for the paragraph, heading, and button blocks.
+ // Store the tag and its attributes to be able to restore them later.
+ $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' );
+ $selector_attrs = array();
+ foreach ( $selector_attribute_names as $name ) {
+ $selector_attrs[ $name ] = $block_reader->get_attribute( $name );
+ }
+ $selector_markup = "<$selector>" . wp_kses_post( $source_value ) . "$selector>";
+ $amended_content = new WP_HTML_Tag_Processor( $selector_markup );
+ $amended_content->next_tag();
+ foreach ( $selector_attrs as $attribute_key => $attribute_value ) {
+ $amended_content->set_attribute( $attribute_key, $attribute_value );
+ }
+ if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) {
+ return $amended_content->get_updated_html();
+ }
+ if ( 'core/button' === $block_name ) {
+ $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}$button_wrapper>";
+ $amended_button = new WP_HTML_Tag_Processor( $button_markup );
+ $amended_button->next_tag();
+ foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) {
+ $amended_button->set_attribute( $attribute_key, $attribute_value );
+ }
+ return $amended_button->get_updated_html();
}
- return $amended_button->get_updated_html();
+ } else {
+ $block_reader->seek( 'iterate-selectors' );
}
- } else {
- $block_reader->seek( 'iterate-selectors' );
}
- }
- $block_reader->release_bookmark( 'iterate-selectors' );
- return $block_content;
-
- case 'attribute':
- $amended_content = new WP_HTML_Tag_Processor( $block_content );
- if ( ! $amended_content->next_tag(
- array(
- // TODO: build the query from CSS selector.
- 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'],
- )
- ) ) {
+ $block_reader->release_bookmark( 'iterate-selectors' );
return $block_content;
- }
- $amended_content->set_attribute( $block_type->attributes[ $attribute_name ]['attribute'], $source_value );
- return $amended_content->get_updated_html();
- default:
- return $block_content;
- }
-}
+ case 'attribute':
+ $amended_content = new WP_HTML_Tag_Processor( $block_content );
+ if ( ! $amended_content->next_tag(
+ array(
+ // TODO: build the query from CSS selector.
+ 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'],
+ )
+ ) ) {
+ return $block_content;
+ }
+ $amended_content->set_attribute( $block_type->attributes[ $attribute_name ]['attribute'], $source_value );
+ return $amended_content->get_updated_html();
-/**
- * Process the block bindings attribute.
- *
- * @param string $block_content Block Content.
- * @param array $parsed_block The full block, including name and attributes.
- * @param WP_Block $block_instance The block instance.
- * @return string Block content with the bind applied.
- */
-function gutenberg_process_block_bindings( $block_content, $parsed_block, $block_instance ) {
- $supported_block_attrs = array(
- 'core/paragraph' => array( 'content' ),
- 'core/heading' => array( 'content' ),
- 'core/image' => array( 'id', 'url', 'title', 'alt' ),
- 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ),
- );
-
- // If the block doesn't have the bindings property or isn't one of the supported block types, return.
- if (
- ! isset( $supported_block_attrs[ $block_instance->name ] ) ||
- empty( $parsed_block['attrs']['metadata']['bindings'] ) ||
- ! is_array( $parsed_block['attrs']['metadata']['bindings'] )
- ) {
- return $block_content;
+ default:
+ return $block_content;
+ }
}
- /*
- * Assuming the following format for the bindings property of the "metadata" attribute:
+ /**
+ * Process the block bindings attribute.
*
- * "bindings": {
- * "title": {
- * "source": "core/post-meta",
- * "args": { "key": "text_custom_field" }
- * },
- * "url": {
- * "source": "core/post-meta",
- * "args": { "key": "url_custom_field" }
- * }
- * }
+ * @param string $block_content Block Content.
+ * @param array $parsed_block The full block, including name and attributes.
+ * @param WP_Block $block_instance The block instance.
+ * @return string Block content with the bind applied.
*/
+ function gutenberg_process_block_bindings( $block_content, $parsed_block, $block_instance ) {
+ $supported_block_attrs = array(
+ 'core/paragraph' => array( 'content' ),
+ 'core/heading' => array( 'content' ),
+ 'core/image' => array( 'id', 'url', 'title', 'alt' ),
+ 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ),
+ );
- $modified_block_content = $block_content;
- foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) {
- // If the attribute is not in the supported list, process next attribute.
- if ( ! in_array( $attribute_name, $supported_block_attrs[ $block_instance->name ], true ) ) {
- continue;
- }
- // If no source is provided, or that source is not registered, process next attribute.
- if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) {
- continue;
+ // If the block doesn't have the bindings property or isn't one of the supported block types, return.
+ if (
+ ! isset( $supported_block_attrs[ $block_instance->name ] ) ||
+ empty( $parsed_block['attrs']['metadata']['bindings'] ) ||
+ ! is_array( $parsed_block['attrs']['metadata']['bindings'] )
+ ) {
+ return $block_content;
}
- $block_binding_source = get_block_bindings_source( $block_binding['source'] );
- if ( null === $block_binding_source ) {
- continue;
- }
+ /*
+ * Assuming the following format for the bindings property of the "metadata" attribute:
+ *
+ * "bindings": {
+ * "title": {
+ * "source": "core/post-meta",
+ * "args": { "key": "text_custom_field" }
+ * },
+ * "url": {
+ * "source": "core/post-meta",
+ * "args": { "key": "url_custom_field" }
+ * }
+ * }
+ */
+
+ $modified_block_content = $block_content;
+ foreach ( $parsed_block['attrs']['metadata']['bindings'] as $attribute_name => $block_binding ) {
+ // If the attribute is not in the supported list, process next attribute.
+ if ( ! in_array( $attribute_name, $supported_block_attrs[ $block_instance->name ], true ) ) {
+ continue;
+ }
+ // If no source is provided, or that source is not registered, process next attribute.
+ if ( ! isset( $block_binding['source'] ) || ! is_string( $block_binding['source'] ) ) {
+ continue;
+ }
+
+ $block_binding_source = get_block_bindings_source( $block_binding['source'] );
+ if ( null === $block_binding_source ) {
+ continue;
+ }
- $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array();
- $source_value = $block_binding_source->get_value( $source_args, $block_instance, $attribute_name );
+ $source_args = ! empty( $block_binding['args'] ) && is_array( $block_binding['args'] ) ? $block_binding['args'] : array();
+ $source_value = $block_binding_source->get_value( $source_args, $block_instance, $attribute_name );
- // If the value is not null, process the HTML based on the block and the attribute.
- if ( ! is_null( $source_value ) ) {
- $modified_block_content = gutenberg_block_bindings_replace_html( $modified_block_content, $block_instance->name, $attribute_name, $source_value );
+ // If the value is not null, process the HTML based on the block and the attribute.
+ if ( ! is_null( $source_value ) ) {
+ $modified_block_content = gutenberg_block_bindings_replace_html( $modified_block_content, $block_instance->name, $attribute_name, $source_value );
+ }
}
+
+ return $modified_block_content;
}
- return $modified_block_content;
+ add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 );
}
-add_filter( 'render_block', 'gutenberg_process_block_bindings', 20, 3 );
-
/**
* Enable the viewStyle block API for core versions < 6.5
*
diff --git a/lib/experimental/posts/load.php b/lib/experimental/posts/load.php
new file mode 100644
index 00000000000000..56a600ab97c05d
--- /dev/null
+++ b/lib/experimental/posts/load.php
@@ -0,0 +1,43 @@
+';
+}
+
+/**
+ * Replaces the default posts menu item with the new posts dashboard.
+ */
+function gutenberg_replace_posts_dashboard() {
+ $gutenberg_experiments = get_option( 'gutenberg-experiments' );
+ if ( ! $gutenberg_experiments || ! array_key_exists( 'gutenberg-new-posts-dashboard', $gutenberg_experiments ) || ! $gutenberg_experiments['gutenberg-new-posts-dashboard'] ) {
+ return;
+ }
+ $ptype_obj = get_post_type_object( 'post' );
+ add_submenu_page(
+ 'gutenberg',
+ $ptype_obj->labels->name,
+ $ptype_obj->labels->name,
+ 'edit_posts',
+ 'gutenberg-posts-dashboard',
+ 'gutenberg_posts_dashboard'
+ );
+}
diff --git a/lib/experiments-page.php b/lib/experiments-page.php
index 5147edda0deec5..ca5c87eec737be 100644
--- a/lib/experiments-page.php
+++ b/lib/experiments-page.php
@@ -139,6 +139,18 @@ function gutenberg_initialize_experiments_settings() {
)
);
+ add_settings_field(
+ 'gutenberg-new-posts-dashboard',
+ __( 'Redesigned posts dashboard', 'gutenberg' ),
+ 'gutenberg_display_experiment_field',
+ 'gutenberg-experiments',
+ 'gutenberg_experiments_section',
+ array(
+ 'label' => __( 'Enable a redesigned posts dashboard.', 'gutenberg' ),
+ 'id' => 'gutenberg-new-posts-dashboard',
+ )
+ );
+
register_setting(
'gutenberg-experiments',
'gutenberg-experiments'
diff --git a/lib/load.php b/lib/load.php
index 45b8f1a1ceb3b0..201bfe1967d5b9 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -149,6 +149,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/l10n.php';
require __DIR__ . '/experimental/synchronization.php';
require __DIR__ . '/experimental/script-modules.php';
+require __DIR__ . '/experimental/posts/load.php';
if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) {
require __DIR__ . '/experimental/disable-tinymce.php';
diff --git a/package-lock.json b/package-lock.json
index e2a875e89a92a8..7e20ec0fa393dd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -55127,7 +55127,7 @@
},
"packages/react-native-aztec": {
"name": "@wordpress/react-native-aztec",
- "version": "1.119.1",
+ "version": "1.120.0",
"license": "GPL-2.0-or-later",
"dependencies": {
"@wordpress/element": "file:../element",
@@ -55144,7 +55144,7 @@
},
"packages/react-native-bridge": {
"name": "@wordpress/react-native-bridge",
- "version": "1.119.1",
+ "version": "1.120.0",
"license": "GPL-2.0-or-later",
"dependencies": {
"@wordpress/react-native-aztec": "file:../react-native-aztec"
@@ -55159,7 +55159,7 @@
},
"packages/react-native-editor": {
"name": "@wordpress/react-native-editor",
- "version": "1.119.1",
+ "version": "1.120.0",
"hasInstallScript": true,
"license": "GPL-2.0-or-later",
"dependencies": {
diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss
index 0e3dddf70632b3..c8c35acfce4867 100644
--- a/packages/base-styles/_z-index.scss
+++ b/packages/base-styles/_z-index.scss
@@ -189,8 +189,6 @@ $z-layers: (
".customize-widgets__block-toolbar": 7,
// Site editor layout
- ".edit-site-layout__header-container": 4,
- ".edit-site-layout__hub": 3,
".edit-site-page-header": 2,
".edit-site-page-content": 1,
".edit-site-patterns__dataviews-list-pagination": 2,
diff --git a/packages/block-editor/src/components/inserter/block-types-tab.js b/packages/block-editor/src/components/inserter/block-types-tab.js
index 42c4a6a35e14bf..50a8b46b46427c 100644
--- a/packages/block-editor/src/components/inserter/block-types-tab.js
+++ b/packages/block-editor/src/components/inserter/block-types-tab.js
@@ -207,7 +207,6 @@ export function BlockTypesTab(
showMostUsedBlocks={ showMostUsedBlocks }
className="block-editor-inserter__insertable-blocks-at-selection"
/>
-
>
) }
set_attribute( 'data-wp-interactive', 'core/navigation' );
$tags->set_attribute( 'data-wp-context', '{ "submenuOpenedBy": { "click": false, "hover": false, "focus": false }, "type": "submenu" }' );
$tags->set_attribute( 'data-wp-watch', 'callbacks.initMenu' );
- $tags->set_attribute( 'data-wp-on--focusout', 'actions.handleMenuFocusout' );
+ $tags->set_attribute( 'data-wp-on-async--focusout', 'actions.handleMenuFocusout' );
$tags->set_attribute( 'data-wp-on--keydown', 'actions.handleMenuKeydown' );
// This is a fix for Safari. Without it, Safari doesn't change the active
@@ -836,8 +836,8 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut
$tags->set_attribute( 'tabindex', '-1' );
if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) {
- $tags->set_attribute( 'data-wp-on--mouseenter', 'actions.openMenuOnHover' );
- $tags->set_attribute( 'data-wp-on--mouseleave', 'actions.closeMenuOnHover' );
+ $tags->set_attribute( 'data-wp-on-async--mouseenter', 'actions.openMenuOnHover' );
+ $tags->set_attribute( 'data-wp-on-async--mouseleave', 'actions.closeMenuOnHover' );
}
// Add directives to the toggle submenu button.
@@ -847,7 +847,7 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut
'class_name' => 'wp-block-navigation-submenu__toggle',
)
) ) {
- $tags->set_attribute( 'data-wp-on--click', 'actions.toggleMenuOnClick' );
+ $tags->set_attribute( 'data-wp-on-async--click', 'actions.toggleMenuOnClick' );
$tags->set_attribute( 'data-wp-bind--aria-expanded', 'state.isMenuOpen' );
// The `aria-expanded` attribute for SSR is already added in the submenu block.
}
@@ -858,7 +858,7 @@ function block_core_navigation_add_directives_to_submenu( $tags, $block_attribut
'class_name' => 'wp-block-navigation__submenu-container',
)
) ) {
- $tags->set_attribute( 'data-wp-on--focus', 'actions.openMenuOnFocus' );
+ $tags->set_attribute( 'data-wp-on-async--focus', 'actions.openMenuOnFocus' );
}
// Iterate through subitems if exist.
diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js
index 14d09e061848e7..9da7ab70d84f33 100644
--- a/packages/block-library/src/navigation/view.js
+++ b/packages/block-library/src/navigation/view.js
@@ -143,7 +143,7 @@ const { state, actions } = store(
// If focus is outside modal, and in the document, close menu
// event.target === The element losing focus
// event.relatedTarget === The element receiving focus (if any)
- // When focusout is outsite the document,
+ // When focusout is outside the document,
// `window.document.activeElement` doesn't change.
// The event.relatedTarget is null when something outside the navigation menu is clicked. This is only necessary for Safari.
diff --git a/packages/block-library/src/query-pagination-next/index.php b/packages/block-library/src/query-pagination-next/index.php
index d574f940938469..3b5be47fbed371 100644
--- a/packages/block-library/src/query-pagination-next/index.php
+++ b/packages/block-library/src/query-pagination-next/index.php
@@ -77,7 +77,7 @@ function render_block_core_query_pagination_next( $attributes, $content, $block
) ) {
$p->set_attribute( 'data-wp-key', 'query-pagination-next' );
$p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' );
- $p->set_attribute( 'data-wp-on--mouseenter', 'core/query::actions.prefetch' );
+ $p->set_attribute( 'data-wp-on-async--mouseenter', 'core/query::actions.prefetch' );
$p->set_attribute( 'data-wp-watch', 'core/query::callbacks.prefetch' );
$content = $p->get_updated_html();
}
diff --git a/packages/block-library/src/query-pagination-previous/index.php b/packages/block-library/src/query-pagination-previous/index.php
index 95ed54075a7c54..1592f0a10cbff5 100644
--- a/packages/block-library/src/query-pagination-previous/index.php
+++ b/packages/block-library/src/query-pagination-previous/index.php
@@ -63,7 +63,7 @@ function render_block_core_query_pagination_previous( $attributes, $content, $bl
) ) {
$p->set_attribute( 'data-wp-key', 'query-pagination-previous' );
$p->set_attribute( 'data-wp-on--click', 'core/query::actions.navigate' );
- $p->set_attribute( 'data-wp-on--mouseenter', 'core/query::actions.prefetch' );
+ $p->set_attribute( 'data-wp-on-async--mouseenter', 'core/query::actions.prefetch' );
$p->set_attribute( 'data-wp-watch', 'core/query::callbacks.prefetch' );
$content = $p->get_updated_html();
}
diff --git a/packages/block-library/src/query/editor.scss b/packages/block-library/src/query/editor.scss
index 61b553b6ed02e0..9b9db430a81a65 100644
--- a/packages/block-library/src/query/editor.scss
+++ b/packages/block-library/src/query/editor.scss
@@ -39,7 +39,8 @@
position: sticky;
top: 0;
padding: $grid-unit-20 0;
- margin-bottom: 2px;
+ transform: translateY(- $grid-unit-05); // Offsets the top padding on the modal content container
+ margin-bottom: - $grid-unit-05;
z-index: z-index(".block-library-query-pattern__selection-search");
}
}
diff --git a/packages/block-library/src/search/index.php b/packages/block-library/src/search/index.php
index 9d4c61f4163127..39b8591c86600f 100644
--- a/packages/block-library/src/search/index.php
+++ b/packages/block-library/src/search/index.php
@@ -191,8 +191,8 @@ function render_block_core_search( $attributes ) {
data-wp-interactive="core/search"'
. $form_context .
'data-wp-class--wp-block-search__searchfield-hidden="!context.isSearchInputVisible"
- data-wp-on--keydown="actions.handleSearchKeydown"
- data-wp-on--focusout="actions.handleSearchFocusout"
+ data-wp-on-async--keydown="actions.handleSearchKeydown"
+ data-wp-on-async--focusout="actions.handleSearchFocusout"
';
}
diff --git a/packages/core-data/src/queried-data/reducer.js b/packages/core-data/src/queried-data/reducer.js
index 1f9bb64c7889d7..3a0c8869175c47 100644
--- a/packages/core-data/src/queried-data/reducer.js
+++ b/packages/core-data/src/queried-data/reducer.js
@@ -110,7 +110,8 @@ export function items( state = {}, action ) {
[ context ]: {
...state[ context ],
...action.items.reduce( ( accumulator, value ) => {
- const itemId = value[ key ];
+ const itemId = value?.[ key ];
+
accumulator[ itemId ] = conservativeMapItem(
state?.[ context ]?.[ itemId ],
value
@@ -164,7 +165,7 @@ export function itemIsComplete( state = {}, action ) {
[ context ]: {
...state[ context ],
...action.items.reduce( ( result, item ) => {
- const itemId = item[ key ];
+ const itemId = item?.[ key ];
// Defer to completeness if already assigned. Technically the
// data may be outdated if receiving items for a field subset.
@@ -232,7 +233,7 @@ const receiveQueries = compose( [
return {
itemIds: getMergedItemIds(
state?.itemIds || [],
- action.items.map( ( item ) => item[ key ] ),
+ action.items.map( ( item ) => item?.[ key ] ).filter( Boolean ),
page,
perPage
),
diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js
index 8e6be425244687..3d6473d7610366 100644
--- a/packages/core-data/src/reducer.js
+++ b/packages/core-data/src/reducer.js
@@ -256,7 +256,7 @@ function entity( entityConfig ) {
const nextState = { ...state };
for ( const record of action.items ) {
- const recordId = record[ action.key ];
+ const recordId = record?.[ action.key ];
const edits = nextState[ recordId ];
if ( ! edits ) {
continue;
diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js
index 3e5373eda6d6ab..167b8ac052fe45 100644
--- a/packages/core-data/src/resolvers.js
+++ b/packages/core-data/src/resolvers.js
@@ -278,7 +278,7 @@ export const getEntityRecords =
if ( ! query?._fields && ! query.context ) {
const key = entityConfig.key || DEFAULT_ENTITY_KEY;
const resolutionsArgs = records
- .filter( ( record ) => record[ key ] )
+ .filter( ( record ) => record?.[ key ] )
.map( ( record ) => [ kind, name, record[ key ] ] );
dispatch( {
diff --git a/packages/edit-post/src/components/init-pattern-modal/index.js b/packages/edit-post/src/components/init-pattern-modal/index.js
index c9bca96be05f44..1f181006cfec82 100644
--- a/packages/edit-post/src/components/init-pattern-modal/index.js
+++ b/packages/edit-post/src/components/init-pattern-modal/index.js
@@ -84,10 +84,7 @@ export default function InitPatternModal() {
/>
{ isEditMode && }
- { showVisualEditor && }
{ ! isReady ? : null }
{ isEditMode && }
{ isReady && (
@@ -213,6 +209,23 @@ export default function EditSiteEditor( { isLoading } ) {
! isEditingPage &&
}
>
+ { isEditMode && (
+
+ { ( { length } ) =>
+ length <= 1 && (
+
+ )
+ }
+
+ ) }
{ supportsGlobalStyles && }
diff --git a/packages/edit-site/src/components/editor/style.scss b/packages/edit-site/src/components/editor/style.scss
index f44b5a6f02ce20..b157057062c9d1 100644
--- a/packages/edit-site/src/components/editor/style.scss
+++ b/packages/edit-site/src/components/editor/style.scss
@@ -17,7 +17,3 @@
display: flex;
justify-content: center;
}
-
-.editor-header {
- padding-left: $header-height;
-}
diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js
index 4bbf29cced39ea..58de32d4b46878 100644
--- a/packages/edit-site/src/components/layout/index.js
+++ b/packages/edit-site/src/components/layout/index.js
@@ -16,9 +16,10 @@ import {
useReducedMotion,
useViewportMatch,
useResizeObserver,
+ usePrevious,
} from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
-import { useState } from '@wordpress/element';
+import { useState, useRef, useEffect } from '@wordpress/element';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import {
CommandMenu,
@@ -72,7 +73,7 @@ export default function Layout() {
useCommonCommands();
const isMobileViewport = useViewportMatch( 'medium', '<' );
-
+ const toggleRef = useRef();
const {
isDistractionFree,
hasFixedToolbar,
@@ -120,27 +121,6 @@ export default function Layout() {
triggerAnimationOnChange: canvasMode + '__' + routeKey,
} );
- // This determines which animation variant should apply to the header.
- // There is also a `isDistractionFreeHovering` state that gets priority
- // when hovering the `edit-site-layout__header-container` in distraction
- // free mode. It's set via framer and trickles down to all the children
- // so they can use this variant state too.
- //
- // TODO: The issue with this is we want to have the hover state stick when hovering
- // a popover opened via the header. We'll probably need to lift this state to
- // handle it ourselves. Also, focusWithin the header needs to be handled.
- let headerAnimationState;
-
- if ( canvasMode === 'view' ) {
- // We need 'view' to always take priority so 'isDistractionFree'
- // doesn't bleed over into the view (sidebar) state
- headerAnimationState = 'view';
- } else if ( isDistractionFree ) {
- headerAnimationState = 'isDistractionFree';
- } else {
- headerAnimationState = canvasMode; // edit, view, init
- }
-
// Sets the right context for the command palette
let commandContext = 'site-editor';
@@ -154,6 +134,14 @@ export default function Layout() {
const [ backgroundColor ] = useGlobalStyle( 'color.background' );
const [ gradientValue ] = useGlobalStyle( 'color.gradient' );
+ const previousCanvaMode = usePrevious( canvasMode );
+ useEffect( () => {
+ if ( previousCanvaMode === 'edit' ) {
+ toggleRef.current?.focus();
+ }
+ // Should not depend on the previous canvas mode value but the next.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [ canvasMode ] );
// Synchronizing the URL with the store value of canvasMode happens in an effect
// This condition ensures the component is only rendered after the synchronization happens
@@ -183,41 +171,6 @@ export default function Layout() {
}
) }
>
-
-
-
-
{ /*
The NavigableRegion must always be rendered and not use
@@ -246,6 +199,12 @@ export default function Layout() {
} }
className="edit-site-layout__sidebar"
>
+
{ areas.sidebar }
diff --git a/packages/edit-site/src/components/layout/style.scss b/packages/edit-site/src/components/layout/style.scss
index 0c5412b6d765bb..01c31de0d65d6c 100644
--- a/packages/edit-site/src/components/layout/style.scss
+++ b/packages/edit-site/src/components/layout/style.scss
@@ -11,37 +11,6 @@
}
}
-.edit-site-layout__hub {
- position: fixed;
- top: 0;
- left: 0;
- width: calc(100vw - #{$canvas-padding * 2});
- height: $header-height;
- z-index: z-index(".edit-site-layout__hub");
-
- @include break-medium {
- width: calc(#{$nav-sidebar-width} - #{$grid-unit-15});
- }
-
- .edit-site-layout.is-full-canvas & {
- padding-right: 0;
- border-radius: 0;
- width: $header-height;
- box-shadow: none;
- }
-}
-
-.edit-site-layout__header-container:has(+ .edit-site-layout__content > .edit-site-layout__mobile>.edit-site-page) {
- margin-bottom: $header-height;
- @include break-medium {
- margin-bottom: 0;
- }
-}
-
-.edit-site-layout__header-container {
- z-index: z-index(".edit-site-layout__header-container");
-}
-
.edit-site-layout__content {
height: 100%;
flex-grow: 1;
@@ -163,10 +132,22 @@
height: 100%;
}
+/* stylelint-disable -- Disable reason: View Transitions not supported properly by stylelint. */
+html.canvas-mode-edit-transition::view-transition-group(toggle) {
+ animation-delay: 255ms;
+}
+/* stylelint-enable */
+
+.edit-site-layout.is-full-canvas .edit-site-layout__sidebar-region .edit-site-layout__view-mode-toggle {
+ display: none;
+}
+
.edit-site-layout__view-mode-toggle.components-button {
+ /* stylelint-disable -- Disable reason: View Transitions not supported properly by stylelint. */
+ view-transition-name: toggle;
+ /* stylelint-enable */
position: relative;
color: $white;
- border-radius: 0;
height: $header-height;
width: $header-height;
overflow: hidden;
@@ -174,6 +155,8 @@
display: flex;
align-items: center;
justify-content: center;
+ background: $gray-900;
+ border-radius: 0;
&:hover,
&:active {
@@ -207,7 +190,6 @@
.edit-site-layout__view-mode-toggle-icon {
display: flex;
- border-radius: $radius-block-ui;
height: $grid-unit-80;
width: $grid-unit-80;
justify-content: center;
@@ -244,33 +226,6 @@
}
}
-.edit-site-layout.is-distraction-free {
- .edit-site-layout__header-container {
- height: $header-height;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- z-index: z-index(".edit-site-layout__header-container");
- width: 100%;
-
- // We need ! important because we override inline styles
- // set by the motion component.
- &:focus-within {
- opacity: 1 !important;
- div {
- transform: translateX(0) translateY(0) translateZ(0) !important;
- }
- }
- }
-
- .edit-site-site-hub {
- position: absolute;
- top: 0;
- z-index: z-index(".edit-site-layout__hub");
- }
-}
-
.edit-site-layout__area {
flex-grow: 1;
margin: 0;
diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js
index 223ede9ac1d988..47727bab047d46 100644
--- a/packages/edit-site/src/components/page-patterns/index.js
+++ b/packages/edit-site/src/components/page-patterns/index.js
@@ -86,15 +86,12 @@ const DEFAULT_VIEW = {
const SYNC_FILTERS = [
{
value: PATTERN_SYNC_TYPES.full,
- label: _x( 'Synced', 'Option that shows all synchronized patterns' ),
+ label: _x( 'Synced', 'pattern (singular)' ),
description: __( 'Patterns that are kept in sync across the site.' ),
},
{
value: PATTERN_SYNC_TYPES.unsynced,
- label: _x(
- 'Not synced',
- 'Option that shows all patterns that are not synchronized'
- ),
+ label: _x( 'Not synced', 'pattern (singular)' ),
description: __(
'Patterns that can be changed freely without affecting the site.'
),
@@ -298,13 +295,19 @@ export default function DataviewsPatterns() {
- { SYNC_FILTERS.find(
- ( { value } ) => value === item.syncStatus
- )?.label ||
- SYNC_FILTERS.find(
- ( { value } ) =>
- value === PATTERN_SYNC_TYPES.unsynced
- ).label }
+ {
+ (
+ SYNC_FILTERS.find(
+ ( { value } ) =>
+ value === item.syncStatus
+ ) ||
+ SYNC_FILTERS.find(
+ ( { value } ) =>
+ value ===
+ PATTERN_SYNC_TYPES.unsynced
+ )
+ ).label
+ }
);
},
diff --git a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js
index e6ebd5b1f8ee8b..0b5f93546875e5 100644
--- a/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js
+++ b/packages/edit-site/src/components/sidebar-dataviews/dataview-item.js
@@ -35,11 +35,15 @@ export default function DataViewItem( {
const iconToUse =
icon || VIEW_LAYOUTS.find( ( v ) => v.type === type ).icon;
+ let activeView = isCustom ? customViewId : slug;
+ if ( activeView === 'all' ) {
+ activeView = undefined;
+ }
const linkInfo = useLink( {
postType,
layout,
- activeView: isCustom ? customViewId : slug,
- isCustom: isCustom ? 'true' : 'false',
+ activeView,
+ isCustom: isCustom ? 'true' : undefined,
} );
return (
{
- const { canvasMode, dashboardLink, homeUrl, siteTitle } = useSelect(
- ( select ) => {
- const { getCanvasMode, getSettings } = unlock(
- select( editSiteStore )
- );
+const SiteHub = memo(
+ forwardRef( ( { isTransparent }, ref ) => {
+ const { dashboardLink, homeUrl, siteTitle } = useSelect( ( select ) => {
+ const { getSettings } = unlock( select( editSiteStore ) );
const {
getSite,
@@ -47,7 +35,6 @@ const SiteHub = memo( ( { isTransparent, className } ) => {
} = select( coreStore );
const _site = getSite();
return {
- canvasMode: getCanvasMode(),
dashboardLink:
getSettings().__experimentalDashboardLink || 'index.php',
homeUrl: getUnstableBase()?.home,
@@ -56,141 +43,63 @@ const SiteHub = memo( ( { isTransparent, className } ) => {
? filterURLForDisplay( _site?.url )
: _site?.title,
};
- },
- []
- );
- const { open: openCommandCenter } = useDispatch( commandsStore );
-
- const disableMotion = useReducedMotion();
- const { setCanvasMode } = unlock( useDispatch( editSiteStore ) );
- const { clearSelectedBlock } = useDispatch( blockEditorStore );
- const { setDeviceType } = useDispatch( editorStore );
- const isBackToDashboardButton = canvasMode === 'view';
- const siteIconButtonProps = isBackToDashboardButton
- ? {
- href: dashboardLink,
- label: __( 'Go to the Dashboard' ),
- }
- : {
- href: dashboardLink, // We need to keep the `href` here so the component doesn't remount as a `
+
+ );
+ } )
+);
export default SiteHub;
diff --git a/packages/edit-site/src/components/site-hub/style.scss b/packages/edit-site/src/components/site-hub/style.scss
index 072dcbeb94f26c..7fae845d4d2030 100644
--- a/packages/edit-site/src/components/site-hub/style.scss
+++ b/packages/edit-site/src/components/site-hub/style.scss
@@ -3,6 +3,7 @@
align-items: center;
justify-content: space-between;
gap: $grid-unit-10;
+ margin-right: $grid-unit-15;
}
.edit-site-site-hub__actions {
@@ -14,10 +15,6 @@
width: $header-height;
flex-shrink: 0;
- .edit-site-layout__view-mode-toggle-icon {
- background: $gray-900;
- }
-
&.has-transparent-background .edit-site-layout__view-mode-toggle-icon {
background: transparent;
}
diff --git a/packages/edit-site/src/components/site-icon/style.scss b/packages/edit-site/src/components/site-icon/style.scss
index d8b5e3f9b51dee..a461b43476fe52 100644
--- a/packages/edit-site/src/components/site-icon/style.scss
+++ b/packages/edit-site/src/components/site-icon/style.scss
@@ -1,8 +1,5 @@
.edit-site-site-icon__icon {
fill: currentColor;
- // Matches SiteIcon motion, smoothing out the transition.
- transition: padding 0.3s ease-out;
- @include reduce-motion("transition");
.edit-site-layout.is-full-canvas & {
// Make the WordPress icon not so big in full canvas.
@@ -13,7 +10,6 @@
.edit-site-site-icon__image {
width: 100%;
height: 100%;
- border-radius: $radius-block-ui * 2;
object-fit: cover;
background: #333;
diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js
index f7ef09f81ce7e7..3e8c637a493de8 100644
--- a/packages/edit-site/src/index.js
+++ b/packages/edit-site/src/index.js
@@ -104,3 +104,7 @@ export function reinitializeEditor() {
export { default as PluginTemplateSettingPanel } from './components/plugin-template-setting-panel';
export { store } from './store';
export * from './deprecated';
+
+// Temporary: While the posts dashboard is being iterated on
+// it's being built in the same package as the site editor.
+export { initializePostsDashboard } from './posts';
diff --git a/packages/edit-site/src/posts.js b/packages/edit-site/src/posts.js
new file mode 100644
index 00000000000000..ceee039806c9e7
--- /dev/null
+++ b/packages/edit-site/src/posts.js
@@ -0,0 +1,20 @@
+/**
+ * WordPress dependencies
+ */
+import { createRoot, StrictMode } from '@wordpress/element';
+
+/**
+ * Initializes the "Posts Dashboard"
+ * @param {string} id DOM element id.
+ */
+export function initializePostsDashboard( id ) {
+ if ( ! globalThis.IS_GUTENBERG_PLUGIN ) {
+ return;
+ }
+ const target = document.getElementById( id );
+ const root = createRoot( target );
+
+ root.render( Welcome To Posts );
+
+ return root;
+}
diff --git a/packages/edit-site/src/posts.scss b/packages/edit-site/src/posts.scss
new file mode 100644
index 00000000000000..777ee3fa3412d5
--- /dev/null
+++ b/packages/edit-site/src/posts.scss
@@ -0,0 +1,19 @@
+@include wordpress-admin-schemes();
+
+#wpadminbar,
+#adminmenumain {
+ display: none;
+}
+#wpcontent {
+ margin-left: 0;
+}
+body {
+ @include wp-admin-reset("#gutenberg-posts-dashboard");
+ @include reset;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ min-height: 100vh;
+}
diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js
index c40e1c5fc366af..bd56e30f10d11d 100644
--- a/packages/edit-site/src/store/private-actions.js
+++ b/packages/edit-site/src/store/private-actions.js
@@ -13,39 +13,67 @@ import { store as editorStore } from '@wordpress/editor';
export const setCanvasMode =
( mode ) =>
( { registry, dispatch } ) => {
- const isMediumOrBigger =
- window.matchMedia( '(min-width: 782px)' ).matches;
- registry.dispatch( blockEditorStore ).__unstableSetEditorMode( 'edit' );
- const isPublishSidebarOpened = registry
- .select( editorStore )
- .isPublishSidebarOpened();
- dispatch( {
- type: 'SET_CANVAS_MODE',
- mode,
- } );
- const isEditMode = mode === 'edit';
- if ( isPublishSidebarOpened && ! isEditMode ) {
- registry.dispatch( editorStore ).closePublishSidebar();
- }
+ const switchCanvasMode = () => {
+ registry.batch( () => {
+ const isMediumOrBigger =
+ window.matchMedia( '(min-width: 782px)' ).matches;
+ registry.dispatch( blockEditorStore ).clearSelectedBlock();
+ registry.dispatch( editorStore ).setDeviceType( 'Desktop' );
+ registry
+ .dispatch( blockEditorStore )
+ .__unstableSetEditorMode( 'edit' );
+ const isPublishSidebarOpened = registry
+ .select( editorStore )
+ .isPublishSidebarOpened();
+ dispatch( {
+ type: 'SET_CANVAS_MODE',
+ mode,
+ } );
+ const isEditMode = mode === 'edit';
+ if ( isPublishSidebarOpened && ! isEditMode ) {
+ registry.dispatch( editorStore ).closePublishSidebar();
+ }
+
+ // Check if the block list view should be open by default.
+ // If `distractionFree` mode is enabled, the block list view should not be open.
+ // This behavior is disabled for small viewports.
+ if (
+ isMediumOrBigger &&
+ isEditMode &&
+ registry
+ .select( preferencesStore )
+ .get( 'core', 'showListViewByDefault' ) &&
+ ! registry
+ .select( preferencesStore )
+ .get( 'core', 'distractionFree' )
+ ) {
+ registry
+ .dispatch( editorStore )
+ .setIsListViewOpened( true );
+ } else {
+ registry
+ .dispatch( editorStore )
+ .setIsListViewOpened( false );
+ }
+ registry.dispatch( editorStore ).setIsInserterOpened( false );
+ } );
+ };
- // Check if the block list view should be open by default.
- // If `distractionFree` mode is enabled, the block list view should not be open.
- // This behavior is disabled for small viewports.
- if (
- isMediumOrBigger &&
- isEditMode &&
- registry
- .select( preferencesStore )
- .get( 'core', 'showListViewByDefault' ) &&
- ! registry
- .select( preferencesStore )
- .get( 'core', 'distractionFree' )
- ) {
- registry.dispatch( editorStore ).setIsListViewOpened( true );
+ if ( ! document.startViewTransition ) {
+ switchCanvasMode();
} else {
- registry.dispatch( editorStore ).setIsListViewOpened( false );
+ document.documentElement.classList.add(
+ `canvas-mode-${ mode }-transition`
+ );
+ const transition = document.startViewTransition( () =>
+ switchCanvasMode()
+ );
+ transition.finished.finally( () => {
+ document.documentElement.classList.remove(
+ `canvas-mode-${ mode }-transition`
+ );
+ } );
}
- registry.dispatch( editorStore ).setIsInserterOpened( false );
};
/**
diff --git a/packages/editor/README.md b/packages/editor/README.md
index d4161f9bac6266..634b16a29971e7 100644
--- a/packages/editor/README.md
+++ b/packages/editor/README.md
@@ -390,7 +390,17 @@ _Returns_
### EntitiesSavedStates
-Undocumented declaration.
+Renders the component for managing saved states of entities.
+
+_Parameters_
+
+- _props_ `Object`: The component props.
+- _props.close_ `Function`: The function to close the dialog.
+- _props.renderDialog_ `Function`: The function to render the dialog.
+
+_Returns_
+
+- `JSX.Element`: The rendered component.
### ErrorBoundary
@@ -1191,7 +1201,7 @@ Undocumented declaration.
### PostPublishPanel
-Undocumented declaration.
+Renders a panel for publishing a post.
### PostSavedState
@@ -1270,23 +1280,58 @@ Undocumented declaration.
### PostSwitchToDraftButton
-Undocumented declaration.
+Renders a button component that allows the user to switch a post to draft status.
+
+_Returns_
+
+- `JSX.Element`: The rendered component.
### PostSyncStatus
-Undocumented declaration.
+Renders the sync status of a post.
+
+_Returns_
+
+- `JSX.Element|null`: The rendered sync status component.
### PostTaxonomies
-Undocumented declaration.
+Renders the taxonomies associated with a post.
+
+_Parameters_
+
+- _props_ `Object`: The component props.
+- _props.taxonomyWrapper_ `Function`: The wrapper function for each taxonomy component.
+
+_Returns_
+
+- `Array`: An array of JSX elements representing the visible taxonomies.
### PostTaxonomiesCheck
-Undocumented declaration.
+Renders the children components only if the current post type has taxonomies.
+
+_Parameters_
+
+- _props_ `Object`: The component props.
+- _props.children_ `Element`: The children components to render.
+
+_Returns_
+
+- `Component|null`: The rendered children components or null if the current post type has no taxonomies.
### PostTaxonomiesFlatTermSelector
-Undocumented declaration.
+Renders a flat term selector component.
+
+_Parameters_
+
+- _props_ `Object`: The component props.
+- _props.slug_ `string`: The slug of the taxonomy.
+
+_Returns_
+
+- `JSX.Element`: The rendered flat term selector component.
### PostTaxonomiesHierarchicalTermSelector
@@ -1303,7 +1348,17 @@ _Returns_
### PostTaxonomiesPanel
-Undocumented declaration.
+Renders a panel for a specific taxonomy.
+
+_Parameters_
+
+- _props_ `Object`: The component props.
+- _props.taxonomy_ `Object`: The taxonomy object.
+- _props.children_ `Element`: The child components.
+
+_Returns_
+
+- `Component`: The rendered taxonomy panel.
### PostTemplatePanel
@@ -1491,7 +1546,18 @@ _Type_
### TableOfContents
-Undocumented declaration.
+Renders a table of contents component.
+
+_Parameters_
+
+- _props_ `Object`: The component props.
+- _props.hasOutlineItemsDisabled_ `boolean`: Whether outline items are disabled.
+- _props.repositionDropdown_ `boolean`: Whether to reposition the dropdown.
+- _ref_ `Element.ref`: The component's ref.
+
+_Returns_
+
+- `JSX.Element`: The rendered table of contents component.
### TextEditorGlobalKeyboardShortcuts
diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js
index 1cbbece9618b9b..5d9840855348c9 100644
--- a/packages/editor/src/components/entities-saved-states/index.js
+++ b/packages/editor/src/components/entities-saved-states/index.js
@@ -26,6 +26,15 @@ function identity( values ) {
return values;
}
+/**
+ * Renders the component for managing saved states of entities.
+ *
+ * @param {Object} props The component props.
+ * @param {Function} props.close The function to close the dialog.
+ * @param {Function} props.renderDialog The function to render the dialog.
+ *
+ * @return {JSX.Element} The rendered component.
+ */
export default function EntitiesSavedStates( {
close,
renderDialog = undefined,
@@ -40,6 +49,23 @@ export default function EntitiesSavedStates( {
);
}
+/**
+ * Renders a panel for saving entities with dirty records.
+ *
+ * @param {Object} props The component props.
+ * @param {string} props.additionalPrompt Additional prompt to display.
+ * @param {Function} props.close Function to close the panel.
+ * @param {Function} props.onSave Function to call when saving entities.
+ * @param {boolean} props.saveEnabled Flag indicating if save is enabled.
+ * @param {string} props.saveLabel Label for the save button.
+ * @param {Function} props.renderDialog Function to render a custom dialog.
+ * @param {Array} props.dirtyEntityRecords Array of dirty entity records.
+ * @param {boolean} props.isDirty Flag indicating if there are dirty entities.
+ * @param {Function} props.setUnselectedEntities Function to set unselected entities.
+ * @param {Array} props.unselectedEntities Array of unselected entities.
+ *
+ * @return {JSX.Element} The rendered component.
+ */
export function EntitiesSavedStatesExtensible( {
additionalPrompt = undefined,
close,
diff --git a/packages/editor/src/components/post-publish-panel/index.js b/packages/editor/src/components/post-publish-panel/index.js
index 31e838575c0871..009d008c72c962 100644
--- a/packages/editor/src/components/post-publish-panel/index.js
+++ b/packages/editor/src/components/post-publish-panel/index.js
@@ -131,6 +131,9 @@ export class PostPublishPanel extends Component {
}
}
+/**
+ * Renders a panel for publishing a post.
+ */
export default compose( [
withSelect( ( select ) => {
const { getPostType } = select( coreStore );
diff --git a/packages/editor/src/components/post-switch-to-draft-button/index.js b/packages/editor/src/components/post-switch-to-draft-button/index.js
index 6aa17416004849..a743c7a2991ffb 100644
--- a/packages/editor/src/components/post-switch-to-draft-button/index.js
+++ b/packages/editor/src/components/post-switch-to-draft-button/index.js
@@ -8,14 +8,23 @@ import {
import { __ } from '@wordpress/i18n';
import { useDispatch, useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
+import deprecated from '@wordpress/deprecated';
/**
* Internal dependencies
*/
import { store as editorStore } from '../../store';
-// TODO: deprecate..
+/**
+ * Renders a button component that allows the user to switch a post to draft status.
+ *
+ * @return {JSX.Element} The rendered component.
+ */
export default function PostSwitchToDraftButton() {
+ deprecated( 'wp.editor.PostSwitchToDraftButton', {
+ since: '6.7',
+ version: '6.9',
+ } );
const [ showConfirmDialog, setShowConfirmDialog ] = useState( false );
const { editPost, savePost } = useDispatch( editorStore );
diff --git a/packages/editor/src/components/post-sync-status/index.js b/packages/editor/src/components/post-sync-status/index.js
index 0ed10cd2482a22..d3e2a1a5522e89 100644
--- a/packages/editor/src/components/post-sync-status/index.js
+++ b/packages/editor/src/components/post-sync-status/index.js
@@ -10,6 +10,11 @@ import { __, _x } from '@wordpress/i18n';
import PostPanelRow from '../post-panel-row';
import { store as editorStore } from '../../store';
+/**
+ * Renders the sync status of a post.
+ *
+ * @return {JSX.Element|null} The rendered sync status component.
+ */
export default function PostSyncStatus() {
const { syncStatus, postType } = useSelect( ( select ) => {
const { getEditedPostAttribute } = select( editorStore );
@@ -35,14 +40,8 @@ export default function PostSyncStatus() {
{ syncStatus === 'unsynced'
- ? _x(
- 'Not synced',
- 'Text that indicates that the pattern is not synchronized'
- )
- : _x(
- 'Synced',
- 'Text that indicates that the pattern is synchronized'
- ) }
+ ? _x( 'Not synced', 'pattern (singular)' )
+ : _x( 'Synced', 'pattern (singular)' ) }
);
diff --git a/packages/editor/src/components/post-taxonomies/check.js b/packages/editor/src/components/post-taxonomies/check.js
index c5621d724cd83b..401b1adad7cad4 100644
--- a/packages/editor/src/components/post-taxonomies/check.js
+++ b/packages/editor/src/components/post-taxonomies/check.js
@@ -9,6 +9,14 @@ import { store as coreStore } from '@wordpress/core-data';
*/
import { store as editorStore } from '../../store';
+/**
+ * Renders the children components only if the current post type has taxonomies.
+ *
+ * @param {Object} props The component props.
+ * @param {Element} props.children The children components to render.
+ *
+ * @return {Component|null} The rendered children components or null if the current post type has no taxonomies.
+ */
export default function PostTaxonomiesCheck( { children } ) {
const hasTaxonomies = useSelect( ( select ) => {
const postType = select( editorStore ).getCurrentPostType();
diff --git a/packages/editor/src/components/post-taxonomies/flat-term-selector.js b/packages/editor/src/components/post-taxonomies/flat-term-selector.js
index 109c6faf53b415..85331fc242a10f 100644
--- a/packages/editor/src/components/post-taxonomies/flat-term-selector.js
+++ b/packages/editor/src/components/post-taxonomies/flat-term-selector.js
@@ -26,9 +26,12 @@ import MostUsedTerms from './most-used-terms';
const EMPTY_ARRAY = [];
/**
- * Module constants
+ * How the max suggestions limit was chosen:
+ * - Matches the `per_page` range set by the REST API.
+ * - Can't use "unbound" query. The `FormTokenField` needs a fixed number.
+ * - Matches default for `FormTokenField`.
*/
-const MAX_TERMS_SUGGESTIONS = 20;
+const MAX_TERMS_SUGGESTIONS = 100;
const DEFAULT_QUERY = {
per_page: MAX_TERMS_SUGGESTIONS,
_fields: 'id,name',
@@ -49,6 +52,14 @@ const termNamesToIds = ( names, terms ) => {
.filter( ( id ) => id !== undefined );
};
+/**
+ * Renders a flat term selector component.
+ *
+ * @param {Object} props The component props.
+ * @param {string} props.slug The slug of the taxonomy.
+ *
+ * @return {JSX.Element} The rendered flat term selector component.
+ */
export function FlatTermSelector( { slug } ) {
const [ values, setValues ] = useState( [] );
const [ search, setSearch ] = useState( '' );
diff --git a/packages/editor/src/components/post-taxonomies/index.js b/packages/editor/src/components/post-taxonomies/index.js
index 30468e04091837..d96027a918c182 100644
--- a/packages/editor/src/components/post-taxonomies/index.js
+++ b/packages/editor/src/components/post-taxonomies/index.js
@@ -43,4 +43,12 @@ export function PostTaxonomies( { taxonomyWrapper = identity } ) {
} );
}
+/**
+ * Renders the taxonomies associated with a post.
+ *
+ * @param {Object} props The component props.
+ * @param {Function} props.taxonomyWrapper The wrapper function for each taxonomy component.
+ *
+ * @return {Array} An array of JSX elements representing the visible taxonomies.
+ */
export default PostTaxonomies;
diff --git a/packages/editor/src/components/post-taxonomies/panel.js b/packages/editor/src/components/post-taxonomies/panel.js
index a2c2d175246403..760626f984db36 100644
--- a/packages/editor/src/components/post-taxonomies/panel.js
+++ b/packages/editor/src/components/post-taxonomies/panel.js
@@ -63,4 +63,13 @@ function PostTaxonomies() {
);
}
+/**
+ * Renders a panel for a specific taxonomy.
+ *
+ * @param {Object} props The component props.
+ * @param {Object} props.taxonomy The taxonomy object.
+ * @param {Element} props.children The child components.
+ *
+ * @return {Component} The rendered taxonomy panel.
+ */
export default PostTaxonomies;
diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js
index 081b1cdfa0f1b2..aaf25621d3324b 100644
--- a/packages/editor/src/components/provider/index.js
+++ b/packages/editor/src/components/provider/index.js
@@ -33,6 +33,7 @@ import StartTemplateOptions from '../start-template-options';
import EditorKeyboardShortcuts from '../global-keyboard-shortcuts';
import PatternRenameModal from '../pattern-rename-modal';
import PatternDuplicateModal from '../pattern-duplicate-modal';
+import TemplatePartMenuItems from '../template-part-menu-items';
const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis );
const { PatternsMenuItems } = unlock( editPatternsPrivateApis );
@@ -160,8 +161,21 @@ export const ExperimentalEditorProvider = withRegistryProvider(
BlockEditorProviderComponent = ExperimentalBlockEditorProvider,
__unstableTemplate: template,
} ) => {
- const mode = useSelect(
- ( select ) => select( editorStore ).getRenderingMode(),
+ const { editorSettings, selection, isReady, mode } = useSelect(
+ ( select ) => {
+ const {
+ getEditorSettings,
+ getEditorSelection,
+ getRenderingMode,
+ __unstableIsEditorReady,
+ } = select( editorStore );
+ return {
+ editorSettings: getEditorSettings(),
+ isReady: __unstableIsEditorReady(),
+ mode: getRenderingMode(),
+ selection: getEditorSelection(),
+ };
+ },
[]
);
const shouldRenderTemplate = !! template && mode !== 'post-only';
@@ -187,21 +201,6 @@ export const ExperimentalEditorProvider = withRegistryProvider(
rootLevelPost.type,
rootLevelPost.slug,
] );
- const { editorSettings, selection, isReady } = useSelect(
- ( select ) => {
- const {
- getEditorSettings,
- getEditorSelection,
- __unstableIsEditorReady,
- } = select( editorStore );
- return {
- editorSettings: getEditorSettings(),
- isReady: __unstableIsEditorReady(),
- selection: getEditorSelection(),
- };
- },
- []
- );
const { id, type } = rootLevelPost;
const blockEditorSettings = useBlockEditorSettings(
editorSettings,
@@ -301,6 +300,7 @@ export const ExperimentalEditorProvider = withRegistryProvider(
{ ! settings.__unstableIsPreviewMode && (
<>
+
{ mode === 'template-locked' && (
diff --git a/packages/editor/src/components/table-of-contents/index.js b/packages/editor/src/components/table-of-contents/index.js
index c63fdbfd83e25c..74a535240833d6 100644
--- a/packages/editor/src/components/table-of-contents/index.js
+++ b/packages/editor/src/components/table-of-contents/index.js
@@ -52,4 +52,14 @@ function TableOfContents(
);
}
+/**
+ * Renders a table of contents component.
+ *
+ * @param {Object} props The component props.
+ * @param {boolean} props.hasOutlineItemsDisabled Whether outline items are disabled.
+ * @param {boolean} props.repositionDropdown Whether to reposition the dropdown.
+ * @param {Element.ref} ref The component's ref.
+ *
+ * @return {JSX.Element} The rendered table of contents component.
+ */
export default forwardRef( TableOfContents );
diff --git a/packages/edit-site/src/components/template-part-converter/convert-to-regular.js b/packages/editor/src/components/template-part-menu-items/convert-to-regular.js
similarity index 100%
rename from packages/edit-site/src/components/template-part-converter/convert-to-regular.js
rename to packages/editor/src/components/template-part-menu-items/convert-to-regular.js
diff --git a/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js b/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js
similarity index 82%
rename from packages/edit-site/src/components/template-part-converter/convert-to-template-part.js
rename to packages/editor/src/components/template-part-menu-items/convert-to-template-part.js
index 5dc68e07c85f3f..4ae15d1dd178c0 100644
--- a/packages/edit-site/src/components/template-part-converter/convert-to-template-part.js
+++ b/packages/editor/src/components/template-part-menu-items/convert-to-template-part.js
@@ -9,15 +9,11 @@ import { __ } from '@wordpress/i18n';
import { useState } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';
import { symbolFilled } from '@wordpress/icons';
-import { privateApis as editorPrivateApis } from '@wordpress/editor';
/**
* Internal dependencies
*/
-import { unlock } from '../../lock-unlock';
-import { store as editSiteStore } from '../../store';
-
-const { CreateTemplatePartModal } = unlock( editorPrivateApis );
+import CreateTemplatePartModal from '../create-template-part-modal';
export default function ConvertToTemplatePart( { clientIds, blocks } ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
@@ -25,10 +21,11 @@ export default function ConvertToTemplatePart( { clientIds, blocks } ) {
const { createSuccessNotice } = useDispatch( noticesStore );
const { canCreate } = useSelect( ( select ) => {
- const { supportsTemplatePartsMode } =
- select( editSiteStore ).getSettings();
return {
- canCreate: ! supportsTemplatePartsMode,
+ canCreate:
+ select( blockEditorStore ).canInsertBlockType(
+ 'core/template-part'
+ ),
};
}, [] );
diff --git a/packages/edit-site/src/components/template-part-converter/index.js b/packages/editor/src/components/template-part-menu-items/index.js
similarity index 96%
rename from packages/edit-site/src/components/template-part-converter/index.js
rename to packages/editor/src/components/template-part-menu-items/index.js
index de47eb6ae26f4d..0e126644d49938 100644
--- a/packages/edit-site/src/components/template-part-converter/index.js
+++ b/packages/editor/src/components/template-part-menu-items/index.js
@@ -13,7 +13,7 @@ import {
import ConvertToRegularBlocks from './convert-to-regular';
import ConvertToTemplatePart from './convert-to-template-part';
-export default function TemplatePartConverter() {
+export default function TemplatePartMenuItems() {
return (
{ ( { selectedClientIds, onClose } ) => (
diff --git a/packages/editor/src/components/template-part-menu-items/index.native.js b/packages/editor/src/components/template-part-menu-items/index.native.js
new file mode 100644
index 00000000000000..af843714462387
--- /dev/null
+++ b/packages/editor/src/components/template-part-menu-items/index.native.js
@@ -0,0 +1,3 @@
+export default function TemplatePartMenuItems() {
+ return null;
+}
diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js
index 16ae706bf18f1d..be9863e2dc57d1 100644
--- a/packages/patterns/src/components/create-pattern-modal.js
+++ b/packages/patterns/src/components/create-pattern-modal.js
@@ -129,10 +129,7 @@ export function CreatePatternModalContents( {
categoryMap={ categoryMap }
/>
## Unreleased
+
+## 1.120.0
- [*] Prevent deleting content when backspacing in the first Paragraph block [#62069]
- [internal] Adds new bridge functionality for updating content [#61796]
diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock
index 86405ca4eacfc0..9407c238841aa1 100644
--- a/packages/react-native-editor/ios/Podfile.lock
+++ b/packages/react-native-editor/ios/Podfile.lock
@@ -13,7 +13,7 @@ PODS:
- ReactCommon/turbomodule/core (= 0.73.3)
- fmt (6.2.1)
- glog (0.3.5)
- - Gutenberg (1.119.1):
+ - Gutenberg (1.120.0):
- React-Core (= 0.73.3)
- React-CoreModules (= 0.73.3)
- React-RCTImage (= 0.73.3)
@@ -1109,7 +1109,7 @@ PODS:
- React-Core
- RNSVG (14.0.0):
- React-Core
- - RNTAztecView (1.119.1):
+ - RNTAztecView (1.120.0):
- React-Core
- WordPress-Aztec-iOS (= 1.19.11)
- SDWebImage (5.11.1):
@@ -1343,7 +1343,7 @@ SPEC CHECKSUMS:
FBReactNativeSpec: 73b3972e2bd20b3235ff2014f06a3d3af675ed29
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
- Gutenberg: be04e16bda9f59460f938d9c3f1248778df0368f
+ Gutenberg: 0d15c3a0b8aace231a954709a536580d7905c867
hermes-engine: 5420539d016f368cd27e008f65f777abd6098c56
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: 60305b2e989864154bd9be3d772730f08fc6a59c
@@ -1402,7 +1402,7 @@ SPEC CHECKSUMS:
RNReanimated: 6936b41d8afb97175e7c0ab40425b53103f71046
RNScreens: 2b73f5eb2ac5d94fbd61fa4be0bfebd345716825
RNSVG: 255767813dac22db1ec2062c8b7e7b856d4e5ae6
- RNTAztecView: 268a6489f223c3a91afa2ba5ee7bef82df900c69
+ RNTAztecView: 3a1df2dd827082d22a4dd06b6031e2fefd2885c6
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json
index c131e7caf1f776..6e6a52a0817e53 100644
--- a/packages/react-native-editor/package.json
+++ b/packages/react-native-editor/package.json
@@ -1,6 +1,6 @@
{
"name": "@wordpress/react-native-editor",
- "version": "1.119.1",
+ "version": "1.120.0",
"description": "Mobile WordPress gutenberg editor.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
diff --git a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js
index cf8b3d96c8d7e1..1a7293b6a55cb0 100644
--- a/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js
+++ b/packages/reusable-blocks/src/components/reusable-blocks-menu-items/reusable-block-convert-button.js
@@ -184,10 +184,7 @@ export default function ReusableBlockConvertButton( {
placeholder={ __( 'My pattern' ) }
/>
next_tag( array( 'class_name' => 'wp-block-query-pagination-previous' ) );
$this->assertSame( 'query-pagination-previous', $p->get_attribute( 'data-wp-key' ) );
$this->assertSame( 'core/query::actions.navigate', $p->get_attribute( 'data-wp-on--click' ) );
- $this->assertSame( 'core/query::actions.prefetch', $p->get_attribute( 'data-wp-on--mouseenter' ) );
+ $this->assertSame( 'core/query::actions.prefetch', $p->get_attribute( 'data-wp-on-async--mouseenter' ) );
$this->assertSame( 'core/query::callbacks.prefetch', $p->get_attribute( 'data-wp-watch' ) );
$p->next_tag( array( 'class_name' => 'wp-block-query-pagination-next' ) );
$this->assertSame( 'query-pagination-next', $p->get_attribute( 'data-wp-key' ) );
$this->assertSame( 'core/query::actions.navigate', $p->get_attribute( 'data-wp-on--click' ) );
- $this->assertSame( 'core/query::actions.prefetch', $p->get_attribute( 'data-wp-on--mouseenter' ) );
+ $this->assertSame( 'core/query::actions.prefetch', $p->get_attribute( 'data-wp-on-async--mouseenter' ) );
$this->assertSame( 'core/query::callbacks.prefetch', $p->get_attribute( 'data-wp-watch' ) );
$router_config = wp_interactivity_config( 'core/router' );
diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js
index be5c7594b46dad..cb25afc416eaaf 100644
--- a/test/e2e/specs/editor/various/block-bindings.spec.js
+++ b/test/e2e/specs/editor/various/block-bindings.spec.js
@@ -30,12 +30,6 @@ test.describe( 'Block bindings', () => {
await requestUtils.deactivatePlugin( 'gutenberg-test-block-bindings' );
} );
- test.use( {
- BlockBindingsUtils: async ( { editor, page, pageUtils }, use ) => {
- await use( new BlockBindingsUtils( { editor, page, pageUtils } ) );
- },
- } );
-
test.describe( 'Template context', () => {
test.beforeEach( async ( { admin, editor } ) => {
await admin.visitSiteEditor( {
@@ -1170,7 +1164,6 @@ test.describe( 'Block bindings', () => {
test.describe( 'Paragraph', () => {
test( 'should show the value of the custom field when exists', async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/paragraph',
@@ -1195,19 +1188,14 @@ test.describe( 'Block bindings', () => {
);
// Check the frontend shows the value of the custom field.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
+ const previewPage = await editor.openPreviewPage();
await expect(
- page.locator( '#paragraph-binding' )
- ).toBeVisible();
- await expect( page.locator( '#paragraph-binding' ) ).toHaveText(
- 'Value of the text_custom_field'
- );
+ previewPage.locator( '#paragraph-binding' )
+ ).toHaveText( 'Value of the text_custom_field' );
} );
test( "should show the value of the key when custom field doesn't exist", async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/paragraph',
@@ -1237,16 +1225,14 @@ test.describe( 'Block bindings', () => {
);
// Check the frontend doesn't show the content.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
- await expect( page.locator( '#paragraph-binding' ) ).toHaveText(
- 'fallback value'
- );
+ const previewPage = await editor.openPreviewPage();
+ await expect(
+ previewPage.locator( '#paragraph-binding' )
+ ).toHaveText( 'fallback value' );
} );
test( 'should not show the value of a protected meta field', async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/paragraph',
@@ -1268,16 +1254,14 @@ test.describe( 'Block bindings', () => {
} );
await expect( paragraphBlock ).toHaveText( '_protected_field' );
// Check the frontend doesn't show the content.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
- await expect( page.locator( '#paragraph-binding' ) ).toHaveText(
- 'fallback value'
- );
+ const previewPage = await editor.openPreviewPage();
+ await expect(
+ previewPage.locator( '#paragraph-binding' )
+ ).toHaveText( 'fallback value' );
} );
test( 'should not show the value of a meta field with `show_in_rest` false', async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/paragraph',
@@ -1301,11 +1285,10 @@ test.describe( 'Block bindings', () => {
'show_in_rest_false_field'
);
// Check the frontend doesn't show the content.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
- await expect( page.locator( '#paragraph-binding' ) ).toHaveText(
- 'fallback value'
- );
+ const previewPage = await editor.openPreviewPage();
+ await expect(
+ previewPage.locator( '#paragraph-binding' )
+ ).toHaveText( 'fallback value' );
} );
test( 'should add empty paragraph block when pressing enter', async ( {
@@ -1412,7 +1395,6 @@ test.describe( 'Block bindings', () => {
test.describe( 'Heading', () => {
test( 'should show the value of the custom field', async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/heading',
@@ -1437,14 +1419,10 @@ test.describe( 'Block bindings', () => {
);
// Check the frontend shows the value of the custom field.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
+ const previewPage = await editor.openPreviewPage();
await expect(
- page.locator( '#heading-binding' )
- ).toBeVisible();
- await expect( page.locator( '#heading-binding' ) ).toHaveText(
- 'Value of the text_custom_field'
- );
+ previewPage.locator( '#heading-binding' )
+ ).toHaveText( 'Value of the text_custom_field' );
} );
test( 'should add empty paragraph block when pressing enter', async ( {
@@ -1498,7 +1476,6 @@ test.describe( 'Block bindings', () => {
test.describe( 'Button', () => {
test( 'should show the value of the custom field when text is bound', async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/buttons',
@@ -1533,10 +1510,10 @@ test.describe( 'Block bindings', () => {
);
// Check the frontend shows the value of the custom field.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
- const buttonDom = page.locator( '#button-text-binding a' );
- await expect( buttonDom ).toBeVisible();
+ const previewPage = await editor.openPreviewPage();
+ const buttonDom = previewPage.locator(
+ '#button-text-binding a'
+ );
await expect( buttonDom ).toHaveText(
'Value of the text_custom_field'
);
@@ -1548,7 +1525,6 @@ test.describe( 'Block bindings', () => {
test( 'should use the value of the custom field when url is bound', async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/buttons',
@@ -1573,10 +1549,10 @@ test.describe( 'Block bindings', () => {
} );
// Check the frontend shows the original value of the custom field.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
- const buttonDom = page.locator( '#button-url-binding a' );
- await expect( buttonDom ).toBeVisible();
+ const previewPage = await editor.openPreviewPage();
+ const buttonDom = previewPage.locator(
+ '#button-url-binding a'
+ );
await expect( buttonDom ).toHaveText( 'button default text' );
await expect( buttonDom ).toHaveAttribute(
'href',
@@ -1586,7 +1562,6 @@ test.describe( 'Block bindings', () => {
test( 'should use the values of the custom fields when text and url are bound', async ( {
editor,
- page,
} ) => {
await editor.insertBlock( {
name: 'core/buttons',
@@ -1615,10 +1590,10 @@ test.describe( 'Block bindings', () => {
} );
// Check the frontend uses the values of the custom fields.
- const postId = await editor.publishPost();
- await page.goto( `/?p=${ postId }` );
- const buttonDom = page.locator( '#button-multiple-bindings a' );
- await expect( buttonDom ).toBeVisible();
+ const previewPage = await editor.openPreviewPage();
+ const buttonDom = previewPage.locator(
+ '#button-multiple-bindings a'
+ );
await expect( buttonDom ).toHaveText(
'Value of the text_custom_field'
);
@@ -1701,8 +1676,6 @@ test.describe( 'Block bindings', () => {
} );
test( 'should show the value of the custom field when url is bound', async ( {
editor,
- page,
- BlockBindingsUtils,
} ) => {
await editor.insertBlock( {
name: 'core/image',
@@ -1732,10 +1705,10 @@ test.describe( 'Block bindings', () => {
);
// Check the frontend uses the value of the custom field.
- const postId = await BlockBindingsUtils.updatePost();
- await page.goto( `/?p=${ postId }` );
- const imageDom = page.locator( '#image-url-binding img' );
- await expect( imageDom ).toBeVisible();
+ const previewPage = await editor.openPreviewPage();
+ const imageDom = previewPage.locator(
+ '#image-url-binding img'
+ );
await expect( imageDom ).toHaveAttribute(
'src',
imageCustomFieldSrc
@@ -1753,7 +1726,6 @@ test.describe( 'Block bindings', () => {
test( 'should show value of the custom field in the alt textarea when alt is bound', async ( {
editor,
page,
- BlockBindingsUtils,
} ) => {
await editor.insertBlock( {
name: 'core/image',
@@ -1793,10 +1765,10 @@ test.describe( 'Block bindings', () => {
expect( altValue ).toBe( 'Value of the text_custom_field' );
// Check the frontend uses the value of the custom field.
- const postId = await BlockBindingsUtils.updatePost();
- await page.goto( `/?p=${ postId }` );
- const imageDom = page.locator( '#image-alt-binding img' );
- await expect( imageDom ).toBeVisible();
+ const previewPage = await editor.openPreviewPage();
+ const imageDom = previewPage.locator(
+ '#image-alt-binding img'
+ );
await expect( imageDom ).toHaveAttribute(
'src',
imagePlaceholderSrc
@@ -1814,7 +1786,6 @@ test.describe( 'Block bindings', () => {
test( 'should show value of the custom field in the title input when title is bound', async ( {
editor,
page,
- BlockBindingsUtils,
} ) => {
await editor.insertBlock( {
name: 'core/image',
@@ -1864,10 +1835,10 @@ test.describe( 'Block bindings', () => {
expect( titleValue ).toBe( 'Value of the text_custom_field' );
// Check the frontend uses the value of the custom field.
- const postId = await BlockBindingsUtils.updatePost();
- await page.goto( `/?p=${ postId }` );
- const imageDom = page.locator( '#image-title-binding img' );
- await expect( imageDom ).toBeVisible();
+ const previewPage = await editor.openPreviewPage();
+ const imageDom = previewPage.locator(
+ '#image-title-binding img'
+ );
await expect( imageDom ).toHaveAttribute(
'src',
imagePlaceholderSrc
@@ -1885,7 +1856,6 @@ test.describe( 'Block bindings', () => {
test( 'Multiple bindings should show the value of the custom fields', async ( {
editor,
page,
- BlockBindingsUtils,
} ) => {
await editor.insertBlock( {
name: 'core/image',
@@ -1946,10 +1916,10 @@ test.describe( 'Block bindings', () => {
expect( titleValue ).toBe( 'default title value' );
// Check the frontend uses the values of the custom fields.
- const postId = await BlockBindingsUtils.updatePost();
- await page.goto( `/?p=${ postId }` );
- const imageDom = page.locator( '#image-multiple-bindings img' );
- await expect( imageDom ).toBeVisible();
+ const previewPage = await editor.openPreviewPage();
+ const imageDom = previewPage.locator(
+ '#image-multiple-bindings img'
+ );
await expect( imageDom ).toHaveAttribute(
'src',
imageCustomFieldSrc
@@ -2168,24 +2138,3 @@ test.describe( 'Block bindings', () => {
} );
} );
} );
-
-class BlockBindingsUtils {
- constructor( { page } ) {
- this.page = page;
- }
-
- // Helper to update the post.
- async updatePost() {
- await this.page
- .getByRole( 'region', { name: 'Editor top bar' } )
- .getByRole( 'button', { name: 'Save' } )
- .click();
- await this.page
- .getByRole( 'button', { name: 'Dismiss this notice' } )
- .filter( { hasText: 'updated' } )
- .waitFor();
- const postId = new URL( this.page.url() ).searchParams.get( 'post' );
-
- return typeof postId === 'string' ? parseInt( postId, 10 ) : null;
- }
-}
diff --git a/test/e2e/specs/site-editor/template-part.spec.js b/test/e2e/specs/site-editor/template-part.spec.js
index f1e317e171736c..d88273574bc4b0 100644
--- a/test/e2e/specs/site-editor/template-part.spec.js
+++ b/test/e2e/specs/site-editor/template-part.spec.js
@@ -57,8 +57,7 @@ test.describe( 'Template Part', () => {
page,
} ) => {
// Visit the index.
- await admin.visitSiteEditor();
- await editor.canvas.locator( 'body' ).click();
+ await admin.visitSiteEditor( { canvas: 'edit' } );
const headerTemplateParts = editor.canvas.locator(
'[data-type="core/template-part"]'
);
@@ -84,8 +83,7 @@ test.describe( 'Template Part', () => {
} ) => {
const paragraphText = 'Test 2';
- await admin.visitSiteEditor();
- await editor.canvas.locator( 'body' ).click();
+ await admin.visitSiteEditor( { canvas: 'edit' } );
// Add a block and select it.
await editor.insertBlock( {
name: 'core/paragraph',
@@ -124,8 +122,7 @@ test.describe( 'Template Part', () => {
const paragraphText1 = 'Test 3';
const paragraphText2 = 'Test 4';
- await admin.visitSiteEditor();
- await editor.canvas.locator( 'body' ).click();
+ await admin.visitSiteEditor( { canvas: 'edit' } );
// Add a block and select it.
await editor.insertBlock( {
name: 'core/paragraph',
@@ -199,8 +196,7 @@ test.describe( 'Template Part', () => {
} );
// Visit the index.
- await admin.visitSiteEditor();
- await editor.canvas.locator( 'body' ).click();
+ await admin.visitSiteEditor( { canvas: 'edit' } );
// Check that the header contains the paragraph added earlier.
const paragraph = editor.canvas.locator(
`p >> text="${ paragraphText }"`
@@ -303,8 +299,7 @@ test.describe( 'Template Part', () => {
editor,
page,
} ) => {
- await admin.visitSiteEditor();
- await editor.canvas.locator( 'body' ).click();
+ await admin.visitSiteEditor( { canvas: 'edit' } );
// Add a block and select it.
await editor.insertBlock( {
@@ -344,8 +339,7 @@ test.describe( 'Template Part', () => {
editor,
page,
} ) => {
- await admin.visitSiteEditor();
- await editor.canvas.locator( 'body' ).click();
+ await admin.visitSiteEditor( { canvas: 'edit' } );
// Select existing header template part.
await editor.selectBlocks(
diff --git a/test/e2e/specs/site-editor/template-revert.spec.js b/test/e2e/specs/site-editor/template-revert.spec.js
index 5c19ed5a39e004..1f8d9ea73d7fd9 100644
--- a/test/e2e/specs/site-editor/template-revert.spec.js
+++ b/test/e2e/specs/site-editor/template-revert.spec.js
@@ -20,10 +20,9 @@ test.describe( 'Template Revert', () => {
await requestUtils.deleteAllTemplates( 'wp_template_part' );
await requestUtils.activateTheme( 'twentytwentyone' );
} );
- test.beforeEach( async ( { admin, requestUtils, editor } ) => {
+ test.beforeEach( async ( { admin, requestUtils } ) => {
await requestUtils.deleteAllTemplates( 'wp_template' );
- await admin.visitSiteEditor();
- await editor.canvas.locator( 'body' ).click();
+ await admin.visitSiteEditor( { canvas: 'edit' } );
} );
test( 'should delete the template after saving the reverted template', async ( {