From 36fcdb2d83a5b4d4278e01e5716191bf11ed8a53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Thu, 7 Apr 2022 12:16:07 +0200 Subject: [PATCH 01/31] Add `__experimentalEnableQuoteBlockV2` flag (#40089) --- lib/experimental/block-editor-settings-mobile.php | 11 ++++++++--- lib/experimental/blocks.php | 11 ++++++++++- ...class-wp-rest-block-editor-settings-controller.php | 6 ++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/experimental/block-editor-settings-mobile.php b/lib/experimental/block-editor-settings-mobile.php index dc3c5ed49a4cf5..bd05e24e4acc9f 100644 --- a/lib/experimental/block-editor-settings-mobile.php +++ b/lib/experimental/block-editor-settings-mobile.php @@ -20,10 +20,15 @@ function gutenberg_get_block_editor_settings_mobile( $settings ) { defined( 'REST_REQUEST' ) && REST_REQUEST && isset( $_GET['context'] ) && - 'mobile' === $_GET['context'] && - WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() + 'mobile' === $_GET['context'] ) { - $settings['__experimentalStyles'] = gutenberg_get_global_styles(); + if ( WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { + $settings['__experimentalStyles'] = gutenberg_get_global_styles(); + } + + // To be set to true when the web makes quote v2 (inner blocks) the default. + // See https://github.com/WordPress/gutenberg/pull/25892. + $settings['__experimentalEnableQuoteBlockV2'] = gutenberg_is_quote_v2_enabled(); } return $settings; diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index 521b2ff1fa9466..2ab396d83070c0 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -152,6 +152,15 @@ function ( $response ) { } add_action( 'rest_api_init', 'gutenberg_rest_comment_set_children_as_embeddable' ); +/** + * Returns whether the quote v2 is enabled by the user. + * + * @return boolean + */ +function gutenberg_is_quote_v2_enabled() { + return get_option( 'gutenberg-experiments' ) && array_key_exists( 'gutenberg-quote-v2', get_option( 'gutenberg-experiments' ) ); +} + /** * Sets a global JS variable used to trigger the availability of the experimental blocks. */ @@ -160,7 +169,7 @@ function gutenberg_enable_experimental_blocks() { wp_add_inline_script( 'wp-block-library', 'window.__experimentalEnableListBlockV2 = true', 'before' ); } - if ( get_option( 'gutenberg-experiments' ) && array_key_exists( 'gutenberg-quote-v2', get_option( 'gutenberg-experiments' ) ) ) { + if ( gutenberg_is_quote_v2_enabled() ) { wp_add_inline_script( 'wp-block-library', 'window.__experimentalEnableQuoteBlockV2 = true', 'before' ); } } diff --git a/lib/experimental/class-wp-rest-block-editor-settings-controller.php b/lib/experimental/class-wp-rest-block-editor-settings-controller.php index e0a42ac2a35edd..0d1aa47fb2b02b 100644 --- a/lib/experimental/class-wp-rest-block-editor-settings-controller.php +++ b/lib/experimental/class-wp-rest-block-editor-settings-controller.php @@ -156,6 +156,12 @@ public function get_item_schema() { 'context' => array( 'mobile' ), ), + '__experimentalEnableQuoteBlockV2' => array( + 'description' => __( 'Whether the V2 of the quote block that uses inner blocks should be enabled.', 'gutenberg' ), + 'type' => 'boolean', + 'context' => array( 'mobile' ), + ), + 'alignWide' => array( 'description' => __( 'Enable/Disable Wide/Full Alignments.', 'gutenberg' ), 'type' => 'boolean', From 99f820e04ec3dc9a239ed6a5fda42cb24f89275a Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Thu, 7 Apr 2022 13:27:37 +0300 Subject: [PATCH 02/31] Allow multiple view scripts per block (#36176) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Handle arrays in viewScript * Split modal script in navigation's viewScript * Enqueue modal script when needed * Add function in the 6.0-compat folder * Indentation fix * Add webpack changes to build also view files prefixed with view * fix the schema * PHPCS fix (= alignment) * remove unnecessary line * remove in_footer arg * handle script translations * Add a wp_enqueue_block_script function * rename function * Update lib/blocks.php Co-authored-by: Greg Ziółkowski * allow script translations by defining a textdomain * reverse i18n logic * typo * revert navigation block changes Co-authored-by: Grzegorz Ziolkowski --- lib/blocks.php | 100 ++++++++++++++++++++++++++++ lib/compat/wordpress-6.0/blocks.php | 51 ++++++++++++++ packages/block-library/README.md | 2 +- schemas/json/block.json | 14 +++- tools/webpack/blocks.js | 24 ++++--- 5 files changed, 178 insertions(+), 13 deletions(-) diff --git a/lib/blocks.php b/lib/blocks.php index 320d50bf509500..8e2b25715f1099 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -593,3 +593,103 @@ function gutenberg_multiple_block_styles( $metadata ) { return $metadata; } add_filter( 'block_type_metadata', 'gutenberg_multiple_block_styles' ); + +if ( ! function_exists( 'wp_enqueue_block_view_script' ) ) { + /** + * Enqueue a frontend script for a specific block. + * + * Scripts enqueued using this function will only get printed + * when the block gets rendered on the frontend. + * + * @param string $block_name The block-name, including namespace. + * @param array $args An array of arguments [handle,src,deps,ver,media]. + * + * @return void + */ + function wp_enqueue_block_view_script( $block_name, $args ) { + $args = wp_parse_args( + $args, + array( + 'handle' => '', + 'src' => '', + 'deps' => array(), + 'ver' => false, + 'in_footer' => false, + + // Additional arg to allow translations for the script's textdomain. + 'textdomain' => '', + ) + ); + + /** + * Callback function to register and enqueue scripts. + * + * @param string $content When the callback is used for the render_block filter, + * the content needs to be returned so the function parameter + * is to ensure the content exists. + * @return string Block content. + */ + $callback = static function( $content, $block ) use ( $args, $block_name ) { + + // Sanity check. + if ( empty( $block['blockName'] ) || $block_name !== $block['blockName'] ) { + return $content; + } + + // Register the stylesheet. + if ( ! empty( $args['src'] ) ) { + wp_register_script( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['in_footer'] ); + } + + // Enqueue the stylesheet. + wp_enqueue_script( $args['handle'] ); + + // If a textdomain is defined, use it to set the script translations. + if ( ! empty( $args['textdomain'] ) && in_array( 'wp-i18n', $args['deps'], true ) ) { + wp_set_script_translations( $args['handle'], $args['textdomain'] ); + } + + return $content; + }; + + /* + * The filter's callback here is an anonymous function because + * using a named function in this case is not possible. + * + * The function cannot be unhooked, however, users are still able + * to dequeue the script registered/enqueued by the callback + * which is why in this case, using an anonymous function + * was deemed acceptable. + */ + add_filter( 'render_block', $callback, 10, 2 ); + } +} + +/** + * Allow multiple view scripts per block. + * + * Filters the metadata provided for registering a block type. + * + * @param array $metadata Metadata for registering a block type. + * + * @return array + */ +function gutenberg_block_type_metadata_multiple_view_scripts( $metadata ) { + + // Early return if viewScript is empty, or not an array. + if ( ! isset( $metadata['viewScript'] ) || ! is_array( $metadata['viewScript'] ) ) { + return $metadata; + } + + // Register all viewScript items. + foreach ( $metadata['viewScript'] as $view_script ) { + $item_metadata = $metadata; + $item_metadata['viewScript'] = $view_script; + gutenberg_block_type_metadata_view_script( array(), $item_metadata ); + } + + // Proceed with the default behavior. + $metadata['viewScript'] = $metadata['viewScript'][0]; + return $metadata; +} +add_filter( 'block_type_metadata', 'gutenberg_block_type_metadata_multiple_view_scripts' ); diff --git a/lib/compat/wordpress-6.0/blocks.php b/lib/compat/wordpress-6.0/blocks.php index 40108be343ec99..842dd50b7f6107 100644 --- a/lib/compat/wordpress-6.0/blocks.php +++ b/lib/compat/wordpress-6.0/blocks.php @@ -122,3 +122,54 @@ function gutenberg_build_query_vars_from_query_block( $block, $page ) { } return $query; } + +/** + * Registers view scripts for core blocks if handling is missing in WordPress core. + * + * This is a temporary solution until the Gutenberg plugin sets + * the required WordPress version to 6.0. + * + * @param array $settings Array of determined settings for registering a block type. + * @param array $metadata Metadata provided for registering a block type. + * + * @return array Array of settings for registering a block type. + */ +function gutenberg_block_type_metadata_view_script( $settings, $metadata ) { + if ( + ! isset( $metadata['viewScript'] ) || + ! empty( $settings['view_script'] ) || + ! isset( $metadata['file'] ) || + strpos( $metadata['file'], gutenberg_dir_path() ) !== 0 + ) { + return $settings; + } + + $view_script_path = realpath( dirname( $metadata['file'] ) . '/' . remove_block_asset_path_prefix( $metadata['viewScript'] ) ); + + if ( file_exists( $view_script_path ) ) { + $view_script_id = str_replace( array( '.min.js', '.js' ), '', basename( remove_block_asset_path_prefix( $metadata['viewScript'] ) ) ); + $view_script_handle = str_replace( 'core/', 'wp-block-', $metadata['name'] ) . '-' . $view_script_id; + wp_deregister_script( $view_script_handle ); + + // Replace suffix and extension with `.asset.php` to find the generated dependencies file. + $view_asset_file = substr( $view_script_path, 0, -( strlen( '.js' ) ) ) . '.asset.php'; + $view_asset = file_exists( $view_asset_file ) ? require( $view_asset_file ) : null; + $view_script_dependencies = isset( $view_asset['dependencies'] ) ? $view_asset['dependencies'] : array(); + $view_script_version = isset( $view_asset['version'] ) ? $view_asset['version'] : false; + $result = wp_register_script( + $view_script_handle, + gutenberg_url( str_replace( gutenberg_dir_path(), '', $view_script_path ) ), + $view_script_dependencies, + $view_script_version + ); + if ( $result ) { + $settings['view_script'] = $view_script_handle; + + if ( ! empty( $metadata['textdomain'] ) && in_array( 'wp-i18n', $view_script_dependencies, true ) ) { + wp_set_script_translations( $view_script_handle, $metadata['textdomain'] ); + } + } + } + return $settings; +} +add_filter( 'block_type_metadata_settings', 'gutenberg_block_type_metadata_view_script', 10, 2 ); diff --git a/packages/block-library/README.md b/packages/block-library/README.md index 92cf2c6a351843..45f923af9f8e3e 100644 --- a/packages/block-library/README.md +++ b/packages/block-library/README.md @@ -14,7 +14,7 @@ _This package assumes that your code will run in an **ES2015+** environment. If ## Building JavaScript for the browser -If a `view.js` file is present in the block's directory, this file will be built along other assets, making it available to load from the browser. +If a `view.js` file (or a file prefixed with `view`, e.g. `view-example.js`) is present in the block's directory, this file will be built along other assets, making it available to load from the browser. This enables us to, for instance, load this file when the block is present on the page in two ways: diff --git a/schemas/json/block.json b/schemas/json/block.json index 99217d43c376fc..43e24b28f91ee0 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -421,8 +421,18 @@ "description": "Block type frontend and editor script definition. It will be enqueued both in the editor and when viewing the content on the front of the site." }, "viewScript": { - "type": "string", - "description": "Block type frontend script definition. It will be enqueued only when viewing the content on the front of the site." + "description": "Block type frontend script definition. It will be enqueued only when viewing the content on the front of the site.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] }, "editorStyle": { "description": "Block type editor style definition. It will only be enqueued in the context of the editor.", diff --git a/tools/webpack/blocks.js b/tools/webpack/blocks.js index 11d4635af96324..c9a5af0b631ce1 100644 --- a/tools/webpack/blocks.js +++ b/tools/webpack/blocks.js @@ -17,10 +17,11 @@ const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extrac const { baseConfig, plugins, stylesTransform } = require( './shared' ); /* - * Matches a block's name in paths in the form - * build-module//view.js + * Matches a block's filepaths in the form build-module/.js */ -const blockNameRegex = new RegExp( /(?<=build-module\/).*(?=(\/view))/g ); +const blockViewRegex = new RegExp( + /build-module\/(?.*\/view.*).js$/ +); /** * We need to automatically rename some functions when they are called inside block files, @@ -31,14 +32,14 @@ const prefixFunctions = [ 'build_query_vars_from_query_block' ]; const createEntrypoints = () => { /* - * Returns an array of paths to view.js files within the `@wordpress/block-library` package. - * These paths can be matched by the regex `blockNameRegex` in order to extract - * the block's name. + * Returns an array of paths to block view files within the `@wordpress/block-library` package. + * These paths can be matched by the regex `blockViewRegex` in order to extract + * the block's filename. * * Returns an empty array if no files were found. */ const blockViewScriptPaths = fastGlob.sync( - './packages/block-library/build-module/**/view.js' + './packages/block-library/build-module/**/view*.js' ); /* @@ -46,11 +47,14 @@ const createEntrypoints = () => { * each block's view.js file. */ return blockViewScriptPaths.reduce( ( entries, scriptPath ) => { - const [ blockName ] = scriptPath.match( blockNameRegex ); + const result = scriptPath.match( blockViewRegex ); + if ( ! result?.groups?.filename ) { + return entries; + } return { ...entries, - [ 'blocks/' + blockName ]: scriptPath, + [ result.groups.filename ]: scriptPath, }; }, {} ); }; @@ -61,7 +65,7 @@ module.exports = { entry: createEntrypoints(), output: { devtoolNamespace: 'wp', - filename: './build/block-library/[name]/view.min.js', + filename: './build/block-library/blocks/[name].min.js', path: join( __dirname, '..', '..' ), }, plugins: [ From 30c17975066da6623afdfa67fb3bc98e3143fb4f Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 7 Apr 2022 11:56:37 +0100 Subject: [PATCH 03/31] Theme Export: Remove default theme.json properties on export (#39848) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André <583546+oandregal@users.noreply.github.com> --- .../wordpress-6.0/block-template-utils.php | 5 ++++- .../class-wp-theme-json-gutenberg.php | 1 - ...class-wp-theme-json-resolver-gutenberg.php | 8 ++++++- phpunit/class-wp-theme-json-test.php | 21 +++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/compat/wordpress-6.0/block-template-utils.php b/lib/compat/wordpress-6.0/block-template-utils.php index bef14ca345d4cb..60a6cee7f4139a 100644 --- a/lib/compat/wordpress-6.0/block-template-utils.php +++ b/lib/compat/wordpress-6.0/block-template-utils.php @@ -97,7 +97,8 @@ function gutenberg_generate_block_templates_export_file() { } // Load theme.json into the zip file. - $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data(); + $tree = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data( array(), array( 'with_supports' => false ) ); + // Merge with user data. $tree->merge( WP_Theme_JSON_Resolver_Gutenberg::get_user_data() ); $theme_json_raw = $tree->get_data(); @@ -114,8 +115,10 @@ function gutenberg_generate_block_templates_export_file() { // Convert to a string. $theme_json_encoded = wp_json_encode( $theme_json_raw, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); + // Replace 4 spaces with a tab. $theme_json_tabbed = preg_replace( '~(?:^|\G)\h{4}~m', "\t", $theme_json_encoded ); + // Add the theme.json file to the zip. $zip->addFromString( 'theme.json', diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php index 752d4a6151efe3..c71029a535d2f8 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php @@ -469,5 +469,4 @@ public function get_data() { return $flattened_theme_json; } - } diff --git a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php index 7714b6256d136b..2e743d0c1f9ccc 100644 --- a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php @@ -25,12 +25,14 @@ class WP_Theme_JSON_Resolver_Gutenberg extends WP_Theme_JSON_Resolver_6_0 { * the theme.json takes precedence. * * @param array $deprecated Deprecated argument. + * @param array $settings Contains a key called with_supports to determine whether to include theme supports in the data. * @return WP_Theme_JSON_Gutenberg Entity that holds theme data. */ - public static function get_theme_data( $deprecated = array() ) { + public static function get_theme_data( $deprecated = array(), $settings = array( 'with_supports' => true ) ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __METHOD__, '5.9' ); } + if ( null === static::$theme ) { $theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) ); $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); @@ -51,6 +53,10 @@ public static function get_theme_data( $deprecated = array() ) { } } + if ( ! $settings['with_supports'] ) { + return static::$theme; + } + /* * We want the presets and settings declared in theme.json * to override the ones declared via theme supports. diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index b138564896cb7c..a2367d13f48975 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2451,4 +2451,25 @@ function test_export_data_deals_with_empty_theme_data() { $this->assertEqualSetsWithIndex( $expected, $actual ); } + function test_export_data_deals_with_empty_data() { + $theme_v2 = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + ), + 'theme' + ); + $actual_v2 = $theme_v2->get_data(); + $expected_v2 = array( 'version' => 2 ); + $this->assertEqualSetsWithIndex( $expected_v2, $actual_v2 ); + + $theme_v1 = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 1, + ), + 'theme' + ); + $actual_v1 = $theme_v1->get_data(); + $expected_v1 = array( 'version' => 2 ); + $this->assertEqualSetsWithIndex( $expected_v1, $actual_v1 ); + } } From a618eaf35a417cd4cb7492b324cd6b8e5b86b7d9 Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Thu, 7 Apr 2022 12:59:07 +0100 Subject: [PATCH 04/31] Theme Export: Restore appearanceTools when exporting a theme (#39840) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Theme Export: Restore appearanceTools when exporting a theme * update unit test * Rename TO_OPT_IN to APPEARANCE_TOOLS_OPT_INS * Unset appearanceTools we don't want to pass this to the block-editor store. * Rename do_opt_out_of_settings to use_appearance_tools_setting * Add unit test for desired behaviour * Set appearanceTools if all opt-ins are true * Add unit test for desired behaviour at the block level * Make it work at the block level * Make the code more compact * Document & inline all code into get_data * Update PHPDoc * change inline comments into a comment block to keep the linter happy * fix linting issues Co-authored-by: André <583546+oandregal@users.noreply.github.com> --- .../class-wp-theme-json-gutenberg.php | 133 ++++++++++++++++-- phpunit/class-wp-theme-json-test.php | 31 ++++ 2 files changed, 156 insertions(+), 8 deletions(-) diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php index c71029a535d2f8..929c69c24c72cf 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php @@ -133,6 +133,18 @@ class WP_Theme_JSON_Gutenberg extends WP_Theme_JSON_5_9 { 'title', ); + const APPEARANCE_TOOLS_OPT_INS = array( + array( 'border', 'color' ), + array( 'border', 'radius' ), + array( 'border', 'style' ), + array( 'border', 'width' ), + array( 'color', 'link' ), + array( 'spacing', 'blockGap' ), + array( 'spacing', 'margin' ), + array( 'spacing', 'padding' ), + array( 'typography', 'lineHeight' ), + ); + /** * The valid properties under the settings key. * @@ -426,18 +438,48 @@ protected static function get_metadata_boolean( $data, $path, $default = false ) } /** - * Returns a valid theme.json for a theme. - * Essentially, it flattens the preset data. + * Returns a valid theme.json as provided by a theme. + * + * Unlike get_raw_data() this returns the presets flattened, + * as provided by a theme. This also uses appearanceTools + * instead of their opt-ins if all of them are true. * * @return array */ public function get_data() { - $flattened_theme_json = $this->theme_json; - $nodes = static::get_setting_nodes( $this->theme_json ); + $output = $this->theme_json; + $nodes = static::get_setting_nodes( $output ); + + /** + * Flatten the theme & custom origins into a single one. + * + * For example, the following: + * + * { + * "settings": { + * "color": { + * "palette": { + * "theme": [ {} ], + * "custom": [ {} ] + * } + * } + * } + * } + * + * will be converted to: + * + * { + * "settings": { + * "color": { + * "palette": [ {} ] + * } + * } + * } + */ foreach ( $nodes as $node ) { foreach ( static::PRESETS_METADATA as $preset_metadata ) { $path = array_merge( $node['path'], $preset_metadata['path'] ); - $preset = _wp_array_get( $flattened_theme_json, $path, null ); + $preset = _wp_array_get( $output, $path, null ); if ( null === $preset ) { continue; } @@ -461,12 +503,87 @@ public function get_data() { foreach ( $items as $slug => $value ) { $flattened_preset[] = array_merge( array( 'slug' => $slug ), $value ); } - _wp_array_set( $flattened_theme_json, $path, $flattened_preset ); + _wp_array_set( $output, $path, $flattened_preset ); } } - wp_recursive_ksort( $flattened_theme_json ); + // If all of the static::APPEARANCE_TOOLS_OPT_INS are true, + // this code unsets them and sets 'appearanceTools' instead. + foreach ( $nodes as $node ) { + $all_opt_ins_are_set = true; + foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { + $full_path = array_merge( $node['path'], $opt_in_path ); + // Use "unset prop" as a marker instead of "null" because + // "null" can be a valid value for some props (e.g. blockGap). + $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); + if ( 'unset prop' === $opt_in_value ) { + $all_opt_ins_are_set = false; + break; + } + } + + if ( $all_opt_ins_are_set ) { + _wp_array_set( $output, array_merge( $node['path'], array( 'appearanceTools' ) ), true ); + foreach ( static::APPEARANCE_TOOLS_OPT_INS as $opt_in_path ) { + $full_path = array_merge( $node['path'], $opt_in_path ); + // Use "unset prop" as a marker instead of "null" because + // "null" can be a valid value for some props (e.g. blockGap). + $opt_in_value = _wp_array_get( $output, $full_path, 'unset prop' ); + if ( true !== $opt_in_value ) { + continue; + } + + // The following could be improved to be path independent. + // At the moment it relies on a couple of assumptions: + // + // - all opt-ins having a path of size 2. + // - there's two sources of settings: the top-level and the block-level. + if ( + ( 1 === count( $node['path'] ) ) && + ( 'settings' === $node['path'][0] ) + ) { + // Top-level settings. + unset( $output['settings'][ $opt_in_path[0] ][ $opt_in_path[1] ] ); + if ( empty( $output['settings'][ $opt_in_path[0] ] ) ) { + unset( $output['settings'][ $opt_in_path[0] ] ); + } + } elseif ( + ( 3 === count( $node['path'] ) ) && + ( 'settings' === $node['path'][0] ) && + ( 'blocks' === $node['path'][1] ) + ) { + // Block-level settings. + $block_name = $node['path'][2]; + unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ][ $opt_in_path[1] ] ); + if ( empty( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ) ) { + unset( $output['settings']['blocks'][ $block_name ][ $opt_in_path[0] ] ); + } + } + } + } + } + + wp_recursive_ksort( $output ); + + return $output; + } + + /** + * Enables some settings. + * + * @since 5.9.0 + * + * @param array $context The context to which the settings belong. + */ + protected static function do_opt_in_into_settings( &$context ) { + foreach ( static::APPEARANCE_TOOLS_OPT_INS as $path ) { + // Use "unset prop" as a marker instead of "null" because + // "null" can be a valid value for some props (e.g. blockGap). + if ( 'unset prop' === _wp_array_get( $context, $path, 'unset prop' ) ) { + _wp_array_set( $context, $path, true ); + } + } - return $flattened_theme_json; + unset( $context['appearanceTools'] ); } } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index a2367d13f48975..89240870399dc9 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -2472,4 +2472,35 @@ function test_export_data_deals_with_empty_data() { $expected_v1 = array( 'version' => 2 ); $this->assertEqualSetsWithIndex( $expected_v1, $actual_v1 ); } + + function test_export_data_sets_appearance_tools() { + $theme = new WP_Theme_JSON_Gutenberg( + array( + 'version' => 2, + 'settings' => array( + 'appearanceTools' => true, + 'blocks' => array( + 'core/paragraph' => array( + 'appearanceTools' => true, + ), + ), + ), + ) + ); + + $actual = $theme->get_data(); + $expected = array( + 'version' => 2, + 'settings' => array( + 'appearanceTools' => true, + 'blocks' => array( + 'core/paragraph' => array( + 'appearanceTools' => true, + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } } From 508037f7a7a25f376c629718c4374500b96b2a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Thu, 7 Apr 2022 14:42:26 +0200 Subject: [PATCH 05/31] Consolidate `WP_Theme_JSON_Resolver` changes in both `lib/compat/wordpress-6.0` and `lib/experimental` (#40140) --- .../class-wp-theme-json-resolver-6-0.php | 19 ++++++++++++------- ...class-wp-theme-json-resolver-gutenberg.php | 4 ++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php index fb1cf53bda6aef..2ed23f15cc0b6d 100644 --- a/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php +++ b/lib/compat/wordpress-6.0/class-wp-theme-json-resolver-6-0.php @@ -61,12 +61,14 @@ public static function get_core_data() { * the theme.json takes precedence. * * @param array $deprecated Deprecated argument. + * @param array $settings Contains a key called with_supports to determine whether to include theme supports in the data. * @return WP_Theme_JSON_Gutenberg Entity that holds theme data. */ - public static function get_theme_data( $deprecated = array() ) { + public static function get_theme_data( $deprecated = array(), $settings = array( 'with_supports' => true ) ) { if ( ! empty( $deprecated ) ) { _deprecated_argument( __METHOD__, '5.9' ); } + if ( null === static::$theme ) { $theme_json_data = static::read_json_file( static::get_file_path_from_theme( 'theme.json' ) ); $theme_json_data = static::translate( $theme_json_data, wp_get_theme()->get( 'TextDomain' ) ); @@ -85,12 +87,16 @@ public static function get_theme_data( $deprecated = array() ) { } } + if ( ! $settings['with_supports'] ) { + return static::$theme; + } + /* - * We want the presets and settings declared in theme.json - * to override the ones declared via theme supports. - * So we take theme supports, transform it to theme.json shape - * and merge the static::$theme upon that. - */ + * We want the presets and settings declared in theme.json + * to override the ones declared via theme supports. + * So we take theme supports, transform it to theme.json shape + * and merge the static::$theme upon that. + */ $theme_support_data = WP_Theme_JSON_Gutenberg::get_from_editor_settings( get_default_block_editor_settings() ); if ( ! static::theme_has_support() ) { if ( ! isset( $theme_support_data['settings']['color'] ) ) { @@ -125,7 +131,6 @@ public static function get_theme_data( $deprecated = array() ) { return $with_theme_supports; } - /** * Returns the style variations defined by the theme. * diff --git a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php index 2e743d0c1f9ccc..960ea659e8d2ee 100644 --- a/lib/experimental/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/experimental/class-wp-theme-json-resolver-gutenberg.php @@ -88,10 +88,14 @@ public static function get_theme_data( $deprecated = array(), $settings = array( $default_gradients = true; } $theme_support_data['settings']['color']['defaultGradients'] = $default_gradients; + + // Classic themes without a theme.json don't support global duotone. + $theme_support_data['settings']['color']['defaultDuotone'] = false; } $with_theme_supports = new WP_Theme_JSON_Gutenberg( $theme_support_data ); $with_theme_supports->merge( static::$theme ); return $with_theme_supports; } + } From e0de22238efe72bca50e0d19ebe0823cf62fec90 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 7 Apr 2022 17:09:19 +0400 Subject: [PATCH 06/31] Block Editor: Stabilize 'generateAnchors' setting (#40143) * Block Editor: Stabilize 'generateAnchors' setting * Remove dupe --- packages/block-editor/README.md | 2 +- packages/block-editor/src/store/defaults.js | 5 +++-- packages/block-library/src/heading/edit.js | 2 +- .../src/components/provider/use-block-editor-settings.js | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 74cf98a864abd9..852a755ca9e866 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -599,11 +599,11 @@ _Properties_ - _bodyPlaceholder_ `string`: Empty post placeholder - _titlePlaceholder_ `string`: Empty title placeholder - _codeEditingEnabled_ `boolean`: Whether or not the user can switch to the code editor +- _generateAnchors_ `boolean`: Enable/Disable auto anchor generation for Heading blocks - _\_\_experimentalCanUserUseUnfilteredHTML_ `boolean`: Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. - _\_\_experimentalBlockDirectory_ `boolean`: Whether the user has enabled the Block Directory - _\_\_experimentalBlockPatterns_ `Array`: Array of objects representing the block patterns - _\_\_experimentalBlockPatternCategories_ `Array`: Array of objects representing the block pattern categories -- _\_\_experimentalGenerateAnchors_ `boolean`: Enable/Disable auto anchor generation for Heading blocks - _\_\_experimentalCanLockBlocks_ `boolean`: Whether the user can manage Block Lock state - _\_\_unstableGalleryWithImageBlocks_ `boolean`: Whether the user has enabled the refactored gallery block which uses InnerBlocks diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 8edee1dd1adee7..33dc4ef022ee15 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -24,11 +24,11 @@ export const PREFERENCES_DEFAULTS = { * @property {string} bodyPlaceholder Empty post placeholder * @property {string} titlePlaceholder Empty title placeholder * @property {boolean} codeEditingEnabled Whether or not the user can switch to the code editor + * @property {boolean} generateAnchors Enable/Disable auto anchor generation for Heading blocks * @property {boolean} __experimentalCanUserUseUnfilteredHTML Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. * @property {boolean} __experimentalBlockDirectory Whether the user has enabled the Block Directory * @property {Array} __experimentalBlockPatterns Array of objects representing the block patterns * @property {Array} __experimentalBlockPatternCategories Array of objects representing the block pattern categories - * @property {boolean} __experimentalGenerateAnchors Enable/Disable auto anchor generation for Heading blocks * @property {boolean} __experimentalCanLockBlocks Whether the user can manage Block Lock state * @property {boolean} __unstableGalleryWithImageBlocks Whether the user has enabled the refactored gallery block which uses InnerBlocks */ @@ -158,9 +158,10 @@ export const SETTINGS_DEFAULTS = { __experimentalBlockPatterns: [], __experimentalBlockPatternCategories: [], __experimentalSpotlightEntityBlocks: [], - __experimentalGenerateAnchors: false, __experimentalCanLockBlocks: true, __unstableGalleryWithImageBlocks: false, + + generateAnchors: false, // gradients setting is not used anymore now defaults are passed from theme.json on the server and core has its own defaults. // The setting is only kept for backward compatibility purposes. gradients: [ diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 10000dbb141cd0..f4b9cb46198c30 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -45,7 +45,7 @@ function HeadingEdit( { const settings = select( blockEditorStore ).getSettings(); return { - canGenerateAnchors: !! settings.__experimentalGenerateAnchors, + canGenerateAnchors: !! settings.generateAnchors, }; }, [] ); diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 65f0577f9fd161..5ebb485174ea2d 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -105,7 +105,6 @@ function useBlockEditorSettings( settings, hasTemplate ) { '__experimentalFeatures', '__experimentalPreferredStyleVariations', '__experimentalSetIsInserterOpened', - '__experimentalGenerateAnchors', '__experimentalCanLockBlocks', '__unstableGalleryWithImageBlocks', 'alignWide', @@ -122,6 +121,7 @@ function useBlockEditorSettings( settings, hasTemplate ) { 'focusMode', 'fontSizes', 'gradients', + 'generateAnchors', 'hasFixedToolbar', 'hasReducedUI', 'imageDefaultSize', From ba8b6ef2acb0d3c9a0cfc7155c1b8c8651a930c9 Mon Sep 17 00:00:00 2001 From: Luis Felipe Zaguini <26530524+zaguiini@users.noreply.github.com> Date: Thu, 7 Apr 2022 10:53:35 -0300 Subject: [PATCH 07/31] Do not trigger warning on already enqueued font families (#40060) --- lib/experimental/class-wp-webfonts.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/experimental/class-wp-webfonts.php b/lib/experimental/class-wp-webfonts.php index d4a73c77cc2d2d..d7c1badd088222 100644 --- a/lib/experimental/class-wp-webfonts.php +++ b/lib/experimental/class-wp-webfonts.php @@ -152,15 +152,7 @@ public function enqueue_webfont( $font_family_name ) { $slug = $this->get_font_slug( $font_family_name ); if ( isset( $this->enqueued_webfonts[ $slug ] ) ) { - trigger_error( - sprintf( - /* translators: %s unique slug to identify the font family of the webfont */ - __( 'The "%s" font family is already enqueued.', 'gutenberg' ), - $slug - ) - ); - - return false; + return true; } if ( ! isset( $this->registered_webfonts[ $slug ] ) ) { From 10032aa189b543ad418fc43eddadae9f8f9d486d Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill Date: Thu, 7 Apr 2022 08:57:56 -0500 Subject: [PATCH 08/31] Try to optimize getClientIdsOfDescendants using memoization. (#40112) --- packages/block-editor/src/store/selectors.js | 25 +++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 95dc770c70b31f..1e51cd770493c7 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -225,18 +225,21 @@ export const __unstableGetClientIdsTree = createSelector( * * @return {Array} ids of descendants. */ -export const getClientIdsOfDescendants = ( state, clientIds ) => { - const collectedIds = []; - for ( const givenId of clientIds ) { - for ( const descendantId of getBlockOrder( state, givenId ) ) { - collectedIds.push( - descendantId, - ...getClientIdsOfDescendants( state, [ descendantId ] ) - ); +export const getClientIdsOfDescendants = createSelector( + ( state, clientIds ) => { + const collectedIds = []; + for ( const givenId of clientIds ) { + for ( const descendantId of getBlockOrder( state, givenId ) ) { + collectedIds.push( + descendantId, + ...getClientIdsOfDescendants( state, [ descendantId ] ) + ); + } } - } - return collectedIds; -}; + return collectedIds; + }, + ( state ) => [ state.blocks.order ] +); /** * Returns an array containing the clientIds of the top-level blocks and From 4ef32a1f42f994531005091f72428d6f24388a97 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Thu, 7 Apr 2022 22:03:54 +0800 Subject: [PATCH 09/31] Fix flaky issue reporter not splitting `...` separator (#40129) * Fix ... separator not being correctly splited * Add comment and fix dot --- .github/report-flaky-tests/index.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/report-flaky-tests/index.js b/.github/report-flaky-tests/index.js index b1223e0ca6a8e2..d0a79648e55a76 100644 --- a/.github/report-flaky-tests/index.js +++ b/.github/report-flaky-tests/index.js @@ -92,9 +92,27 @@ const metaData = { TEST_RESULTS_LIST.open.length, body.indexOf( TEST_RESULTS_LIST.close ) ) + /** + * Split the text from: + * ``` + * Test result 1 + * ... + * Test result 2 + * Test result 3 + * ``` + * + * into: + * ``` + * [ + * ' Test result 1 ', + * ' Test result 2 ', + * ' Test result 3 ', + * ] + * ``` + */ .split( new RegExp( - `(?<=${ TEST_RESULT.close })\n(?=${ TEST_RESULT.open })` + `(?<=${ TEST_RESULT.close })\n(?:\.\.\.\n)?(?=${ TEST_RESULT.open })` ) ); // GitHub issues has character limits on issue's body, From 90849f9522985a4b64a4dd05ce72f715013bfdb3 Mon Sep 17 00:00:00 2001 From: Zebulan Stanphill Date: Thu, 7 Apr 2022 09:16:28 -0500 Subject: [PATCH 10/31] Add select-parent button to block toolbar "More options" menu. (#40105) * Add select-parent button to block toolbar "More options" menu. * Update packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js Co-authored-by: Alex Stine Co-authored-by: Alex Stine --- .../block-settings-dropdown.js | 66 +++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js index 95accb96d837ac..2d31b49d2ed2d6 100644 --- a/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js +++ b/packages/block-editor/src/components/block-settings-menu/block-settings-dropdown.js @@ -6,12 +6,17 @@ import { castArray, flow, noop } from 'lodash'; /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { getBlockType, serialize } from '@wordpress/blocks'; import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; import { moreVertical } from '@wordpress/icons'; -import { Children, cloneElement, useCallback } from '@wordpress/element'; -import { serialize } from '@wordpress/blocks'; +import { + Children, + cloneElement, + useCallback, + useRef, +} from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; import { useCopyToClipboard } from '@wordpress/compose'; @@ -19,12 +24,14 @@ import { useCopyToClipboard } from '@wordpress/compose'; * Internal dependencies */ import BlockActions from '../block-actions'; +import BlockIcon from '../block-icon'; import BlockModeToggle from './block-mode-toggle'; import BlockHTMLConvertButton from './block-html-convert-button'; import __unstableBlockSettingsMenuFirstItem from './block-settings-menu-first-item'; import BlockSettingsMenuControls from '../block-settings-menu-controls'; import { store as blockEditorStore } from '../../store'; import useBlockDisplayTitle from '../block-title/use-block-display-title'; +import { useShowMoversGestures } from '../block-toolbar/utils'; const POPOVER_PROPS = { className: 'block-editor-block-settings-menu__popover', @@ -47,7 +54,10 @@ export function BlockSettingsDropdown( { const count = blockClientIds.length; const firstBlockClientId = blockClientIds[ 0 ]; const { + firstParentClientId, + hasReducedUI, onlyBlock, + parentBlockType, previousBlockClientId, nextBlockClientId, selectedBlockClientIds, @@ -55,12 +65,23 @@ export function BlockSettingsDropdown( { ( select ) => { const { getBlockCount, + getBlockName, + getBlockParents, getPreviousBlockClientId, getNextBlockClientId, getSelectedBlockClientIds, + getSettings, } = select( blockEditorStore ); + + const parents = getBlockParents( firstBlockClientId ); + const _firstParentClientId = parents[ parents.length - 1 ]; + const parentBlockName = getBlockName( _firstParentClientId ); + return { + firstParentClientId: _firstParentClientId, + hasReducedUI: getSettings().hasReducedUI, onlyBlock: 1 === getBlockCount(), + parentBlockType: getBlockType( parentBlockName ), previousBlockClientId: getPreviousBlockClientId( firstBlockClientId ), @@ -87,6 +108,10 @@ export function BlockSettingsDropdown( { }; }, [] ); + const { selectBlock, toggleBlockHighlight } = useDispatch( + blockEditorStore + ); + const updateSelectionAfterDuplicate = useCallback( __experimentalSelectBlock ? async ( clientIdsPromise ) => { @@ -135,6 +160,19 @@ export function BlockSettingsDropdown( { ); const removeBlockLabel = count === 1 ? label : __( 'Remove blocks' ); + // Allows highlighting the parent block outline when focusing or hovering + // the parent block selector within the child. + const selectParentButtonRef = useRef(); + const { gestures: showParentOutlineGestures } = useShowMoversGestures( { + ref: selectParentButtonRef, + onChange( isFocused ) { + if ( isFocused && hasReducedUI ) { + return; + } + toggleBlockHighlight( firstParentClientId, isFocused ); + }, + } ); + return ( + { firstParentClientId !== undefined && ( + + } + onClick={ () => + selectBlock( firstParentClientId ) + } + > + { sprintf( + /* translators: %s: Name of the block's parent. */ + __( 'Select parent block (%s)' ), + parentBlockType.title + ) } + + ) } { count === 1 && ( Date: Thu, 7 Apr 2022 15:20:43 +0100 Subject: [PATCH 11/31] Add a unit test for wp_recursive_ksort (#40149) * Add a unit test for recursive ksort * fix assignment --- phpunit/functions-test.php | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 phpunit/functions-test.php diff --git a/phpunit/functions-test.php b/phpunit/functions-test.php new file mode 100644 index 00000000000000..87dcf0c7e77eef --- /dev/null +++ b/phpunit/functions-test.php @@ -0,0 +1,91 @@ + 1, + 'settings' => array( + 'typography' => array( + 'fontFamilies' => array( + 'fontFamily' => 'DM Sans, sans-serif', + 'slug' => 'dm-sans', + 'name' => 'DM Sans', + ), + ), + 'color' => array( + 'palette' => array( + array( + 'slug' => 'foreground', + 'color' => '#242321', + 'name' => 'Foreground', + ), + array( + 'slug' => 'background', + 'color' => '#FCFBF8', + 'name' => 'Background', + ), + array( + 'slug' => 'primary', + 'color' => '#71706E', + 'name' => 'Primary', + ), + array( + 'slug' => 'tertiary', + 'color' => '#CFCFCF', + 'name' => 'Tertiary', + ), + ), + ), + ), + ); + + // Sort the array. + wp_recursive_ksort( $theme_json ); + + // Expected result. + $expected_theme_json = array( + 'settings' => array( + 'color' => array( + 'palette' => array( + array( + 'color' => '#242321', + 'name' => 'Foreground', + 'slug' => 'foreground', + ), + array( + 'color' => '#FCFBF8', + 'name' => 'Background', + 'slug' => 'background', + ), + array( + 'color' => '#71706E', + 'name' => 'Primary', + 'slug' => 'primary', + ), + array( + 'color' => '#CFCFCF', + 'name' => 'Tertiary', + 'slug' => 'tertiary', + ), + ), + ), + 'typography' => array( + 'fontFamilies' => array( + 'fontFamily' => 'DM Sans, sans-serif', + 'name' => 'DM Sans', + 'slug' => 'dm-sans', + ), + ), + ), + 'version' => 1, + ); + $this->assertEquals( $theme_json, $expected_theme_json ); + } +} From f26587afba1f2d1d73f6e7d60de5757705e3813c Mon Sep 17 00:00:00 2001 From: Luis Felipe Zaguini <26530524+zaguiini@users.noreply.github.com> Date: Thu, 7 Apr 2022 11:22:38 -0300 Subject: [PATCH 12/31] Webfonts API: Return font family slug when registering a webfont (#40120) --- lib/experimental/class-wp-webfonts.php | 4 ++-- lib/experimental/webfonts.php | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/experimental/class-wp-webfonts.php b/lib/experimental/class-wp-webfonts.php index d7c1badd088222..e422f51658befb 100644 --- a/lib/experimental/class-wp-webfonts.php +++ b/lib/experimental/class-wp-webfonts.php @@ -121,7 +121,7 @@ public function get_providers() { * @since 6.0.0 * * @param array $webfont Webfont to be registered. - * @return bool True if successfully registered, else false. + * @return string|false The font family slug if successfully registered, else false. */ public function register_webfont( array $webfont ) { $webfont = $this->validate_webfont( $webfont ); @@ -139,7 +139,7 @@ public function register_webfont( array $webfont ) { } $this->registered_webfonts[ $slug ][] = $webfont; - return true; + return $slug; } /** diff --git a/lib/experimental/webfonts.php b/lib/experimental/webfonts.php index 6d4c5f8dcc12b3..201101d1d7093e 100644 --- a/lib/experimental/webfonts.php +++ b/lib/experimental/webfonts.php @@ -63,11 +63,20 @@ function wp_webfonts() { * @param array[] $webfonts Webfonts to be registered. * This contains an array of webfonts to be registered. * Each webfont is an array. + * @return string[] The font family slug of the registered webfonts. */ function wp_register_webfonts( array $webfonts ) { + $registered_webfont_slugs = array(); + foreach ( $webfonts as $webfont ) { - wp_register_webfont( $webfont ); + $slug = wp_register_webfont( $webfont ); + + if ( is_string( $slug ) ) { + $registered_webfont_slugs[ $slug ] = true; + } } + + return array_keys( $registered_webfont_slugs ); } } @@ -94,7 +103,7 @@ function wp_register_webfonts( array $webfonts ) { * @since 6.0.0 * * @param array $webfont Webfont to be registered. - * @return bool True if successfully registered, else false. + * @return string|false The font family slug if successfully registered, else false. */ function wp_register_webfont( array $webfont ) { return wp_webfonts()->register_webfont( $webfont ); From d9808930aec3da092e85027f81636cedb0c96446 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 7 Apr 2022 18:55:21 +0400 Subject: [PATCH 13/31] Block Locking: Stabilize settings (#40145) * Block Locking: Stabilize settings * Stabilize block support flag * Fix lint error --- docs/reference-guides/block-api/block-supports.md | 6 +++--- packages/block-editor/README.md | 2 +- packages/block-editor/src/store/defaults.js | 6 ++++-- packages/block-editor/src/store/selectors.js | 4 ++-- .../src/components/provider/use-block-editor-settings.js | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/reference-guides/block-api/block-supports.md b/docs/reference-guides/block-api/block-supports.md index f38bec24ffe83e..5ca033cff021f4 100644 --- a/docs/reference-guides/block-api/block-supports.md +++ b/docs/reference-guides/block-api/block-supports.md @@ -519,17 +519,17 @@ supports: { } ``` -## __experimentalLock +## lock - Type: `boolean` - Default value: `true` -A block may want to disable the ability to toggle the lock state. It can be locked/unlocked by a user from the block "Options" dropdown by default. To disable this behavior, set `__experimentalLock` to `false`. +A block may want to disable the ability to toggle the lock state. It can be locked/unlocked by a user from the block "Options" dropdown by default. To disable this behavior, set `lock` to `false`. ```js supports: { // Remove support for locking UI. - __experimentalLock: false + lock: false } ``` diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 852a755ca9e866..bb4dc1725bc3fa 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -598,13 +598,13 @@ _Properties_ - _keepCaretInsideBlock_ `boolean`: Whether caret should move between blocks in edit mode - _bodyPlaceholder_ `string`: Empty post placeholder - _titlePlaceholder_ `string`: Empty title placeholder +- _canLockBlocks_ `boolean`: Whether the user can manage Block Lock state - _codeEditingEnabled_ `boolean`: Whether or not the user can switch to the code editor - _generateAnchors_ `boolean`: Enable/Disable auto anchor generation for Heading blocks - _\_\_experimentalCanUserUseUnfilteredHTML_ `boolean`: Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. - _\_\_experimentalBlockDirectory_ `boolean`: Whether the user has enabled the Block Directory - _\_\_experimentalBlockPatterns_ `Array`: Array of objects representing the block patterns - _\_\_experimentalBlockPatternCategories_ `Array`: Array of objects representing the block pattern categories -- _\_\_experimentalCanLockBlocks_ `boolean`: Whether the user can manage Block Lock state - _\_\_unstableGalleryWithImageBlocks_ `boolean`: Whether the user has enabled the refactored gallery block which uses InnerBlocks ### SkipToSelectedBlock diff --git a/packages/block-editor/src/store/defaults.js b/packages/block-editor/src/store/defaults.js index 33dc4ef022ee15..551f65146b4c98 100644 --- a/packages/block-editor/src/store/defaults.js +++ b/packages/block-editor/src/store/defaults.js @@ -23,13 +23,13 @@ export const PREFERENCES_DEFAULTS = { * @property {boolean} keepCaretInsideBlock Whether caret should move between blocks in edit mode * @property {string} bodyPlaceholder Empty post placeholder * @property {string} titlePlaceholder Empty title placeholder + * @property {boolean} canLockBlocks Whether the user can manage Block Lock state * @property {boolean} codeEditingEnabled Whether or not the user can switch to the code editor * @property {boolean} generateAnchors Enable/Disable auto anchor generation for Heading blocks * @property {boolean} __experimentalCanUserUseUnfilteredHTML Whether the user should be able to use unfiltered HTML or the HTML should be filtered e.g., to remove elements considered insecure like iframes. * @property {boolean} __experimentalBlockDirectory Whether the user has enabled the Block Directory * @property {Array} __experimentalBlockPatterns Array of objects representing the block patterns * @property {Array} __experimentalBlockPatternCategories Array of objects representing the block pattern categories - * @property {boolean} __experimentalCanLockBlocks Whether the user can manage Block Lock state * @property {boolean} __unstableGalleryWithImageBlocks Whether the user has enabled the refactored gallery block which uses InnerBlocks */ export const SETTINGS_DEFAULTS = { @@ -152,13 +152,15 @@ export const SETTINGS_DEFAULTS = { // List of allowed mime types and file extensions. allowedMimeTypes: null, + // Allows to disable block locking interface. + canLockBlocks: true, + __experimentalCanUserUseUnfilteredHTML: false, __experimentalBlockDirectory: false, __mobileEnablePageTemplates: false, __experimentalBlockPatterns: [], __experimentalBlockPatternCategories: [], __experimentalSpotlightEntityBlocks: [], - __experimentalCanLockBlocks: true, __unstableGalleryWithImageBlocks: false, generateAnchors: false, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 1e51cd770493c7..d3d5b7a4688e6d 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1578,12 +1578,12 @@ export function canMoveBlocks( state, clientIds, rootClientId = null ) { * @return {boolean} Whether a given block type can be locked/unlocked. */ export function canLockBlockType( state, nameOrType ) { - if ( ! hasBlockSupport( nameOrType, '__experimentalLock', true ) ) { + if ( ! hasBlockSupport( nameOrType, 'lock', true ) ) { return false; } // Use block editor settings as the default value. - return !! state.settings?.__experimentalCanLockBlocks; + return !! state.settings?.canLockBlocks; } /** diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 5ebb485174ea2d..a3fa78a7b7972e 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -105,11 +105,11 @@ function useBlockEditorSettings( settings, hasTemplate ) { '__experimentalFeatures', '__experimentalPreferredStyleVariations', '__experimentalSetIsInserterOpened', - '__experimentalCanLockBlocks', '__unstableGalleryWithImageBlocks', 'alignWide', 'allowedBlockTypes', 'bodyPlaceholder', + 'canLockBlocks', 'codeEditingEnabled', 'colors', 'disableCustomColors', From 0e5b82b29d7266306b4970b723633eb4ec45890b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 7 Apr 2022 19:46:51 +0400 Subject: [PATCH 14/31] Template Parts: Limit slug to Latin chars (#38861) --- .../block-library/src/template-part/edit/utils/hooks.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/template-part/edit/utils/hooks.js b/packages/block-library/src/template-part/edit/utils/hooks.js index 010e7f87eb80be..dab55db6fc19ae 100644 --- a/packages/block-library/src/template-part/edit/utils/hooks.js +++ b/packages/block-library/src/template-part/edit/utils/hooks.js @@ -102,13 +102,18 @@ export function useCreateTemplatePartFromBlocks( area, setAttributes ) { const { saveEntityRecord } = useDispatch( coreStore ); return async ( blocks = [], title = __( 'Untitled Template Part' ) ) => { + // Currently template parts only allow latin chars. + // Fallback slug will receive suffix by default. + const cleanSlug = + kebabCase( title ).replace( /[^\w-]+/g, '' ) || 'wp-custom-part'; + // If we have `area` set from block attributes, means an exposed // block variation was inserted. So add this prop to the template // part entity on creation. Afterwards remove `area` value from // block attributes. const record = { title, - slug: kebabCase( title ), + slug: cleanSlug, content: serialize( blocks ), // `area` is filterable on the server and defaults to `UNCATEGORIZED` // if provided value is not allowed. From d3116882edf17350e4e611598ec373aa0aefcf16 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 7 Apr 2022 20:15:21 +0400 Subject: [PATCH 15/31] Add 'supports.lock' to block.json schema (#40161) --- schemas/json/block.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/schemas/json/block.json b/schemas/json/block.json index 43e24b28f91ee0..2572b322d90b29 100644 --- a/schemas/json/block.json +++ b/schemas/json/block.json @@ -295,6 +295,11 @@ "description": "A block may want to disable the ability of being converted into a reusable block. By default all blocks can be converted to a reusable block. If supports reusable is set to false, the option to convert the block into a reusable block will not appear.", "default": true }, + "lock": { + "type": "boolean", + "description": "A block may want to disable the ability to toggle the lock state. It can be locked/unlocked by a user from the block 'Options' dropdown by default. To disable this behavior, set lock to false.", + "default": true + }, "spacing": { "type": "object", "description": "This value signals that a block supports some of the CSS style properties related to spacing. When it does, the block editor will show UI controls for the user to set their values, if the theme declares support.\n\nWhen the block declares support for a specific spacing property, the attributes definition is extended to include the style attribute.", From 33d4729068cc3152ecfc720ee11e000f7abcd427 Mon Sep 17 00:00:00 2001 From: Siobhan Bamber Date: Thu, 7 Apr 2022 18:26:15 +0100 Subject: [PATCH 16/31] Mobile Release v1.73.1 (#40134) Mobile Release v1.73.1 --- packages/react-native-aztec/package.json | 2 +- packages/react-native-bridge/package.json | 2 +- packages/react-native-editor/CHANGELOG.md | 4 ++++ packages/react-native-editor/ios/Podfile.lock | 8 ++++---- packages/react-native-editor/package.json | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/react-native-aztec/package.json b/packages/react-native-aztec/package.json index 32b0eb165c1360..add2a3b45a56bc 100644 --- a/packages/react-native-aztec/package.json +++ b/packages/react-native-aztec/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-aztec", - "version": "1.73.0", + "version": "1.73.1", "description": "Aztec view for react-native.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-bridge/package.json b/packages/react-native-bridge/package.json index 58b9de9b28c2d7..9392a67989146d 100644 --- a/packages/react-native-bridge/package.json +++ b/packages/react-native-bridge/package.json @@ -1,6 +1,6 @@ { "name": "@wordpress/react-native-bridge", - "version": "1.73.0", + "version": "1.73.1", "description": "Native bridge library used to integrate the block editor into a native App.", "private": true, "author": "The WordPress Contributors", diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index ca168b14b99835..a017db6b70cf91 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -13,6 +13,10 @@ For each user feature we should also add a importance categorization label to i - [*] Remove banner error notification on upload failure [#39694] +## 1.73.1 + +- [*] [Spacer block] Fix crash when changing the height value using the text input [#40053] + ## 1.73.0 - [*] Update react-native-reanimated version to 2.4.1 [#39430] diff --git a/packages/react-native-editor/ios/Podfile.lock b/packages/react-native-editor/ios/Podfile.lock index 0e0a4c55409939..6974ed91ee57ac 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.66.2) - fmt (6.2.1) - glog (0.3.5) - - Gutenberg (1.73.0): + - Gutenberg (1.73.1): - React-Core (= 0.66.2) - React-CoreModules (= 0.66.2) - React-RCTImage (= 0.66.2) @@ -337,7 +337,7 @@ PODS: - React-Core - RNSVG (9.13.6): - React-Core - - RNTAztecView (1.73.0): + - RNTAztecView (1.73.1): - React-Core - WordPress-Aztec-iOS (~> 1.19.8) - WordPress-Aztec-iOS (1.19.8) @@ -503,7 +503,7 @@ SPEC CHECKSUMS: FBReactNativeSpec: 18438b1c04ce502ed681cd19db3f4508964c082a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 5337263514dd6f09803962437687240c5dc39aa4 - Gutenberg: bc10452b8e27d87c2be7a236ad2ed43c86a10b55 + Gutenberg: bc774b5b5277763e517d1ba37c5af2a99c5b9be0 RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9 RCTRequired: 5e9e85f48da8dd447f5834ce14c6799ea8c7f41a RCTTypeSafety: aba333d04d88d1f954e93666a08d7ae57a87ab30 @@ -542,7 +542,7 @@ SPEC CHECKSUMS: RNReanimated: ae78e43201015a3fb2982a6e3ffe3507ee7f2d81 RNScreens: 953633729a42e23ad0c93574d676b361e3335e8b RNSVG: 36a7359c428dcb7c6bce1cc546fbfebe069809b0 - RNTAztecView: 68174e994beb0f260608fb97dc9d20bbc812f868 + RNTAztecView: d55667bbe550c4f638cbfe0397fbd93fffc66d9d WordPress-Aztec-iOS: 7d11d598f14c82c727c08b56bd35fbeb7dafb504 Yoga: 9a08effa851c1d8cc1647691895540bc168ea65f diff --git a/packages/react-native-editor/package.json b/packages/react-native-editor/package.json index 362ccb91621e99..bd6d45d8ae2460 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.73.0", + "version": "1.73.1", "description": "Mobile WordPress gutenberg editor.", "author": "The WordPress Contributors", "license": "GPL-2.0-or-later", From 0e2879929b6352ccf7de1dd72ae1033bec85241c Mon Sep 17 00:00:00 2001 From: Jos Date: Fri, 8 Apr 2022 09:43:23 +0800 Subject: [PATCH 17/31] [RNMobile] - Add optional wait in getBlockAtPosition (#40078) * move test data and create optional wait for getBlockAtPosition * lint fix extra space * rename for clarity Co-authored-by: jos <17252150+jostnes@users.noreply.github.com> --- .../gutenberg-editor-paragraph.test.js | 30 +++++++--------- .../__device-tests__/helpers/test-data.js | 12 +++++++ .../__device-tests__/pages/editor-page.js | 36 +++++++++++++++---- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js index f9afa06ebe7378..ff648eeeda3cbc 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-paragraph.test.js @@ -133,15 +133,12 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { } ); it( 'should be able to merge blocks with unknown html elements', async () => { - await editorPage.setHtmlContent( ` - -

abcD

- - - -

E

-` ); - + await editorPage.setHtmlContent( + [ + testData.unknownElementParagraphBlock, + testData.lettersInParagraphBlock, + ].join( '\n\n' ) + ); // // Merge paragraphs. const secondParagraphBlockElement = await editorPage.getBlockAtPosition( blockNames.paragraph, @@ -165,15 +162,12 @@ describe( 'Gutenberg Editor tests for Paragraph Block', () => { // Based on https://github.com/wordpress-mobile/gutenberg-mobile/pull/1507 it( 'should handle multiline paragraphs from web', async () => { - await editorPage.setHtmlContent( ` - -

multiple lines

- - - -

-` ); - + await editorPage.setHtmlContent( + [ + testData.multiLinesParagraphBlock, + testData.paragraphBlockEmpty, + ].join( '\n\n' ) + ); // // Merge paragraphs. const secondParagraphBlockElement = await editorPage.getBlockAtPosition( blockNames.paragraph, diff --git a/packages/react-native-editor/__device-tests__/helpers/test-data.js b/packages/react-native-editor/__device-tests__/helpers/test-data.js index 2c851aa195d911..b7dcf9267e40d6 100644 --- a/packages/react-native-editor/__device-tests__/helpers/test-data.js +++ b/packages/react-native-editor/__device-tests__/helpers/test-data.js @@ -165,3 +165,15 @@ exports.moreBlockEmpty = ` exports.paragraphBlockEmpty = `

`; + +exports.multiLinesParagraphBlock = ` +

multiple lines
multiple lines
multiple lines

+`; + +exports.unknownElementParagraphBlock = ` +

abcD

+`; + +exports.lettersInParagraphBlock = ` +

ABCD

+`; diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index 5486f5b31c327f..0a89deba8d5027 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -51,9 +51,31 @@ class EditorPage { async getBlockAtPosition( blockName, position = 1, - options = { autoscroll: false } + options = { autoscroll: false, useWaitForVisible: false } ) { - const blockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")]`; + let blockLocator; + + // Make it optional to use waitForVisible() so we can handle this test by test. + // This condition can be removed once we have gone through all test cases. + if ( options.useWaitForVisible ) { + let elementType; + switch ( blockName ) { + case blockNames.cover: + elementType = 'XCUIElementTypeButton'; + break; + default: + elementType = 'XCUIElementTypeOther'; + break; + } + + blockLocator = isAndroid() + ? `//android.view.ViewGroup[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")]` + : `(//${ elementType }[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")])[1]`; + + await waitForVisible( this.driver, blockLocator ); + } else { + blockLocator = `//*[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockName } Block. Row ${ position }")]`; + } const elements = await this.driver.elementsByXPath( blockLocator ); const lastElementFound = elements[ elements.length - 1 ]; if ( elements.length === 0 && options.autoscroll ) { @@ -429,6 +451,10 @@ class EditorPage { : '//XCUIElementTypeButton'; const blockActionsMenuButtonIdentifier = `Open Block Actions Menu`; const blockActionsMenuButtonLocator = `${ buttonElementName }[contains(@${ this.accessibilityIdXPathAttrib }, "${ blockActionsMenuButtonIdentifier }")]`; + const blockActionsMenuButton = await waitForVisible( + this.driver, + blockActionsMenuButtonLocator + ); if ( isAndroid() ) { const block = await this.getBlockAtPosition( blockName, position ); @@ -443,14 +469,12 @@ class EditorPage { } } - const blockActionsMenuButton = await this.driver.elementByXPath( - blockActionsMenuButtonLocator - ); await blockActionsMenuButton.click(); const removeActionButtonIdentifier = 'Remove block'; const removeActionButtonLocator = `${ buttonElementName }[contains(@${ this.accessibilityIdXPathAttrib }, "${ removeActionButtonIdentifier }")]`; - const removeActionButton = await this.driver.elementByXPath( + const removeActionButton = await waitForVisible( + this.driver, removeActionButtonLocator ); From e22a548f1d81b47e7d41cd2cb008971be079e5d4 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 8 Apr 2022 11:06:20 +0800 Subject: [PATCH 18/31] Turn the wrap to multiple lines option off by default on the row block (#40171) * Turn the wrap to multiple lines option off by default on the row block * Also avoid wrapping for row multi-select option --- .../src/components/convert-to-group-buttons/toolbar.js | 2 +- packages/block-library/src/group/variations.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js b/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js index 9d9f967b142023..8c610240df7d4e 100644 --- a/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js +++ b/packages/block-editor/src/components/convert-to-group-buttons/toolbar.js @@ -15,7 +15,7 @@ import { store as blockEditorStore } from '../../store'; const layouts = { group: undefined, - row: { type: 'flex' }, + row: { type: 'flex', flexWrap: 'nowrap' }, stack: { type: 'flex', orientation: 'vertical' }, }; diff --git a/packages/block-library/src/group/variations.js b/packages/block-library/src/group/variations.js index 97ede5caaf634d..3797a515c458f8 100644 --- a/packages/block-library/src/group/variations.js +++ b/packages/block-library/src/group/variations.js @@ -21,7 +21,7 @@ const variations = [ name: 'group-row', title: __( 'Row' ), description: __( 'Blocks shown in a row.' ), - attributes: { layout: { type: 'flex' } }, + attributes: { layout: { type: 'flex', flexWrap: 'nowrap' } }, scope: [ 'inserter', 'transform' ], isActive: ( blockAttributes ) => blockAttributes.layout?.type === 'flex' && From f8488e6d9598dee465a03ee68c0efb2af255320f Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Fri, 8 Apr 2022 11:34:16 +0800 Subject: [PATCH 19/31] Migrate `new-post` to Playwright (#39539) --- .../src/page/create-new-post.js | 66 +++++++++++ .../src/page/index.ts | 2 + .../specs/editor/various/new-post.test.js | 99 ----------------- .../e2e/specs/editor/various/new-post.spec.js | 104 ++++++++++++++++++ 4 files changed, 172 insertions(+), 99 deletions(-) create mode 100644 packages/e2e-test-utils-playwright/src/page/create-new-post.js delete mode 100644 packages/e2e-tests/specs/editor/various/new-post.test.js create mode 100644 test/e2e/specs/editor/various/new-post.spec.js diff --git a/packages/e2e-test-utils-playwright/src/page/create-new-post.js b/packages/e2e-test-utils-playwright/src/page/create-new-post.js new file mode 100644 index 00000000000000..2c88ec3fc07abb --- /dev/null +++ b/packages/e2e-test-utils-playwright/src/page/create-new-post.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { addQueryArgs } from '@wordpress/url'; + +/** + * Creates new post. + * + * @this {import('.').PageUtils} + * @param {Object} object Object to create new post, along with tips enabling option. + * @param {string} [object.postType] Post type of the new post. + * @param {string} [object.title] Title of the new post. + * @param {string} [object.content] Content of the new post. + * @param {string} [object.excerpt] Excerpt of the new post. + * @param {boolean} [object.showWelcomeGuide] Whether to show the welcome guide. + */ +export async function createNewPost( { + postType, + title, + content, + excerpt, + showWelcomeGuide = false, +} = {} ) { + const query = addQueryArgs( '', { + post_type: postType, + post_title: title, + content, + excerpt, + } ).slice( 1 ); + + await this.visitAdminPage( 'post-new.php', query ); + + await this.page.waitForSelector( '.edit-post-layout' ); + + const isWelcomeGuideActive = await this.page.evaluate( () => + window.wp.data + .select( 'core/edit-post' ) + .isFeatureActive( 'welcomeGuide' ) + ); + const isFullscreenMode = await this.page.evaluate( () => + window.wp.data + .select( 'core/edit-post' ) + .isFeatureActive( 'fullscreenMode' ) + ); + + if ( showWelcomeGuide !== isWelcomeGuideActive ) { + await this.page.evaluate( () => + window.wp.data + .dispatch( 'core/edit-post' ) + .toggleFeature( 'welcomeGuide' ) + ); + + await this.page.reload(); + await this.page.waitForSelector( '.edit-post-layout' ); + } + + if ( isFullscreenMode ) { + await this.page.evaluate( () => + window.wp.data + .dispatch( 'core/edit-post' ) + .toggleFeature( 'fullscreenMode' ) + ); + + await this.page.waitForSelector( 'body:not(.is-fullscreen-mode)' ); + } +} diff --git a/packages/e2e-test-utils-playwright/src/page/index.ts b/packages/e2e-test-utils-playwright/src/page/index.ts index b5d8eb0364ca42..60d532e0b527c5 100644 --- a/packages/e2e-test-utils-playwright/src/page/index.ts +++ b/packages/e2e-test-utils-playwright/src/page/index.ts @@ -7,6 +7,7 @@ import type { Browser, Page, BrowserContext } from '@playwright/test'; * Internal dependencies */ import { clickBlockToolbarButton } from './click-block-toolbar-button'; +import { createNewPost } from './create-new-post'; import { getPageError } from './get-page-error'; import { isCurrentURL } from './is-current-url'; import { showBlockToolbar } from './show-block-toolbar'; @@ -24,6 +25,7 @@ class PageUtils { } clickBlockToolbarButton = clickBlockToolbarButton; + createNewPost = createNewPost; getPageError = getPageError; isCurrentURL = isCurrentURL; showBlockToolbar = showBlockToolbar; diff --git a/packages/e2e-tests/specs/editor/various/new-post.test.js b/packages/e2e-tests/specs/editor/various/new-post.test.js deleted file mode 100644 index 0d1491370cb87b..00000000000000 --- a/packages/e2e-tests/specs/editor/various/new-post.test.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * WordPress dependencies - */ -import { - activatePlugin, - createNewPost, - deactivatePlugin, -} from '@wordpress/e2e-test-utils'; - -describe( 'new editor state', () => { - beforeAll( async () => { - await activatePlugin( 'gutenberg-test-plugin-post-formats-support' ); - } ); - - beforeEach( async () => { - await createNewPost(); - } ); - - afterAll( async () => { - await deactivatePlugin( 'gutenberg-test-plugin-post-formats-support' ); - } ); - - it( 'should show the New Post page in Gutenberg', async () => { - expect( page.url() ).toEqual( - expect.stringContaining( 'post-new.php' ) - ); - // Should display the blank title. - const title = await page.$( '[aria-label="Add title"]' ); - expect( title ).not.toBeNull(); - // Trim padding non-breaking space. - expect( - await title.evaluate( ( el ) => el.textContent.trim() ) - ).toBeFalsy(); - // Should display the Preview button. - const postPreviewButton = await page.$( - '.editor-post-preview.components-button' - ); - expect( postPreviewButton ).not.toBeNull(); - // Should display the Post Formats UI. - const postFormatsUi = await page.$( '.editor-post-format' ); - expect( postFormatsUi ).not.toBeNull(); - } ); - - it( 'should have no history', async () => { - const undoButton = await page.$( - '.editor-history__undo[aria-disabled="false"]' - ); - const redoButton = await page.$( - '.editor-history__redo[aria-disabled="false"]' - ); - - expect( undoButton ).toBeNull(); - expect( redoButton ).toBeNull(); - } ); - - it( 'should focus the title if the title is empty', async () => { - const activeElementClasses = await page.evaluate( () => { - return Object.values( document.activeElement.classList ); - } ); - const activeElementTagName = await page.evaluate( () => { - return document.activeElement.tagName.toLowerCase(); - } ); - - expect( activeElementClasses ).toContain( 'editor-post-title__input' ); - expect( activeElementTagName ).toEqual( 'h1' ); - } ); - - it( 'should not focus the title if the title exists', async () => { - // Enter a title for this post. - await page.type( '.editor-post-title__input', 'Here is the title' ); - // Save the post as a draft. - await page.click( '.editor-post-save-draft' ); - await page.waitForSelector( '.editor-post-saved-state.is-saved' ); - // Reload the browser so a post is loaded with a title. - await page.reload(); - await page.waitForSelector( '.edit-post-layout' ); - - const activeElementClasses = await page.evaluate( () => { - return Object.values( document.activeElement.classList ); - } ); - const activeElementTagName = await page.evaluate( () => { - return document.activeElement.tagName.toLowerCase(); - } ); - - expect( activeElementClasses ).not.toContain( - 'editor-post-title__input' - ); - // The document `body` should be the `activeElement`, because nothing is - // focused by default when a post already has a title. - expect( activeElementTagName ).toEqual( 'body' ); - } ); - - it( 'should be saveable with sufficient initial edits', async () => { - await createNewPost( { title: 'Here is the title' } ); - - // Verify saveable by presence of the Save Draft button. - await page.$( 'button.editor-post-save-draft' ); - } ); -} ); diff --git a/test/e2e/specs/editor/various/new-post.spec.js b/test/e2e/specs/editor/various/new-post.spec.js new file mode 100644 index 00000000000000..4f695da2f00e54 --- /dev/null +++ b/test/e2e/specs/editor/various/new-post.spec.js @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'new editor state', () => { + test.beforeAll( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( + 'gutenberg-test-plugin-post-formats-support' + ); + } ); + + test.afterAll( async ( { requestUtils } ) => { + await requestUtils.deactivatePlugin( + 'gutenberg-test-plugin-post-formats-support' + ); + } ); + + test( 'should show the New Post page in Gutenberg', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + await expect( page ).toHaveURL( /post-new.php/ ); + + // Should display the blank title. + const title = page.locator( 'role=textbox[name="Add title"i]' ); + await expect( title ).toBeEditable(); + await expect( title ).toHaveText( '' ); + + // Should display the Preview button. + await expect( + page.locator( 'role=button[name="Preview"i]' ) + ).toBeVisible(); + + // Should display the Post Formats UI. + await expect( + page.locator( 'role=combobox[name="Post Format"i]' ) + ).toBeVisible(); + } ); + + test( 'should have no history', async ( { page, pageUtils } ) => { + await pageUtils.createNewPost(); + + await expect( + page.locator( 'role=button[name="Undo"i]' ) + ).toBeDisabled(); + await expect( + page.locator( 'role=button[name="Redo"i]' ) + ).toBeDisabled(); + } ); + + test( 'should focus the title if the title is empty', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + await expect( + page.locator( 'role=textbox[name="Add title"i]' ) + ).toBeFocused(); + } ); + + test( 'should not focus the title if the title exists', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + // Enter a title for this post. + await page.type( + 'role=textbox[name="Add title"i]', + 'Here is the title' + ); + // Save the post as a draft. + await page.click( 'role=button[name="Save draft"i]' ); + await page.waitForSelector( + 'role=button[name="Dismiss this notice"] >> text=Draft saved' + ); + + // Reload the browser so a post is loaded with a title. + await page.reload(); + await page.waitForSelector( '.edit-post-layout' ); + + // The document `body` should be the `activeElement`, because nothing is + // focused by default when a post already has a title. + await expect( page.locator( 'body' ) ).toBeFocused(); + } ); + + test( 'should be saveable with sufficient initial edits', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost( { title: 'Here is the title' } ); + + // Verify saveable by presence of the Save Draft button. + const saveDraftButton = page.locator( + 'role=button[name="Save draft"i]' + ); + await expect( saveDraftButton ).toBeVisible(); + await expect( saveDraftButton ).toBeEnabled(); + } ); +} ); From 9b04278034d1c5e29f81c9930e5ba109d83d9996 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Fri, 8 Apr 2022 12:38:32 +0800 Subject: [PATCH 20/31] Migrate `cut-copy-paste-whole-blocks` to Playwright (#39807) * Add pressKeyWithModifier, insertBlock, and some other utils * Migrate copy-cut-paste-whole-blocks to Playwright * Remove original test * Fix broken rebase * Fix snapshot suffix * Use block-editor store instead of editor --- .../src/page/get-edited-post-content.js | 11 + .../src/page/index.ts | 10 + .../src/page/insert-block.js | 33 +++ .../src/page/press-key-with-modifier.ts | 138 +++++++++++ .../e2e-test-utils-playwright/src/test.ts | 11 + .../copy-cut-paste-whole-blocks.test.js.snap | 125 ---------- .../copy-cut-paste-whole-blocks.test.js | 191 --------------- .../copy-cut-paste-whole-blocks.spec.js | 224 ++++++++++++++++++ ...extual-element-image-spacer-1-chromium.txt | 9 + ...and-paste-individual-blocks-1-chromium.txt | 7 + ...and-paste-individual-blocks-2-chromium.txt | 11 + ...ts-are-focused-image-spacer-2-chromium.txt | 7 + ...ts-are-focused-image-spacer-1-chromium.txt | 3 + ...and-paste-individual-blocks-1-chromium.txt | 3 + ...and-paste-individual-blocks-2-chromium.txt | 7 + ...ld-handle-paste-events-once-1-chromium.txt | 9 + ...-input-fields-and-textareas-2-chromium.txt | 7 + ...-input-fields-and-textareas-1-chromium.txt | 3 + ...-copy-when-text-is-selected-1-chromium.txt | 7 + ...-copy-when-text-is-selected-2-chromium.txt | 11 + 20 files changed, 511 insertions(+), 316 deletions(-) create mode 100644 packages/e2e-test-utils-playwright/src/page/get-edited-post-content.js create mode 100644 packages/e2e-test-utils-playwright/src/page/insert-block.js create mode 100644 packages/e2e-test-utils-playwright/src/page/press-key-with-modifier.ts delete mode 100644 packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap delete mode 100644 packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-can-copy-group-onto-non-textual-element-image-spacer-1-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-1-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-2-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-74cd5-textual-elements-are-focused-image-spacer-2-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-899a8-textual-elements-are-focused-image-spacer-1-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-1-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-2-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-handle-paste-events-once-1-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--957b6-opy-in-places-like-input-fields-and-textareas-2-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--c7325-opy-in-places-like-input-fields-and-textareas-1-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-1-chromium.txt create mode 100644 test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-2-chromium.txt diff --git a/packages/e2e-test-utils-playwright/src/page/get-edited-post-content.js b/packages/e2e-test-utils-playwright/src/page/get-edited-post-content.js new file mode 100644 index 00000000000000..53585621dcd335 --- /dev/null +++ b/packages/e2e-test-utils-playwright/src/page/get-edited-post-content.js @@ -0,0 +1,11 @@ +/** + * Returns a promise which resolves with the edited post content (HTML string). + * + * @this {import('./').PageUtils} + * @return {Promise} Promise resolving with post content markup. + */ +export async function getEditedPostContent() { + return await this.page.evaluate( () => + window.wp.data.select( 'core/editor' ).getEditedPostContent() + ); +} diff --git a/packages/e2e-test-utils-playwright/src/page/index.ts b/packages/e2e-test-utils-playwright/src/page/index.ts index 60d532e0b527c5..e08f29f378ae18 100644 --- a/packages/e2e-test-utils-playwright/src/page/index.ts +++ b/packages/e2e-test-utils-playwright/src/page/index.ts @@ -8,8 +8,14 @@ import type { Browser, Page, BrowserContext } from '@playwright/test'; */ import { clickBlockToolbarButton } from './click-block-toolbar-button'; import { createNewPost } from './create-new-post'; +import { getEditedPostContent } from './get-edited-post-content'; import { getPageError } from './get-page-error'; +import { insertBlock } from './insert-block'; import { isCurrentURL } from './is-current-url'; +import { + setClipboardData, + pressKeyWithModifier, +} from './press-key-with-modifier'; import { showBlockToolbar } from './show-block-toolbar'; import { visitAdminPage } from './visit-admin-page'; @@ -26,8 +32,12 @@ class PageUtils { clickBlockToolbarButton = clickBlockToolbarButton; createNewPost = createNewPost; + getEditedPostContent = getEditedPostContent; getPageError = getPageError; + insertBlock = insertBlock; isCurrentURL = isCurrentURL; + pressKeyWithModifier = pressKeyWithModifier; + setClipboardData = setClipboardData; showBlockToolbar = showBlockToolbar; visitAdminPage = visitAdminPage; } diff --git a/packages/e2e-test-utils-playwright/src/page/insert-block.js b/packages/e2e-test-utils-playwright/src/page/insert-block.js new file mode 100644 index 00000000000000..2fcea3d8274dd8 --- /dev/null +++ b/packages/e2e-test-utils-playwright/src/page/insert-block.js @@ -0,0 +1,33 @@ +/** + * @typedef {Object} BlockRepresentation + * @property {string} name Block name. + * @property {?Object} attributes Block attributes. + * @property {?BlockRepresentation[]} innerBlocks Nested blocks. + */ + +/** + * @this {import('./').PageUtils} + * @param {BlockRepresentation} blockRepresentation Inserted block representation. + */ +async function insertBlock( blockRepresentation ) { + await this.page.evaluate( ( _blockRepresentation ) => { + function recursiveCreateBlock( { + name, + attributes = {}, + innerBlocks = [], + } ) { + return window.wp.blocks.createBlock( + name, + attributes, + innerBlocks.map( ( innerBlock ) => + recursiveCreateBlock( innerBlock ) + ) + ); + } + const block = recursiveCreateBlock( _blockRepresentation ); + + window.wp.data.dispatch( 'core/block-editor' ).insertBlock( block ); + }, blockRepresentation ); +} + +export { insertBlock }; diff --git a/packages/e2e-test-utils-playwright/src/page/press-key-with-modifier.ts b/packages/e2e-test-utils-playwright/src/page/press-key-with-modifier.ts new file mode 100644 index 00000000000000..a7d830edaa30a3 --- /dev/null +++ b/packages/e2e-test-utils-playwright/src/page/press-key-with-modifier.ts @@ -0,0 +1,138 @@ +/** + * External dependencies + */ +import { capitalize } from 'lodash'; +import type { Page } from '@playwright/test'; + +/** + * WordPress dependencies + */ +import { modifiers, SHIFT, ALT, CTRL } from '@wordpress/keycodes'; +import type { WPKeycodeModifier } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import type { PageUtils } from './index'; + +let clipboardDataHolder: { + plainText: string; + html: string; +} = { + plainText: '', + html: '', +}; + +/** + * Sets the clipboard data that can be pasted with + * `pressKeyWithModifier( 'primary', 'v' )`. + * + * @param this + * @param clipboardData + * @param clipboardData.plainText + * @param clipboardData.html + */ +export function setClipboardData( + this: PageUtils, + { plainText = '', html = '' }: typeof clipboardDataHolder +) { + clipboardDataHolder = { + plainText, + html, + }; +} + +async function emulateClipboard( page: Page, type: 'copy' | 'cut' | 'paste' ) { + clipboardDataHolder = await page.evaluate( + ( [ _type, _clipboardData ] ) => { + const clipboardDataTransfer = new DataTransfer(); + + if ( _type === 'paste' ) { + clipboardDataTransfer.setData( + 'text/plain', + _clipboardData.plainText + ); + clipboardDataTransfer.setData( + 'text/html', + _clipboardData.html + ); + } else { + const selection = window.getSelection()!; + const plainText = selection.toString(); + let html = plainText; + if ( selection.rangeCount ) { + const range = selection.getRangeAt( 0 ); + const fragment = range.cloneContents(); + html = Array.from( fragment.childNodes ) + .map( ( node ) => + Object.prototype.hasOwnProperty.call( + node, + 'outerHTML' + ) + ? ( node as Element ).outerHTML + : node.nodeValue + ) + .join( '' ); + } + clipboardDataTransfer.setData( 'text/plain', plainText ); + clipboardDataTransfer.setData( 'text/html', html ); + } + + document.activeElement?.dispatchEvent( + new ClipboardEvent( _type, { + bubbles: true, + cancelable: true, + clipboardData: clipboardDataTransfer, + } ) + ); + + return { + plainText: clipboardDataTransfer.getData( 'text/plain' ), + html: clipboardDataTransfer.getData( 'text/html' ), + }; + }, + [ type, clipboardDataHolder ] as const + ); +} + +/** + * Performs a key press with modifier (Shift, Control, Meta, Alt), where each modifier + * is normalized to platform-specific modifier. + * + * @param this + * @param modifier + * @param key + */ +export async function pressKeyWithModifier( + this: PageUtils, + modifier: WPKeycodeModifier, + key: string +) { + if ( modifier.toLowerCase() === 'primary' && key.toLowerCase() === 'c' ) { + return await emulateClipboard( this.page, 'copy' ); + } + + if ( modifier.toLowerCase() === 'primary' && key.toLowerCase() === 'x' ) { + return await emulateClipboard( this.page, 'cut' ); + } + + if ( modifier.toLowerCase() === 'primary' && key.toLowerCase() === 'v' ) { + return await emulateClipboard( this.page, 'paste' ); + } + + const isAppleOS = () => process.platform === 'darwin'; + const overWrittenModifiers = { + ...modifiers, + shiftAlt: ( _isApple: () => boolean ) => + _isApple() ? [ SHIFT, ALT ] : [ SHIFT, CTRL ], + }; + const mappedModifiers = overWrittenModifiers[ modifier ]( + isAppleOS + ).map( ( keycode ) => + keycode === CTRL ? 'Control' : capitalize( keycode ) + ); + + await this.page.keyboard.press( + `${ mappedModifiers.join( '+' ) }+${ key }` + ); +} diff --git a/packages/e2e-test-utils-playwright/src/test.ts b/packages/e2e-test-utils-playwright/src/test.ts index 0766c4e6d76200..a1d533e297de06 100644 --- a/packages/e2e-test-utils-playwright/src/test.ts +++ b/packages/e2e-test-utils-playwright/src/test.ts @@ -99,6 +99,7 @@ function observeConsoleLogging( message: ConsoleMessage ) { const test = base.extend< { pageUtils: PageUtils; + snapshotSuffix: void; }, { requestUtils: RequestUtils; @@ -136,6 +137,16 @@ const test = base.extend< }, { scope: 'worker' }, ], + // A work-around automatic fixture to remove the default snapshot suffix. + // See https://github.com/microsoft/playwright/issues/11134 + snapshotSuffix: [ + async ( {}, use, testInfo ) => { + testInfo.snapshotSuffix = ''; + + await use(); + }, + { auto: true }, + ], } ); export { test, expect }; diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap deleted file mode 100644 index 3f7451bd87db4a..00000000000000 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap +++ /dev/null @@ -1,125 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Copy/cut/paste of whole blocks can copy group onto non textual element (image, spacer) 1`] = `""`; - -exports[`Copy/cut/paste of whole blocks can copy group onto non textual element (image, spacer) 2`] = ` -" -

- - - -
-

P

-
-" -`; - -exports[`Copy/cut/paste of whole blocks should copy and paste individual blocks 1`] = ` -" -

Here is a unique string so we can test copying.

- - - -

2

-" -`; - -exports[`Copy/cut/paste of whole blocks should copy and paste individual blocks 2`] = ` -" -

Here is a unique string so we can test copying.

- - - -

2

- - - -

Here is a unique string so we can test copying.

-" -`; - -exports[`Copy/cut/paste of whole blocks should copy blocks when non textual elements are focused (image, spacer) 1`] = ` -" -
-" -`; - -exports[`Copy/cut/paste of whole blocks should copy blocks when non textual elements are focused (image, spacer) 2`] = ` -" -
- - - -
-" -`; - -exports[`Copy/cut/paste of whole blocks should cut and paste individual blocks 1`] = ` -" -

2

-" -`; - -exports[`Copy/cut/paste of whole blocks should cut and paste individual blocks 2`] = ` -" -

2

- - - -

Yet another unique string.

-" -`; - -exports[`Copy/cut/paste of whole blocks should handle paste events once 1`] = `""`; - -exports[`Copy/cut/paste of whole blocks should handle paste events once 2`] = ` -" -

- - - -
-

P

-
-" -`; - -exports[`Copy/cut/paste of whole blocks should respect inline copy in places like input fields and textareas 1`] = ` -" -[my-shortcode] -" -`; - -exports[`Copy/cut/paste of whole blocks should respect inline copy in places like input fields and textareas 2`] = ` -" -[my-shortcode] - - - -

Pasted: e]

-" -`; - -exports[`Copy/cut/paste of whole blocks should respect inline copy when text is selected 1`] = ` -" -

First block

- - - -

Second block

-" -`; - -exports[`Copy/cut/paste of whole blocks should respect inline copy when text is selected 2`] = ` -" -

First block

- - - -

ck

- - - -

Second block

-" -`; diff --git a/packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js b/packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js deleted file mode 100644 index 19d194d9607b7b..00000000000000 --- a/packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * WordPress dependencies - */ -import { - clickBlockAppender, - createNewPost, - pressKeyWithModifier, - getEditedPostContent, - insertBlock, -} from '@wordpress/e2e-test-utils'; - -describe( 'Copy/cut/paste of whole blocks', () => { - beforeEach( async () => { - await createNewPost(); - } ); - - it( 'should copy and paste individual blocks', async () => { - await clickBlockAppender(); - await page.keyboard.type( - 'Here is a unique string so we can test copying.' - ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '2' ); - await page.keyboard.press( 'ArrowUp' ); - - await pressKeyWithModifier( 'primary', 'c' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - await page.keyboard.press( 'ArrowDown' ); - await pressKeyWithModifier( 'primary', 'v' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should copy blocks when non textual elements are focused (image, spacer)', async () => { - await insertBlock( 'Spacer' ); - // At this point the spacer wrapper should be focused. - await pressKeyWithModifier( 'primary', 'c' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - await clickBlockAppender(); - await pressKeyWithModifier( 'primary', 'v' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should cut and paste individual blocks', async () => { - await clickBlockAppender(); - await page.keyboard.type( 'Yet another unique string.' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( '2' ); - await page.keyboard.press( 'ArrowUp' ); - - await pressKeyWithModifier( 'primary', 'x' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - await page.keyboard.press( 'Tab' ); - await page.keyboard.press( 'ArrowDown' ); - await pressKeyWithModifier( 'primary', 'v' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should respect inline copy when text is selected', async () => { - await clickBlockAppender(); - await page.keyboard.type( 'First block' ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( 'Second block' ); - await page.keyboard.press( 'ArrowUp' ); - await pressKeyWithModifier( 'shift', 'ArrowLeft' ); - await pressKeyWithModifier( 'shift', 'ArrowLeft' ); - - await pressKeyWithModifier( 'primary', 'c' ); - await page.keyboard.press( 'ArrowRight' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - await page.keyboard.press( 'Enter' ); - await pressKeyWithModifier( 'primary', 'v' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should respect inline copy in places like input fields and textareas', async () => { - await insertBlock( 'Shortcode' ); - await page.keyboard.type( '[my-shortcode]' ); - await pressKeyWithModifier( 'shift', 'ArrowLeft' ); - await pressKeyWithModifier( 'shift', 'ArrowLeft' ); - - await pressKeyWithModifier( 'primary', 'c' ); - await page.keyboard.press( 'ArrowRight' ); - await page.keyboard.press( 'ArrowRight' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'Pasted: ' ); - await pressKeyWithModifier( 'primary', 'v' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should handle paste events once', async () => { - // Add group block with paragraph. - await insertBlock( 'Group' ); - await page.click( '.block-editor-button-block-appender' ); - await page.click( '.editor-block-list-item-paragraph' ); - await page.keyboard.type( 'P' ); - await page.keyboard.press( 'ArrowLeft' ); - // Needs to be investigated why this is needed. - await page.evaluate( () => new Promise( requestAnimationFrame ) ); - await page.keyboard.press( 'ArrowLeft' ); - // Cut group. - await pressKeyWithModifier( 'primary', 'x' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - await page.keyboard.press( 'Enter' ); - - await page.evaluate( () => { - window.e2eTestPasteOnce = []; - let oldBlocks = wp.data.select( 'core/block-editor' ).getBlocks(); - wp.data.subscribe( () => { - const blocks = wp.data - .select( 'core/block-editor' ) - .getBlocks(); - if ( blocks !== oldBlocks ) { - window.e2eTestPasteOnce.push( - blocks.map( ( { clientId, name } ) => ( { - clientId, - name, - } ) ) - ); - } - oldBlocks = blocks; - } ); - } ); - - // Paste. - await pressKeyWithModifier( 'primary', 'v' ); - - // Blocks should only be modified once, not twice with new clientIds on a single paste action. - const blocksUpdated = await page.evaluate( - () => window.e2eTestPasteOnce - ); - - expect( blocksUpdated.length ).toEqual( 1 ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'can copy group onto non textual element (image, spacer)', async () => { - // Add group block with paragraph. - await insertBlock( 'Group' ); - await page.click( '.block-editor-button-block-appender' ); - await page.click( '.editor-block-list-item-paragraph' ); - await page.keyboard.type( 'P' ); - await page.keyboard.press( 'ArrowLeft' ); - // Needs to be investigated why this is needed. - await page.evaluate( () => new Promise( requestAnimationFrame ) ); - await page.keyboard.press( 'ArrowLeft' ); - // Cut group. - await pressKeyWithModifier( 'primary', 'x' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - - await page.keyboard.press( 'Enter' ); - - // Insert a non textual element (a spacer) - await insertBlock( 'Spacer' ); - // Spacer is focused. - await page.evaluate( () => { - window.e2eTestPasteOnce = []; - let oldBlocks = wp.data.select( 'core/block-editor' ).getBlocks(); - wp.data.subscribe( () => { - const blocks = wp.data - .select( 'core/block-editor' ) - .getBlocks(); - if ( blocks !== oldBlocks ) { - window.e2eTestPasteOnce.push( - blocks.map( ( { clientId, name } ) => ( { - clientId, - name, - } ) ) - ); - } - oldBlocks = blocks; - } ); - } ); - - await pressKeyWithModifier( 'primary', 'v' ); - - // Paste should be handled on non-textual elements and only handled once. - const blocksUpdated = await page.evaluate( - () => window.e2eTestPasteOnce - ); - - expect( blocksUpdated.length ).toEqual( 1 ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); -} ); diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js new file mode 100644 index 00000000000000..bdce392819ca4f --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js @@ -0,0 +1,224 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +test.describe( 'Copy/cut/paste of whole blocks', () => { + test( 'should copy and paste individual blocks', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + await page.click( 'role=button[name="Add default block"i]' ); + + await page.keyboard.type( + 'Here is a unique string so we can test copying.' + ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + + await pageUtils.pressKeyWithModifier( 'primary', 'c' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'ArrowDown' ); + await pageUtils.pressKeyWithModifier( 'primary', 'v' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + } ); + + test( 'should copy blocks when non textual elements are focused (image, spacer)', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + await pageUtils.insertBlock( { name: 'core/spacer' } ); + // At this point the spacer wrapper should be focused. + await pageUtils.pressKeyWithModifier( 'primary', 'c' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + + // The block appender is only visible when there's no selection. + await page.evaluate( () => { + window.wp.data.dispatch( 'core/block-editor' ).clearSelectedBlock(); + } ); + await page.click( 'role=button[name="Add default block"i]' ); + await pageUtils.pressKeyWithModifier( 'primary', 'v' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + } ); + + test( 'should cut and paste individual blocks', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + await page.click( 'role=button[name="Add default block"i]' ); + + await page.keyboard.type( 'Yet another unique string.' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + + await pageUtils.pressKeyWithModifier( 'primary', 'x' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'ArrowDown' ); + await pageUtils.pressKeyWithModifier( 'primary', 'v' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + } ); + + test( 'should respect inline copy when text is selected', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + await page.click( 'role=button[name="Add default block"i]' ); + + await page.keyboard.type( 'First block' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Second block' ); + await page.keyboard.press( 'ArrowUp' ); + await pageUtils.pressKeyWithModifier( 'shift', 'ArrowLeft' ); + await pageUtils.pressKeyWithModifier( 'shift', 'ArrowLeft' ); + + await pageUtils.pressKeyWithModifier( 'primary', 'c' ); + await page.keyboard.press( 'ArrowRight' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Enter' ); + await pageUtils.pressKeyWithModifier( 'primary', 'v' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + } ); + + test( 'should respect inline copy in places like input fields and textareas', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + await pageUtils.insertBlock( { name: 'core/shortcode' } ); + await page.keyboard.type( '[my-shortcode]' ); + await pageUtils.pressKeyWithModifier( 'shift', 'ArrowLeft' ); + await pageUtils.pressKeyWithModifier( 'shift', 'ArrowLeft' ); + + await pageUtils.pressKeyWithModifier( 'primary', 'c' ); + await page.keyboard.press( 'ArrowRight' ); + await page.keyboard.press( 'ArrowRight' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + + await pageUtils.insertBlock( { name: 'core/paragraph' } ); + await page.keyboard.type( 'Pasted: ' ); + await pageUtils.pressKeyWithModifier( 'primary', 'v' ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + } ); + + test( 'should handle paste events once', async ( { page, pageUtils } ) => { + await pageUtils.createNewPost(); + + // Add group block with paragraph. + await pageUtils.insertBlock( { + name: 'core/group', + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { content: 'P' }, + }, + ], + } ); + // Cut group. + await pageUtils.pressKeyWithModifier( 'primary', 'x' ); + expect( await pageUtils.getEditedPostContent() ).toBe( '' ); + + await page.keyboard.press( 'Enter' ); + + await page.evaluate( () => { + window.e2eTestPasteOnce = []; + let oldBlocks = window.wp.data + .select( 'core/block-editor' ) + .getBlocks(); + window.wp.data.subscribe( () => { + const blocks = window.wp.data + .select( 'core/block-editor' ) + .getBlocks(); + if ( blocks !== oldBlocks ) { + window.e2eTestPasteOnce.push( + blocks.map( ( { clientId, name } ) => ( { + clientId, + name, + } ) ) + ); + } + oldBlocks = blocks; + } ); + } ); + + // Paste. + await pageUtils.pressKeyWithModifier( 'primary', 'v' ); + + // Blocks should only be modified once, not twice with new clientIds on a single paste action. + const blocksUpdated = await page.evaluate( + () => window.e2eTestPasteOnce + ); + + expect( blocksUpdated.length ).toEqual( 1 ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + } ); + + test( 'can copy group onto non textual element (image, spacer)', async ( { + page, + pageUtils, + } ) => { + await pageUtils.createNewPost(); + + // Add group block with paragraph. + await pageUtils.insertBlock( { + name: 'core/group', + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { content: 'P' }, + }, + ], + } ); + // Cut group. + await pageUtils.pressKeyWithModifier( 'primary', 'x' ); + expect( await pageUtils.getEditedPostContent() ).toBe( '' ); + + // Insert a non textual element (a spacer) + await pageUtils.insertBlock( { name: 'core/spacer' } ); + // Spacer is focused. + await page.evaluate( () => { + window.e2eTestPasteOnce = []; + let oldBlocks = window.wp.data + .select( 'core/block-editor' ) + .getBlocks(); + window.wp.data.subscribe( () => { + const blocks = window.wp.data + .select( 'core/block-editor' ) + .getBlocks(); + if ( blocks !== oldBlocks ) { + window.e2eTestPasteOnce.push( + blocks.map( ( { clientId, name } ) => ( { + clientId, + name, + } ) ) + ); + } + oldBlocks = blocks; + } ); + } ); + + await pageUtils.pressKeyWithModifier( 'primary', 'v' ); + + // Paste should be handled on non-textual elements and only handled once. + const blocksUpdated = await page.evaluate( + () => window.e2eTestPasteOnce + ); + + expect( blocksUpdated.length ).toEqual( 1 ); + expect( await pageUtils.getEditedPostContent() ).toMatchSnapshot(); + } ); +} ); diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-can-copy-group-onto-non-textual-element-image-spacer-1-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-can-copy-group-onto-non-textual-element-image-spacer-1-chromium.txt new file mode 100644 index 00000000000000..201bddd8390141 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-can-copy-group-onto-non-textual-element-image-spacer-1-chromium.txt @@ -0,0 +1,9 @@ + +

+ + + +
+

P

+
+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-1-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-1-chromium.txt new file mode 100644 index 00000000000000..7c9d579d8fce49 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-1-chromium.txt @@ -0,0 +1,7 @@ + +

Here is a unique string so we can test copying.

+ + + +

2

+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-2-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-2-chromium.txt new file mode 100644 index 00000000000000..aff1bdf5c2a32d --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-and-paste-individual-blocks-2-chromium.txt @@ -0,0 +1,11 @@ + +

Here is a unique string so we can test copying.

+ + + +

2

+ + + +

Here is a unique string so we can test copying.

+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-74cd5-textual-elements-are-focused-image-spacer-2-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-74cd5-textual-elements-are-focused-image-spacer-2-chromium.txt new file mode 100644 index 00000000000000..3447ba0cf152e5 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-74cd5-textual-elements-are-focused-image-spacer-2-chromium.txt @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-899a8-textual-elements-are-focused-image-spacer-1-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-899a8-textual-elements-are-focused-image-spacer-1-chromium.txt new file mode 100644 index 00000000000000..d933c0bbc6ef07 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-copy-blo-899a8-textual-elements-are-focused-image-spacer-1-chromium.txt @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-1-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-1-chromium.txt new file mode 100644 index 00000000000000..863fd4e6a1ce49 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-1-chromium.txt @@ -0,0 +1,3 @@ + +

2

+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-2-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-2-chromium.txt new file mode 100644 index 00000000000000..f6e4d0abd3b048 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-cut-and-paste-individual-blocks-2-chromium.txt @@ -0,0 +1,7 @@ + +

2

+ + + +

Yet another unique string.

+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-handle-paste-events-once-1-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-handle-paste-events-once-1-chromium.txt new file mode 100644 index 00000000000000..201bddd8390141 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-handle-paste-events-once-1-chromium.txt @@ -0,0 +1,9 @@ + +

+ + + +
+

P

+
+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--957b6-opy-in-places-like-input-fields-and-textareas-2-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--957b6-opy-in-places-like-input-fields-and-textareas-2-chromium.txt new file mode 100644 index 00000000000000..3954dbe8762fc3 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--957b6-opy-in-places-like-input-fields-and-textareas-2-chromium.txt @@ -0,0 +1,7 @@ + +[my-shortcode] + + + +

Pasted: e]

+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--c7325-opy-in-places-like-input-fields-and-textareas-1-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--c7325-opy-in-places-like-input-fields-and-textareas-1-chromium.txt new file mode 100644 index 00000000000000..33c6c911db3800 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect--c7325-opy-in-places-like-input-fields-and-textareas-1-chromium.txt @@ -0,0 +1,3 @@ + +[my-shortcode] + \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-1-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-1-chromium.txt new file mode 100644 index 00000000000000..5e229b8e6ed81b --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-1-chromium.txt @@ -0,0 +1,7 @@ + +

First block

+ + + +

Second block

+ \ No newline at end of file diff --git a/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-2-chromium.txt b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-2-chromium.txt new file mode 100644 index 00000000000000..be02e7518e14d5 --- /dev/null +++ b/test/e2e/specs/editor/various/copy-cut-paste-whole-blocks.spec.js-snapshots/Copy-cut-paste-of-whole-blocks-should-respect-inline-copy-when-text-is-selected-2-chromium.txt @@ -0,0 +1,11 @@ + +

First block

+ + + +

ck

+ + + +

Second block

+ \ No newline at end of file From ba9b739ef9bb11e4107cf02f8f2366f34a99f9ed Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Fri, 8 Apr 2022 07:48:21 +0200 Subject: [PATCH 21/31] Add duotone to placeholders for site logo and featured image (#40085) * Add duotone to placeholders --- packages/block-library/src/post-featured-image/block.json | 2 +- packages/block-library/src/site-logo/block.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/post-featured-image/block.json b/packages/block-library/src/post-featured-image/block.json index 982ba9ac68e8b1..4ca11f519545b6 100644 --- a/packages/block-library/src/post-featured-image/block.json +++ b/packages/block-library/src/post-featured-image/block.json @@ -29,7 +29,7 @@ "supports": { "align": [ "left", "right", "center", "wide", "full" ], "color": { - "__experimentalDuotone": "img", + "__experimentalDuotone": "img, .wp-block-post-featured-image__placeholder, .components-placeholder__illustration, .components-placeholder::before", "text": false, "background": false }, diff --git a/packages/block-library/src/site-logo/block.json b/packages/block-library/src/site-logo/block.json index 1fe60445c5ecf7..1bd8897701ef96 100644 --- a/packages/block-library/src/site-logo/block.json +++ b/packages/block-library/src/site-logo/block.json @@ -34,7 +34,7 @@ "align": true, "alignWide": false, "color": { - "__experimentalDuotone": "img", + "__experimentalDuotone": "img, .components-placeholder__illustration, .components-placeholder::before", "text": false, "background": false } From 01bb7e411c50056689174f9e5e64727f69d5440e Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Fri, 8 Apr 2022 16:24:11 +1000 Subject: [PATCH 22/31] List View: Only show ellipsis on first selected item or when focused (#40174) --- .../src/components/list-view/block.js | 9 +++- .../src/components/list-view/style.scss | 48 +++++++++++++------ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 71a1c42638157d..28a4cbf44b820b 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -56,6 +56,11 @@ function ListViewBlock( { const cellRef = useRef( null ); const [ isHovered, setIsHovered ] = useState( false ); const { clientId } = block; + const isFirstSelectedBlock = + isSelected && selectedClientIds[ 0 ] === clientId; + const isLastSelectedBlock = + isSelected && + selectedClientIds[ selectedClientIds.length - 1 ] === clientId; const { toggleBlockHighlight } = useDispatch( blockEditorStore ); @@ -102,7 +107,7 @@ function ListViewBlock( { const listViewBlockSettingsClassName = classnames( 'block-editor-list-view-block__menu-cell', - { 'is-visible': isHovered || isSelected } + { 'is-visible': isHovered || isFirstSelectedBlock } ); // If ListView has experimental features related to the Persistent List View, @@ -177,6 +182,8 @@ function ListViewBlock( { const classes = classnames( { 'is-selected': isSelected, + 'is-first-selected': isFirstSelectedBlock, + 'is-last-selected': isLastSelectedBlock, 'is-branch-selected': withExperimentalPersistentListViewFeatures && isBranchSelected, 'is-dragging': isDragged, diff --git a/packages/block-editor/src/components/list-view/style.scss b/packages/block-editor/src/components/list-view/style.scss index 318a71f8b92e81..a7f17303336307 100644 --- a/packages/block-editor/src/components/list-view/style.scss +++ b/packages/block-editor/src/components/list-view/style.scss @@ -46,11 +46,17 @@ } // Border radius for corners of the selected item. - &.is-selected td:first-child { - border-radius: $radius-block-ui 0 0 $radius-block-ui; + &.is-first-selected td:first-child { + border-top-left-radius: $radius-block-ui; } - &.is-selected td:last-child { - border-radius: 0 $radius-block-ui $radius-block-ui 0; + &.is-first-selected td:last-child { + border-top-right-radius: $radius-block-ui; + } + &.is-last-selected td:first-child { + border-bottom-left-radius: $radius-block-ui; + } + &.is-last-selected td:last-child { + border-bottom-right-radius: $radius-block-ui; } &.is-branch-selected:not(.is-selected) { // Lighten a CSS variable without introducing a new SASS variable @@ -58,18 +64,24 @@ linear-gradient(transparentize($white, 0.1), transparentize($white, 0.1)), linear-gradient(var(--wp-admin-theme-color), var(--wp-admin-theme-color)); } - &.is-branch-selected.is-selected td:first-child { - border-radius: $radius-block-ui 0 0 0; + &.is-branch-selected.is-first-selected td:first-child { + border-top-left-radius: $radius-block-ui; } - &.is-branch-selected.is-selected td:last-child { - border-radius: 0 $radius-block-ui 0 0; + &.is-branch-selected.is-first-selected td:last-child { + border-top-right-radius: $radius-block-ui; } &[aria-expanded="false"] { - &.is-branch-selected.is-selected td:first-child { - border-radius: $radius-block-ui 0 0 $radius-block-ui; + &.is-branch-selected.is-first-selected td:first-child { + border-top-left-radius: $radius-block-ui; + } + &.is-branch-selected.is-first-selected td:last-child { + border-top-right-radius: $radius-block-ui; + } + &.is-branch-selected.is-last-selected td:first-child { + border-bottom-left-radius: $radius-block-ui; } - &.is-branch-selected.is-selected td:last-child { - border-radius: 0 $radius-block-ui $radius-block-ui 0; + &.is-branch-selected.is-last-selected td:last-child { + border-bottom-right-radius: $radius-block-ui; } } &.is-branch-selected:not(.is-selected) td { @@ -167,17 +179,23 @@ .block-editor-list-view-block__mover-cell { line-height: 0; width: $button-size; - opacity: 0; vertical-align: middle; @include reduce-motion("transition"); + > * { + opacity: 0; + } + // Show on hover, visible, and show above to keep the hit area size. &:hover, &.is-visible { position: relative; z-index: 1; - opacity: 1; - @include edit-post__fade-in-animation; + + > * { + opacity: 1; + @include edit-post__fade-in-animation; + } } &, From c2fa0fac97bf2a97c2ec983bb30aeede41741c5c Mon Sep 17 00:00:00 2001 From: David Arenas Date: Fri, 8 Apr 2022 10:24:26 +0200 Subject: [PATCH 23/31] Comments Query Loop: Update default template (#40165) * Change isLink and fontSize defaults The fontSize prop default is set to 'small'. The isLink prop is set to true. Blocks with changes are: - Comment Author Name - Comment Date - Comment Edit Link - Comment Reply Link * Improve default template * Update fixtures * Update php-unit tests * Remove trailing comma --- docs/reference-guides/core-blocks.md | 8 +- .../src/comment-author-name/block.json | 6 +- .../src/comment-author-name/index.php | 3 + .../block-library/src/comment-date/block.json | 6 +- .../block-library/src/comment-date/index.php | 7 +- .../src/comment-edit-link/block.json | 4 + .../src/comment-edit-link/index.php | 3 + .../src/comment-reply-link/block.json | 4 + .../src/comment-reply-link/index.php | 5 +- .../src/comments-query-loop/edit.js | 55 +++++- ...ss-block-library-comment-template-test.php | 10 +- .../blocks/core__comment-author-name.json | 5 +- ...author-name__deprecated-v1.serialized.html | 2 +- .../fixtures/blocks/core__comment-date.json | 3 +- ...omment-date__deprecated-v1.serialized.html | 2 +- .../blocks/core__comment-edit-link.json | 2 +- .../core__comment-edit-link.serialized.html | 2 +- .../blocks/core__comment-reply-link.json | 2 +- .../core__comment-reply-link.serialized.html | 2 +- .../blocks/core__comments-query-loop.html | 36 +++- .../blocks/core__comments-query-loop.json | 139 ++++++++++++--- .../core__comments-query-loop.parsed.json | 164 ++++++++++++++---- .../core__comments-query-loop.serialized.html | 28 ++- 23 files changed, 407 insertions(+), 91 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index b8ed20978c7b9b..fef3d7540cfe84 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -123,7 +123,7 @@ Displays the name of the author of the comment. ([Source](https://github.com/Wor - **Name:** core/comment-author-name - **Category:** theme - **Supports:** color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** isLink, linkTarget, textAlign +- **Attributes:** fontSize, isLink, linkTarget, textAlign ## Comment Content @@ -141,7 +141,7 @@ Displays the date on which the comment was posted. ([Source](https://github.com/ - **Name:** core/comment-date - **Category:** theme - **Supports:** color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** format, isLink +- **Attributes:** fontSize, format, isLink ## Comment Edit Link @@ -150,7 +150,7 @@ Displays a link to edit the comment in the WordPress Dashboard. This link is onl - **Name:** core/comment-edit-link - **Category:** theme - **Supports:** color (background, gradients, link, ~~text~~), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** linkTarget, textAlign +- **Attributes:** fontSize, linkTarget, textAlign ## Comment Reply Link @@ -159,7 +159,7 @@ Displays a link to reply to a comment. ([Source](https://github.com/WordPress/gu - **Name:** core/comment-reply-link - **Category:** theme - **Supports:** color (background, gradients, link, ~~text~~), typography (fontSize, lineHeight), ~~html~~ -- **Attributes:** textAlign +- **Attributes:** fontSize, textAlign ## Comment Template diff --git a/packages/block-library/src/comment-author-name/block.json b/packages/block-library/src/comment-author-name/block.json index 81c2653caad0d0..e7a07e5ddbc23b 100644 --- a/packages/block-library/src/comment-author-name/block.json +++ b/packages/block-library/src/comment-author-name/block.json @@ -10,7 +10,7 @@ "attributes": { "isLink": { "type": "boolean", - "default": false + "default": true }, "linkTarget": { "type": "string", @@ -18,6 +18,10 @@ }, "textAlign": { "type": "string" + }, + "fontSize": { + "type": "string", + "default": "small" } }, "usesContext": [ "commentId" ], diff --git a/packages/block-library/src/comment-author-name/index.php b/packages/block-library/src/comment-author-name/index.php index 7e60da831a9ef1..0bf3a475ba899e 100644 --- a/packages/block-library/src/comment-author-name/index.php +++ b/packages/block-library/src/comment-author-name/index.php @@ -27,6 +27,9 @@ function render_block_core_comment_author_name( $attributes, $content, $block ) if ( isset( $attributes['textAlign'] ) ) { $classes .= 'has-text-align-' . esc_attr( $attributes['textAlign'] ); } + if ( isset( $attributes['fontSize'] ) ) { + $classes .= 'has-' . esc_attr( $attributes['fontSize'] ) . '-font-size'; + } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) ); $comment_author = get_comment_author( $comment ); diff --git a/packages/block-library/src/comment-date/block.json b/packages/block-library/src/comment-date/block.json index afffce71c48b17..9af63f281ac2e3 100644 --- a/packages/block-library/src/comment-date/block.json +++ b/packages/block-library/src/comment-date/block.json @@ -13,7 +13,11 @@ }, "isLink": { "type": "boolean", - "default": false + "default": true + }, + "fontSize": { + "type": "string", + "default": "small" } }, "usesContext": [ "commentId" ], diff --git a/packages/block-library/src/comment-date/index.php b/packages/block-library/src/comment-date/index.php index 6c1a941b57c254..ddddd57f811621 100644 --- a/packages/block-library/src/comment-date/index.php +++ b/packages/block-library/src/comment-date/index.php @@ -23,7 +23,12 @@ function render_block_core_comment_date( $attributes, $content, $block ) { return ''; } - $wrapper_attributes = get_block_wrapper_attributes(); + $classes = ''; + if ( isset( $attributes['fontSize'] ) ) { + $classes .= 'has-' . esc_attr( $attributes['fontSize'] ) . '-font-size'; + } + + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) ); $formatted_date = get_comment_date( isset( $attributes['format'] ) ? $attributes['format'] : '', $comment diff --git a/packages/block-library/src/comment-edit-link/block.json b/packages/block-library/src/comment-edit-link/block.json index d2dd0fe226dc8f..16fe2ca1097a47 100644 --- a/packages/block-library/src/comment-edit-link/block.json +++ b/packages/block-library/src/comment-edit-link/block.json @@ -15,6 +15,10 @@ }, "textAlign": { "type": "string" + }, + "fontSize": { + "type": "string", + "default": "small" } }, "supports": { diff --git a/packages/block-library/src/comment-edit-link/index.php b/packages/block-library/src/comment-edit-link/index.php index 0816bbcec98c4e..43e5d2482f63a1 100644 --- a/packages/block-library/src/comment-edit-link/index.php +++ b/packages/block-library/src/comment-edit-link/index.php @@ -31,6 +31,9 @@ function render_block_core_comment_edit_link( $attributes, $content, $block ) { if ( isset( $attributes['textAlign'] ) ) { $classes .= 'has-text-align-' . esc_attr( $attributes['textAlign'] ); } + if ( isset( $attributes['fontSize'] ) ) { + $classes .= 'has-' . esc_attr( $attributes['fontSize'] ) . '-font-size'; + } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) ); diff --git a/packages/block-library/src/comment-reply-link/block.json b/packages/block-library/src/comment-reply-link/block.json index ea61ca3553f9c1..a217511da5a9c1 100644 --- a/packages/block-library/src/comment-reply-link/block.json +++ b/packages/block-library/src/comment-reply-link/block.json @@ -11,6 +11,10 @@ "attributes": { "textAlign": { "type": "string" + }, + "fontSize": { + "type": "string", + "default": "small" } }, "supports": { diff --git a/packages/block-library/src/comment-reply-link/index.php b/packages/block-library/src/comment-reply-link/index.php index 169b62ac24bdf0..7ffb97af1bdeb0 100644 --- a/packages/block-library/src/comment-reply-link/index.php +++ b/packages/block-library/src/comment-reply-link/index.php @@ -53,7 +53,10 @@ function render_block_core_comment_reply_link( $attributes, $content, $block ) { $classes = ''; if ( isset( $attributes['textAlign'] ) ) { - $classes .= 'has-text-align-' . $attributes['textAlign']; + $classes .= 'has-text-align-' . esc_attr( $attributes['textAlign'] ); + } + if ( isset( $attributes['fontSize'] ) ) { + $classes .= 'has-' . esc_attr( $attributes['fontSize'] ) . '-font-size'; } $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classes ) ); diff --git a/packages/block-library/src/comments-query-loop/edit.js b/packages/block-library/src/comments-query-loop/edit.js index 096c237baf9f79..297811030f15ef 100644 --- a/packages/block-library/src/comments-query-loop/edit.js +++ b/packages/block-library/src/comments-query-loop/edit.js @@ -9,7 +9,60 @@ import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor'; import CommentsInspectorControls from './edit/comments-inspector-controls'; const TEMPLATE = [ - [ 'core/comment-template' ], + [ + 'core/comment-template', + {}, + [ + [ + 'core/columns', + {}, + [ + [ + 'core/column', + { width: '40px' }, + [ + [ + 'core/avatar', + { + size: 40, + style: { + border: { radius: '20px' }, + }, + }, + ], + ], + ], + [ + 'core/column', + {}, + [ + [ 'core/comment-author-name' ], + [ + 'core/group', + { + layout: { type: 'flex' }, + style: { + spacing: { + margin: { + top: '0px', + bottom: '0px', + }, + }, + }, + }, + [ + [ 'core/comment-date' ], + [ 'core/comment-edit-link' ], + ], + ], + [ 'core/comment-content' ], + [ 'core/comment-reply-link' ], + ], + ], + ], + ], + ], + ], [ 'core/comments-pagination' ], ]; diff --git a/phpunit/class-block-library-comment-template-test.php b/phpunit/class-block-library-comment-template-test.php index eab3b034e77099..48a7e060c9ad89 100644 --- a/phpunit/class-block-library-comment-template-test.php +++ b/phpunit/class-block-library-comment-template-test.php @@ -39,6 +39,7 @@ public function setUp() { array( 'comment_author' => 'Test', 'comment_author_email' => 'test@example.org', + 'comment_author_url' => 'http://example.com/author-url/', 'comment_content' => 'Hello world', ) ); @@ -112,8 +113,8 @@ function test_rendering_comment_template() { // Here we use the function prefixed with 'gutenberg_*' because it's added // in the build step. $this->assertEquals( - gutenberg_render_block_core_comment_template( null, null, $block ), - '
  1. Test
    Hello world
' + '
  1. Hello world
', + gutenberg_render_block_core_comment_template( null, null, $block ) ); } @@ -132,6 +133,7 @@ function test_rendering_comment_template_nested() { 'comment_parent' => self::$comment_ids[0], 'comment_author' => 'Test', 'comment_author_email' => 'test@example.org', + 'comment_author_url' => 'http://example.com/author-url/', 'comment_content' => 'Hello world', ) ); @@ -143,6 +145,7 @@ function test_rendering_comment_template_nested() { 'comment_parent' => $nested_comment_ids[0], 'comment_author' => 'Test', 'comment_author_email' => 'test@example.org', + 'comment_author_url' => 'http://example.com/author-url/', 'comment_content' => 'Hello world', ) ); @@ -160,7 +163,7 @@ function test_rendering_comment_template_nested() { $this->assertEquals( gutenberg_render_block_core_comment_template( null, null, $block ), - '
  1. Test
    Hello world
    1. Test
      Hello world
      1. Test
        Hello world
' + '
  1. Hello world
    1. Hello world
      1. Hello world
' ); } /** @@ -180,6 +183,7 @@ function test_build_comment_query_vars_from_block_sets_cpage_var() { array( 'comment_author' => 'Test', 'comment_author_email' => 'test@example.org', + 'comment_author_url' => 'http://example.com/author-url/', 'comment_content' => 'Hello world', ) ); diff --git a/test/integration/fixtures/blocks/core__comment-author-name.json b/test/integration/fixtures/blocks/core__comment-author-name.json index 1ff63f196a8d97..2d18af64e46af5 100644 --- a/test/integration/fixtures/blocks/core__comment-author-name.json +++ b/test/integration/fixtures/blocks/core__comment-author-name.json @@ -3,8 +3,9 @@ "name": "core/comment-author-name", "isValid": true, "attributes": { - "isLink": false, - "linkTarget": "_self" + "isLink": true, + "linkTarget": "_self", + "fontSize": "small" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__comment-author-name__deprecated-v1.serialized.html b/test/integration/fixtures/blocks/core__comment-author-name__deprecated-v1.serialized.html index 732bcce77f5d00..bd70733721656c 100644 --- a/test/integration/fixtures/blocks/core__comment-author-name__deprecated-v1.serialized.html +++ b/test/integration/fixtures/blocks/core__comment-author-name__deprecated-v1.serialized.html @@ -1 +1 @@ - + diff --git a/test/integration/fixtures/blocks/core__comment-date.json b/test/integration/fixtures/blocks/core__comment-date.json index 7664eaf81d7b62..bc5abfbda14d3b 100644 --- a/test/integration/fixtures/blocks/core__comment-date.json +++ b/test/integration/fixtures/blocks/core__comment-date.json @@ -3,7 +3,8 @@ "name": "core/comment-date", "isValid": true, "attributes": { - "isLink": false + "isLink": true, + "fontSize": "small" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__comment-date__deprecated-v1.serialized.html b/test/integration/fixtures/blocks/core__comment-date__deprecated-v1.serialized.html index 10708571d8fdb4..07c402061599fb 100644 --- a/test/integration/fixtures/blocks/core__comment-date__deprecated-v1.serialized.html +++ b/test/integration/fixtures/blocks/core__comment-date__deprecated-v1.serialized.html @@ -1 +1 @@ - + diff --git a/test/integration/fixtures/blocks/core__comment-edit-link.json b/test/integration/fixtures/blocks/core__comment-edit-link.json index 1584c46ac563bb..5254558c3d2d49 100644 --- a/test/integration/fixtures/blocks/core__comment-edit-link.json +++ b/test/integration/fixtures/blocks/core__comment-edit-link.json @@ -4,9 +4,9 @@ "isValid": true, "attributes": { "linkTarget": "_blank", + "fontSize": "extra-large", "backgroundColor": "recommended-color-03", "fontFamily": "system-monospace", - "fontSize": "extra-large", "style": { "typography": { "fontStyle": "normal", diff --git a/test/integration/fixtures/blocks/core__comment-edit-link.serialized.html b/test/integration/fixtures/blocks/core__comment-edit-link.serialized.html index 8492ea9b93dab1..820db57453cadb 100644 --- a/test/integration/fixtures/blocks/core__comment-edit-link.serialized.html +++ b/test/integration/fixtures/blocks/core__comment-edit-link.serialized.html @@ -1 +1 @@ - + diff --git a/test/integration/fixtures/blocks/core__comment-reply-link.json b/test/integration/fixtures/blocks/core__comment-reply-link.json index 5e9bc4df6f4230..0a6a45d058c810 100644 --- a/test/integration/fixtures/blocks/core__comment-reply-link.json +++ b/test/integration/fixtures/blocks/core__comment-reply-link.json @@ -4,8 +4,8 @@ "isValid": true, "attributes": { "textAlign": "right", - "fontFamily": "cambria-georgia", "fontSize": "extra-large", + "fontFamily": "cambria-georgia", "style": { "typography": { "lineHeight": "0.8", diff --git a/test/integration/fixtures/blocks/core__comment-reply-link.serialized.html b/test/integration/fixtures/blocks/core__comment-reply-link.serialized.html index 5f90cd2be65cb9..433f13508073e8 100644 --- a/test/integration/fixtures/blocks/core__comment-reply-link.serialized.html +++ b/test/integration/fixtures/blocks/core__comment-reply-link.serialized.html @@ -1 +1 @@ - + diff --git a/test/integration/fixtures/blocks/core__comments-query-loop.html b/test/integration/fixtures/blocks/core__comments-query-loop.html index feed071fb840f1..2fbc1e0de80de2 100644 --- a/test/integration/fixtures/blocks/core__comments-query-loop.html +++ b/test/integration/fixtures/blocks/core__comments-query-loop.html @@ -1,17 +1,41 @@
- + +
+ +
+ +
+ - + +
+ - + +
+ - + +
+ - + - + +
+ +
+ + + + + + + + +
diff --git a/test/integration/fixtures/blocks/core__comments-query-loop.json b/test/integration/fixtures/blocks/core__comments-query-loop.json index 643bec7fe66714..98e97afdb9c90a 100644 --- a/test/integration/fixtures/blocks/core__comments-query-loop.json +++ b/test/integration/fixtures/blocks/core__comments-query-loop.json @@ -12,49 +12,132 @@ "attributes": {}, "innerBlocks": [ { - "name": "core/comment-author-avatar", + "name": "core/columns", "isValid": true, "attributes": { - "width": 96, - "height": 96 + "isStackedOnMobile": true }, - "innerBlocks": [] - }, - { - "name": "core/comment-author-name", - "isValid": true, - "attributes": { - "isLink": false, - "linkTarget": "_self" - }, - "innerBlocks": [] - }, - { - "name": "core/comment-date", - "isValid": true, - "attributes": { - "isLink": false - }, - "innerBlocks": [] - }, + "innerBlocks": [ + { + "name": "core/column", + "isValid": true, + "attributes": { + "width": "40px" + }, + "innerBlocks": [ + { + "name": "core/avatar", + "isValid": true, + "attributes": { + "size": 40, + "isLink": false, + "linkTarget": "_self", + "style": { + "border": { + "radius": "20px" + } + } + }, + "innerBlocks": [] + } + ] + }, + { + "name": "core/column", + "isValid": true, + "attributes": {}, + "innerBlocks": [ + { + "name": "core/comment-author-name", + "isValid": true, + "attributes": { + "isLink": true, + "linkTarget": "_self", + "fontSize": "small" + }, + "innerBlocks": [] + }, + { + "name": "core/group", + "isValid": true, + "attributes": { + "tagName": "div", + "style": { + "spacing": { + "margin": { + "top": "0px", + "bottom": "0px" + } + } + }, + "layout": { + "type": "flex" + } + }, + "innerBlocks": [ + { + "name": "core/comment-date", + "isValid": true, + "attributes": { + "isLink": true, + "fontSize": "small" + }, + "innerBlocks": [] + }, + { + "name": "core/comment-edit-link", + "isValid": true, + "attributes": { + "linkTarget": "_self", + "fontSize": "small" + }, + "innerBlocks": [] + } + ] + }, + { + "name": "core/comment-content", + "isValid": true, + "attributes": {}, + "innerBlocks": [] + }, + { + "name": "core/comment-reply-link", + "isValid": true, + "attributes": { + "fontSize": "small" + }, + "innerBlocks": [] + } + ] + } + ] + } + ] + }, + { + "name": "core/comments-pagination", + "isValid": true, + "attributes": { + "paginationArrow": "none" + }, + "innerBlocks": [ { - "name": "core/comment-content", + "name": "core/comments-pagination-previous", "isValid": true, "attributes": {}, "innerBlocks": [] }, { - "name": "core/comment-reply-link", + "name": "core/comments-pagination-numbers", "isValid": true, "attributes": {}, "innerBlocks": [] }, { - "name": "core/comment-edit-link", + "name": "core/comments-pagination-next", "isValid": true, - "attributes": { - "linkTarget": "_self" - }, + "attributes": {}, "innerBlocks": [] } ] diff --git a/test/integration/fixtures/blocks/core__comments-query-loop.parsed.json b/test/integration/fixtures/blocks/core__comments-query-loop.parsed.json index e674c73c1f6f57..a5f207fff8388a 100644 --- a/test/integration/fixtures/blocks/core__comments-query-loop.parsed.json +++ b/test/integration/fixtures/blocks/core__comments-query-loop.parsed.json @@ -8,49 +8,157 @@ "attrs": {}, "innerBlocks": [ { - "blockName": "core/comment-author-avatar", + "blockName": "core/columns", "attrs": {}, - "innerBlocks": [], - "innerHTML": "", - "innerContent": [] - }, - { - "blockName": "core/comment-author-name", - "attrs": {}, - "innerBlocks": [], - "innerHTML": "", - "innerContent": [] - }, - { - "blockName": "core/comment-date", - "attrs": {}, - "innerBlocks": [], - "innerHTML": "", - "innerContent": [] - }, + "innerBlocks": [ + { + "blockName": "core/column", + "attrs": { + "width": "40px" + }, + "innerBlocks": [ + { + "blockName": "core/avatar", + "attrs": { + "size": 40, + "style": { + "border": { + "radius": "20px" + } + } + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } + ], + "innerHTML": "\n\t\t
\n\t\t\t\n\t\t
\n\t\t", + "innerContent": [ + "\n\t\t
\n\t\t\t", + null, + "\n\t\t
\n\t\t" + ] + }, + { + "blockName": "core/column", + "attrs": {}, + "innerBlocks": [ + { + "blockName": "core/comment-author-name", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": "core/group", + "attrs": { + "style": { + "spacing": { + "margin": { + "top": "0px", + "bottom": "0px" + } + } + }, + "layout": { + "type": "flex" + } + }, + "innerBlocks": [ + { + "blockName": "core/comment-date", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": "core/comment-edit-link", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } + ], + "innerHTML": "\n\t\t\t
\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t
\n\t\t\t", + "innerContent": [ + "\n\t\t\t
\n\t\t\t\t", + null, + "\n\n\t\t\t\t", + null, + "\n\t\t\t
\n\t\t\t" + ] + }, + { + "blockName": "core/comment-content", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + }, + { + "blockName": "core/comment-reply-link", + "attrs": {}, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } + ], + "innerHTML": "\n\t\t
\n\t\t\t\n\n\t\t\t\n\n\t\t\t\n\n\t\t\t\n\t\t
\n\t\t", + "innerContent": [ + "\n\t\t
\n\t\t\t", + null, + "\n\n\t\t\t", + null, + "\n\n\t\t\t", + null, + "\n\n\t\t\t", + null, + "\n\t\t
\n\t\t" + ] + } + ], + "innerHTML": "\n\t
\n\t\t\n\n\t\t\n\t
\n\t", + "innerContent": [ + "\n\t
\n\t\t", + null, + "\n\n\t\t", + null, + "\n\t
\n\t" + ] + } + ], + "innerHTML": "\n\t\n\t", + "innerContent": [ "\n\t", null, "\n\t" ] + }, + { + "blockName": "core/comments-pagination", + "attrs": {}, + "innerBlocks": [ { - "blockName": "core/comment-content", + "blockName": "core/comments-pagination-previous", "attrs": {}, "innerBlocks": [], "innerHTML": "", "innerContent": [] }, { - "blockName": "core/comment-reply-link", + "blockName": "core/comments-pagination-numbers", "attrs": {}, "innerBlocks": [], "innerHTML": "", "innerContent": [] }, { - "blockName": "core/comment-edit-link", + "blockName": "core/comments-pagination-next", "attrs": {}, "innerBlocks": [], "innerHTML": "", "innerContent": [] } ], - "innerHTML": "\n\t\n\n\t\n\n\t\n\n\t\n\n\t\n\n\t\n\t", + "innerHTML": "\n\t\n\n\t\n\n\t\n\t", "innerContent": [ "\n\t", null, @@ -58,20 +166,16 @@ null, "\n\n\t", null, - "\n\n\t", - null, - "\n\n\t", - null, - "\n\n\t", - null, "\n\t" ] } ], - "innerHTML": "\n
\n\t\n
\n", + "innerHTML": "\n
\n\t\n\n\t\n
\n", "innerContent": [ "\n
\n\t", null, + "\n\n\t", + null, "\n
\n" ] } diff --git a/test/integration/fixtures/blocks/core__comments-query-loop.serialized.html b/test/integration/fixtures/blocks/core__comments-query-loop.serialized.html index 6496d12b1c37b4..014b6210aaa089 100644 --- a/test/integration/fixtures/blocks/core__comments-query-loop.serialized.html +++ b/test/integration/fixtures/blocks/core__comments-query-loop.serialized.html @@ -1,15 +1,31 @@
- + +
+
+ - + +
- + +
+ +
+ - +
+
+ + + + + + + - -
+ + From 6a05ccc9a5b842b6e375ba6c1afbba8397abb7db Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com> Date: Fri, 8 Apr 2022 11:50:57 +0200 Subject: [PATCH 24/31] Comments Query Loop: Fix pagination setting not being applied on frontend (#40146) * Fix comments not getting comment pagination enable/disable setting * Update unit tests and add no pagination unit test * Add true-false option --- lib/experimental/blocks.php | 37 +++++++++--------- .../experiments/blocks/comments-query.test.js | 39 +++++++++++++++++-- ...ss-block-library-comment-template-test.php | 28 +++++++++++++ 3 files changed, 83 insertions(+), 21 deletions(-) diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index 2ab396d83070c0..c7b8e86abe6837 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -35,24 +35,25 @@ function build_comment_query_vars_from_block( $block ) { $comment_args['hierarchical'] = false; } - $per_page = get_option( 'comments_per_page' ); - $default_page = get_option( 'default_comments_page' ); - - if ( $per_page > 0 ) { - $comment_args['number'] = $per_page; - - $page = (int) get_query_var( 'cpage' ); - if ( $page ) { - $comment_args['paged'] = $page; - } elseif ( 'oldest' === $default_page ) { - $comment_args['paged'] = 1; - } elseif ( 'newest' === $default_page ) { - $comment_args['paged'] = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages; - } - // Set the `cpage` query var to ensure the previous and next pagination links are correct - // when inheriting the Discussion Settings. - if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) { - set_query_var( 'cpage', $comment_args['paged'] ); + if ( get_option( 'page_comments' ) === '1' || get_option( 'page_comments' ) === true ) { + $per_page = get_option( 'comments_per_page' ); + $default_page = get_option( 'default_comments_page' ); + if ( $per_page > 0 ) { + $comment_args['number'] = $per_page; + + $page = (int) get_query_var( 'cpage' ); + if ( $page ) { + $comment_args['paged'] = $page; + } elseif ( 'oldest' === $default_page ) { + $comment_args['paged'] = 1; + } elseif ( 'newest' === $default_page ) { + $comment_args['paged'] = (int) ( new WP_Comment_Query( $comment_args ) )->max_num_pages; + } + // Set the `cpage` query var to ensure the previous and next pagination links are correct + // when inheriting the Discussion Settings. + if ( 0 === $page && isset( $comment_args['paged'] ) && $comment_args['paged'] > 0 ) { + set_query_var( 'cpage', $comment_args['paged'] ); + } } } diff --git a/packages/e2e-tests/specs/experiments/blocks/comments-query.test.js b/packages/e2e-tests/specs/experiments/blocks/comments-query.test.js index d3b8c880a998c1..1fab57ce5dc426 100644 --- a/packages/e2e-tests/specs/experiments/blocks/comments-query.test.js +++ b/packages/e2e-tests/specs/experiments/blocks/comments-query.test.js @@ -24,10 +24,8 @@ describe( 'Comment Query Loop', () => { 'newest' ); } ); - beforeEach( async () => { - await createNewPost(); - } ); it( 'Pagination links are working as expected', async () => { + await createNewPost(); // Insert the Query Comment Loop block. await insertBlock( 'Comments Query Loop' ); // Insert the Comment Loop form. @@ -88,6 +86,41 @@ describe( 'Comment Query Loop', () => { await page.$( '.wp-block-comments-pagination-next' ) ).not.toBeNull(); } ); + it( 'Pagination links are not appearing if break comments is not enabled', async () => { + await setOption( 'page_comments', '0' ); + await createNewPost(); + // Insert the Query Comment Loop block. + await insertBlock( 'Comments Query Loop' ); + // Insert the Comment Loop form. + await insertBlock( 'Post Comments Form' ); + await publishPost(); + // Visit the post that was just published. + await page.click( + '.post-publish-panel__postpublish-buttons .is-primary' + ); + + // Create three comments for that post. + for ( let i = 0; i < 3; i++ ) { + await page.waitForSelector( 'textarea#comment' ); + await page.click( 'textarea#comment' ); + await page.type( + `textarea#comment`, + `This is an automated comment - ${ i }` + ); + await pressKeyTimes( 'Tab', 1 ); + await Promise.all( [ + page.keyboard.press( 'Enter' ), + page.waitForNavigation( { waitUntil: 'networkidle0' } ), + ] ); + } + // We check that there are no comments page link. + expect( + await page.$( '.wp-block-comments-pagination-previous' ) + ).toBeNull(); + expect( + await page.$( '.wp-block-comments-pagination-next' ) + ).toBeNull(); + } ); afterAll( async () => { await trashAllComments(); await activateTheme( 'twentytwentyone' ); diff --git a/phpunit/class-block-library-comment-template-test.php b/phpunit/class-block-library-comment-template-test.php index 48a7e060c9ad89..486a3d7f2a1340 100644 --- a/phpunit/class-block-library-comment-template-test.php +++ b/phpunit/class-block-library-comment-template-test.php @@ -45,6 +45,34 @@ public function setUp() { ); } + function test_build_comment_query_vars_from_block_with_context_no_pagination() { + update_option( 'page_comments', false ); + $parsed_blocks = parse_blocks( + '' + ); + + $block = new WP_Block( + $parsed_blocks[0], + array( + 'postId' => self::$custom_post->ID, + ) + ); + + $this->assertEquals( + build_comment_query_vars_from_block( $block ), + array( + 'orderby' => 'comment_date_gmt', + 'order' => 'ASC', + 'status' => 'approve', + 'no_found_rows' => false, + 'update_comment_meta_cache' => false, + 'post_id' => self::$custom_post->ID, + 'hierarchical' => 'threaded', + ) + ); + update_option( 'page_comments', true ); + } + function test_build_comment_query_vars_from_block_with_context() { $parsed_blocks = parse_blocks( '' From 4e27b20c1352160e202a866835ec5112bdc04fe4 Mon Sep 17 00:00:00 2001 From: Justin Ahinon Date: Fri, 8 Apr 2022 12:31:33 +0200 Subject: [PATCH 25/31] Add vertical alignment to blocks using flex control (#40013) * Add vertical alignment to the row block * Update save.js * Update block.json * Revert "Update block.json" This reverts commit 4183f23c8fae538ea7d963cec853e0ced275145e. * Revert "Update save.js" This reverts commit c5bb0a9a1ce8ed6f58036b9f2b2a4d8a2bb55460. * Revert "Add vertical alignment to the row block" This reverts commit b44e93e7f103110b9994a8ce172d750be9ecaaec. * Add FlexLayoutVerticalAlignmentControl() to flex.js * Update flex.js * Update flex.js * Update flex.js * Update verticalAlignmentMap and add fallback for the vertical alignment * Update packages/block-editor/src/layouts/flex.js Co-authored-by: Nik Tsekouras * Add server side rendering option for vertical alignment * CS fix * Add allowVerticalAlignment switch & conditional rendering * CS fix Co-authored-by: Nik Tsekouras Co-authored-by: Ari Stathopoulos --- lib/block-supports/layout.php | 12 ++++ packages/block-editor/src/layouts/flex.js | 83 ++++++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index a04a2c4451c8ab..5b638099eeee19 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -83,6 +83,12 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support 'center' => 'center', ); + $vertical_alignment_options = array( + 'top' => 'flex-start', + 'center' => 'center', + 'bottom' => 'flex-end', + ); + if ( 'horizontal' === $layout_orientation ) { $justify_content_options += array( 'space-between' => 'space-between' ); } @@ -117,6 +123,12 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { $style .= "justify-content: {$justify_content_options[ $layout['justifyContent'] ]};"; } + + if ( ! empty( $layout['verticalAlignment'] ) && array_key_exists( $layout['verticalAlignment'], $vertical_alignment_options ) ) { + $style .= "align-items: {$vertical_alignment_options[ $layout['verticalAlignment'] ]};"; + } else { + $style .= 'align-items: center;'; + } } else { $style .= 'flex-direction: column;'; if ( ! empty( $layout['justifyContent'] ) && array_key_exists( $layout['justifyContent'], $justify_content_options ) ) { diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js index c7953ce25f5e37..e4fc39b702189d 100644 --- a/packages/block-editor/src/layouts/flex.js +++ b/packages/block-editor/src/layouts/flex.js @@ -18,7 +18,11 @@ import { Button, ToggleControl, Flex, FlexItem } from '@wordpress/components'; import { appendSelectors } from './utils'; import { getGapCSSValue } from '../hooks/gap'; import useSetting from '../components/use-setting'; -import { BlockControls, JustifyContentControl } from '../components'; +import { + BlockControls, + JustifyContentControl, + BlockVerticalAlignmentControl, +} from '../components'; import { shouldSkipSerialization } from '../hooks/utils'; // Used with the default, horizontal flex orientation. @@ -36,6 +40,12 @@ const alignItemsMap = { center: 'center', }; +const verticalAlignmentMap = { + top: 'flex-start', + center: 'center', + bottom: 'flex-end', +}; + const flexWrapOptions = [ 'wrap', 'nowrap' ]; export default { @@ -77,6 +87,7 @@ export default { if ( layoutBlockSupport?.allowSwitching ) { return null; } + const { allowVerticalAlignment = true } = layoutBlockSupport; return ( + { allowVerticalAlignment && + layout?.orientation !== 'vertical' && ( + + ) } ); }, @@ -104,9 +123,12 @@ export default { const flexWrap = flexWrapOptions.includes( layout.flexWrap ) ? layout.flexWrap : 'wrap'; + const verticalAlignment = + verticalAlignmentMap[ layout.verticalAlignment ] || + verticalAlignmentMap.center; const rowOrientation = ` flex-direction: row; - align-items: center; + align-items: ${ verticalAlignment }; justify-content: ${ justifyContent }; `; const alignItems = @@ -140,6 +162,63 @@ export default { }, }; +function FlexLayoutVerticalAlignmentControl( { + layout, + onChange, + isToolbar = false, +} ) { + const { verticalAlignment = verticalAlignmentMap.center } = layout; + + const onVerticalAlignmentChange = ( value ) => { + onChange( { + ...layout, + verticalAlignment: value, + } ); + }; + if ( isToolbar ) { + return ( + + ); + } + + const verticalAlignmentOptions = [ + { + value: 'flex-start', + label: __( 'Align items top' ), + }, + { + value: 'center', + label: __( 'Align items center' ), + }, + { + value: 'flex-end', + label: __( 'Align items bottom' ), + }, + ]; + + return ( +
+ { __( 'Vertical alignment' ) } +
+ { verticalAlignmentOptions.map( ( value, icon, label ) => { + return ( +
+
+ ); +} + function FlexLayoutJustifyContentControl( { layout, onChange, From 593336483d23c3023ea56e5321ef4d57934f162a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Fri, 8 Apr 2022 13:02:09 +0200 Subject: [PATCH 26/31] Plugin: Ensure that PHP code for blocks is correctly assigned to WP releases (#40179) --- .../block-api/block-metadata.md | 4 +- lib/block-supports/border.php | 4 +- lib/block-supports/dimensions.php | 2 +- lib/block-supports/layout.php | 4 +- lib/block-supports/spacing.php | 6 +- lib/blocks.php | 340 +----------------- lib/client-assets.php | 4 +- lib/compat/wordpress-5.9/blocks.php | 183 ++++++++++ lib/compat/wordpress-6.0/blocks.php | 104 ++++++ 9 files changed, 302 insertions(+), 349 deletions(-) diff --git a/docs/reference-guides/block-api/block-metadata.md b/docs/reference-guides/block-api/block-metadata.md index d4588cbbd21fa6..801a3f0fa47f66 100644 --- a/docs/reference-guides/block-api/block-metadata.md +++ b/docs/reference-guides/block-api/block-metadata.md @@ -435,7 +435,7 @@ Block type frontend and editor script definition. It will be enqueued both in th ### View Script -- Type: `WPDefinedAsset` ([learn more](#wpdefinedasset)) +- Type: `WPDefinedAsset`|`WPDefinedAsset[]` ([learn more](#wpdefinedasset)) - Optional - Localized: No - Property: `viewScript` @@ -447,6 +447,8 @@ Block type frontend and editor script definition. It will be enqueued both in th Block type frontend script definition. It will be enqueued only when viewing the content on the front of the site. +_Note: An option to pass also an array of view scripts exists since WordPress `6.0.0`._ + ### Editor Style - Type: `WPDefinedAsset`|`WPDefinedAsset[]` ([learn more](#wpdefinedasset)) diff --git a/lib/block-supports/border.php b/lib/block-supports/border.php index 0ea1b2e88f9b9e..5ff0260944df2b 100644 --- a/lib/block-supports/border.php +++ b/lib/block-supports/border.php @@ -13,7 +13,7 @@ */ function gutenberg_register_border_support( $block_type ) { // Determine if any border related features are supported. - $has_border_support = gutenberg_block_has_support( $block_type, array( '__experimentalBorder' ) ); + $has_border_support = block_has_support( $block_type, array( '__experimentalBorder' ) ); $has_border_color_support = gutenberg_has_border_feature_support( $block_type, 'color' ); // Setup attributes and styles within that if needed. @@ -161,7 +161,7 @@ function gutenberg_has_border_feature_support( $block_type, $feature, $default = // Check if the specific feature has been opted into individually // via nested flag under `__experimentalBorder`. - return gutenberg_block_has_support( $block_type, array( '__experimentalBorder', $feature ), $default ); + return block_has_support( $block_type, array( '__experimentalBorder', $feature ), $default ); } // Register the block support. diff --git a/lib/block-supports/dimensions.php b/lib/block-supports/dimensions.php index 9f6ac8defe9108..8fe5b82c3e7654 100644 --- a/lib/block-supports/dimensions.php +++ b/lib/block-supports/dimensions.php @@ -25,7 +25,7 @@ function gutenberg_register_dimensions_support( $block_type ) { return; } - $has_dimensions_support = gutenberg_block_has_support( $block_type, array( '__experimentalDimensions' ), false ); + $has_dimensions_support = block_has_support( $block_type, array( '__experimentalDimensions' ), false ); // Future block supports such as height & width will be added here. if ( $has_dimensions_support ) { diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 5b638099eeee19..d7d82d1a113bbb 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -11,7 +11,7 @@ * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_layout_support( $block_type ) { - $support_layout = gutenberg_block_has_support( $block_type, array( '__experimentalLayout' ), false ); + $support_layout = block_has_support( $block_type, array( '__experimentalLayout' ), false ); if ( $support_layout ) { if ( ! $block_type->attributes ) { $block_type->attributes = array(); @@ -154,7 +154,7 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support */ function gutenberg_render_layout_support_flag( $block_content, $block ) { $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); - $support_layout = gutenberg_block_has_support( $block_type, array( '__experimentalLayout' ), false ); + $support_layout = block_has_support( $block_type, array( '__experimentalLayout' ), false ); if ( ! $support_layout ) { return $block_content; diff --git a/lib/block-supports/spacing.php b/lib/block-supports/spacing.php index 6217be9f049646..86bb96598af446 100644 --- a/lib/block-supports/spacing.php +++ b/lib/block-supports/spacing.php @@ -15,7 +15,7 @@ * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_spacing_support( $block_type ) { - $has_spacing_support = gutenberg_block_has_support( $block_type, array( 'spacing' ), false ); + $has_spacing_support = block_has_support( $block_type, array( 'spacing' ), false ); // Setup attributes and styles within that if needed. if ( ! $block_type->attributes ) { @@ -44,8 +44,8 @@ function gutenberg_apply_spacing_support( $block_type, $block_attributes ) { } $attributes = array(); - $has_padding_support = gutenberg_block_has_support( $block_type, array( 'spacing', 'padding' ), false ); - $has_margin_support = gutenberg_block_has_support( $block_type, array( 'spacing', 'margin' ), false ); + $has_padding_support = block_has_support( $block_type, array( 'spacing', 'padding' ), false ); + $has_margin_support = block_has_support( $block_type, array( 'spacing', 'margin' ), false ); $block_styles = isset( $block_attributes['style'] ) ? $block_attributes['style'] : null; if ( ! $block_styles ) { diff --git a/lib/blocks.php b/lib/blocks.php index 8e2b25715f1099..41ad13c442411d 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -1,6 +1,6 @@ $src_result ) { - // Skip if this is an absolute URL. - if ( 0 === strpos( $src_result, 'http' ) || 0 === strpos( $src_result, '//' ) ) { - continue; - } - - // Build the absolute URL. - $absolute_url = dirname( $stylesheet_url ) . '/' . $src_result; - $absolute_url = str_replace( '/./', '/', $absolute_url ); - // Convert to URL related to the site root. - $relative_url = wp_make_link_relative( $absolute_url ); - - // Replace the URL in the CSS. - $css = str_replace( - $src_results[0][ $src_index ], - str_replace( $src_result, $relative_url, $src_results[0][ $src_index ] ), - $css - ); - } - } - - return $css; - } -} - /** * Complements the implementation of block type `core/social-icon`, whether it * be provided by core or the plugin, with derived block types for each @@ -325,7 +285,7 @@ function _wp_normalize_relative_css_links( $css, $stylesheet_url ) { * plugin who have used Social Links prior to their conversion to block * variations. * - * This shim is INTENTIONALLY left out of core, as Social Links haven't yet + * This shim is INTENTIONALLY left out of core, as Social Links have never * landed there. * * @see https://github.com/WordPress/gutenberg/pull/19887 @@ -397,299 +357,3 @@ function gutenberg_register_legacy_social_link_blocks() { } add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' ); - -/** - * Checks whether the current block type supports the feature requested. - * - * @param WP_Block_Type $block_type Block type to check for support. - * @param array $feature Path of the feature to check support for. - * @param mixed $default Fallback value for feature support, defaults to false. - * - * @return boolean Whether or not the feature is supported. - */ -function gutenberg_block_has_support( $block_type, $feature, $default = false ) { - $block_support = $default; - if ( $block_type && property_exists( $block_type, 'supports' ) ) { - $block_support = _wp_array_get( $block_type->supports, $feature, $default ); - } - - return true === $block_support || is_array( $block_support ); -} - -/** - * Updates the shape of supports for declaring fontSize and lineHeight. - * - * @param array $metadata Metadata for registering a block type. - * @return array Metadata for registering a block type with the supports shape updated. - */ -function gutenberg_migrate_old_typography_shape( $metadata ) { - // Temporarily disable migrations from core blocks to avoid warnings on versions older than 5.8. - if ( isset( $metadata['supports'] ) && false === strpos( $metadata['file'], '/wp-includes/blocks/' ) ) { - $typography_keys = array( - '__experimentalFontFamily', - '__experimentalFontStyle', - '__experimentalFontWeight', - '__experimentalLetterSpacing', - '__experimentalTextDecoration', - '__experimentalTextTransform', - 'fontSize', - 'lineHeight', - ); - foreach ( $typography_keys as $typography_key ) { - $support_for_key = _wp_array_get( $metadata['supports'], array( $typography_key ), null ); - if ( null !== $support_for_key ) { - trigger_error( - /* translators: %1$s: Block type, %2$s: typography supports key e.g: fontSize, lineHeight etc... */ - sprintf( __( 'Block %1$s is declaring %2$s support on block.json under supports.%2$s. %2$s support is now declared under supports.typography.%2$s.', 'gutenberg' ), $metadata['name'], $typography_key ), - headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE - ); - _wp_array_set( $metadata['supports'], array( 'typography', $typography_key ), $support_for_key ); - unset( $metadata['supports'][ $typography_key ] ); - } - } - } - return $metadata; -} - -if ( ! function_exists( 'wp_migrate_old_typography_shape' ) ) { - add_filter( 'block_type_metadata', 'gutenberg_migrate_old_typography_shape' ); -} - -if ( ! function_exists( 'wp_enqueue_block_style' ) ) { - /** - * Enqueue a stylesheet for a specific block. - * - * If the theme has opted-in to separate-styles loading, - * then the stylesheet will be enqueued on-render, - * otherwise when the block inits. - * - * @param string $block_name The block-name, including namespace. - * @param array $args An array of arguments [handle,src,deps,ver,media]. - * - * @return void - */ - function wp_enqueue_block_style( $block_name, $args ) { - $args = wp_parse_args( - $args, - array( - 'handle' => '', - 'src' => '', - 'deps' => array(), - 'ver' => false, - 'media' => 'all', - ) - ); - - /** - * Callback function to register and enqueue styles. - * - * @param string $content When the callback is used for the render_block filter, - * the content needs to be returned so the function parameter - * is to ensure the content exists. - * @return string Block content. - */ - $callback = static function( $content ) use ( $args ) { - // Register the stylesheet. - if ( ! empty( $args['src'] ) ) { - wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] ); - } - - // Add `path` data if provided. - if ( isset( $args['path'] ) ) { - wp_style_add_data( $args['handle'], 'path', $args['path'] ); - - // Get the RTL file path. - $rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] ); - - // Add RTL stylesheet. - if ( file_exists( $rtl_file_path ) ) { - wp_style_add_data( $args['handle'], 'rtl', 'replace' ); - - if ( is_rtl() ) { - wp_style_add_data( $args['handle'], 'path', $rtl_file_path ); - } - } - } - - // Enqueue the stylesheet. - wp_enqueue_style( $args['handle'] ); - - return $content; - }; - - $hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts'; - if ( wp_should_load_separate_core_block_assets() ) { - /** - * Callback function to register and enqueue styles. - * - * @param string $content The block content. - * @param array $block The full block, including name and attributes. - * @return string Block content. - */ - $callback_separate = static function( $content, $block ) use ( $block_name, $callback ) { - if ( ! empty( $block['blockName'] ) && $block_name === $block['blockName'] ) { - return $callback( $content ); - } - return $content; - }; - - /* - * The filter's callback here is an anonymous function because - * using a named function in this case is not possible. - * - * The function cannot be unhooked, however, users are still able - * to dequeue the stylesheets registered/enqueued by the callback - * which is why in this case, using an anonymous function - * was deemed acceptable. - */ - add_filter( 'render_block', $callback_separate, 10, 2 ); - return; - } - - /* - * The filter's callback here is an anonymous function because - * using a named function in this case is not possible. - * - * The function cannot be unhooked, however, users are still able - * to dequeue the stylesheets registered/enqueued by the callback - * which is why in this case, using an anonymous function - * was deemed acceptable. - */ - add_filter( $hook, $callback ); - - // Enqueue assets in the editor. - add_action( 'enqueue_block_assets', $callback ); - } -} - -/** - * Allow multiple block styles. - * - * @param array $metadata Metadata for registering a block type. - * - * @return array - */ -function gutenberg_multiple_block_styles( $metadata ) { - foreach ( array( 'style', 'editorStyle' ) as $key ) { - if ( ! empty( $metadata[ $key ] ) && is_array( $metadata[ $key ] ) ) { - $default_style = array_shift( $metadata[ $key ] ); - foreach ( $metadata[ $key ] as $handle ) { - $args = array( 'handle' => $handle ); - if ( 0 === strpos( $handle, 'file:' ) && isset( $metadata['file'] ) ) { - $style_path = remove_block_asset_path_prefix( $handle ); - $args = array( - 'handle' => sanitize_key( "{$metadata['name']}-{$style_path}" ), - 'src' => plugins_url( $style_path, $metadata['file'] ), - ); - } - - wp_enqueue_block_style( $metadata['name'], $args ); - } - - // Only return the 1st item in the array. - $metadata[ $key ] = $default_style; - } - } - return $metadata; -} -add_filter( 'block_type_metadata', 'gutenberg_multiple_block_styles' ); - -if ( ! function_exists( 'wp_enqueue_block_view_script' ) ) { - /** - * Enqueue a frontend script for a specific block. - * - * Scripts enqueued using this function will only get printed - * when the block gets rendered on the frontend. - * - * @param string $block_name The block-name, including namespace. - * @param array $args An array of arguments [handle,src,deps,ver,media]. - * - * @return void - */ - function wp_enqueue_block_view_script( $block_name, $args ) { - $args = wp_parse_args( - $args, - array( - 'handle' => '', - 'src' => '', - 'deps' => array(), - 'ver' => false, - 'in_footer' => false, - - // Additional arg to allow translations for the script's textdomain. - 'textdomain' => '', - ) - ); - - /** - * Callback function to register and enqueue scripts. - * - * @param string $content When the callback is used for the render_block filter, - * the content needs to be returned so the function parameter - * is to ensure the content exists. - * @return string Block content. - */ - $callback = static function( $content, $block ) use ( $args, $block_name ) { - - // Sanity check. - if ( empty( $block['blockName'] ) || $block_name !== $block['blockName'] ) { - return $content; - } - - // Register the stylesheet. - if ( ! empty( $args['src'] ) ) { - wp_register_script( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['in_footer'] ); - } - - // Enqueue the stylesheet. - wp_enqueue_script( $args['handle'] ); - - // If a textdomain is defined, use it to set the script translations. - if ( ! empty( $args['textdomain'] ) && in_array( 'wp-i18n', $args['deps'], true ) ) { - wp_set_script_translations( $args['handle'], $args['textdomain'] ); - } - - return $content; - }; - - /* - * The filter's callback here is an anonymous function because - * using a named function in this case is not possible. - * - * The function cannot be unhooked, however, users are still able - * to dequeue the script registered/enqueued by the callback - * which is why in this case, using an anonymous function - * was deemed acceptable. - */ - add_filter( 'render_block', $callback, 10, 2 ); - } -} - -/** - * Allow multiple view scripts per block. - * - * Filters the metadata provided for registering a block type. - * - * @param array $metadata Metadata for registering a block type. - * - * @return array - */ -function gutenberg_block_type_metadata_multiple_view_scripts( $metadata ) { - - // Early return if viewScript is empty, or not an array. - if ( ! isset( $metadata['viewScript'] ) || ! is_array( $metadata['viewScript'] ) ) { - return $metadata; - } - - // Register all viewScript items. - foreach ( $metadata['viewScript'] as $view_script ) { - $item_metadata = $metadata; - $item_metadata['viewScript'] = $view_script; - gutenberg_block_type_metadata_view_script( array(), $item_metadata ); - } - - // Proceed with the default behavior. - $metadata['viewScript'] = $metadata['viewScript'][0]; - return $metadata; -} -add_filter( 'block_type_metadata', 'gutenberg_block_type_metadata_multiple_view_scripts' ); diff --git a/lib/client-assets.php b/lib/client-assets.php index a30cae47dc3dd6..b48731fe724f7b 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -1,7 +1,7 @@ $src_result ) { + // Skip if this is an absolute URL. + if ( 0 === strpos( $src_result, 'http' ) || 0 === strpos( $src_result, '//' ) ) { + continue; + } + + // Build the absolute URL. + $absolute_url = dirname( $stylesheet_url ) . '/' . $src_result; + $absolute_url = str_replace( '/./', '/', $absolute_url ); + // Convert to URL related to the site root. + $relative_url = wp_make_link_relative( $absolute_url ); + + // Replace the URL in the CSS. + $css = str_replace( + $src_results[0][ $src_index ], + str_replace( $src_result, $relative_url, $src_results[0][ $src_index ] ), + $css + ); + } + } + + return $css; + } +} + +if ( ! function_exists( 'wp_enqueue_block_style' ) ) { + /** + * Enqueue a stylesheet for a specific block. + * + * If the theme has opted-in to separate-styles loading, + * then the stylesheet will be enqueued on-render, + * otherwise when the block inits. + * + * @since 5.9.0 + * + * @param string $block_name The block-name, including namespace. + * @param array $args An array of arguments [handle,src,deps,ver,media]. + * + * @return void + */ + function wp_enqueue_block_style( $block_name, $args ) { + $args = wp_parse_args( + $args, + array( + 'handle' => '', + 'src' => '', + 'deps' => array(), + 'ver' => false, + 'media' => 'all', + ) + ); + + /** + * Callback function to register and enqueue styles. + * + * @param string $content When the callback is used for the render_block filter, + * the content needs to be returned so the function parameter + * is to ensure the content exists. + * @return string Block content. + */ + $callback = static function( $content ) use ( $args ) { + // Register the stylesheet. + if ( ! empty( $args['src'] ) ) { + wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] ); + } + + // Add `path` data if provided. + if ( isset( $args['path'] ) ) { + wp_style_add_data( $args['handle'], 'path', $args['path'] ); + + // Get the RTL file path. + $rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] ); + + // Add RTL stylesheet. + if ( file_exists( $rtl_file_path ) ) { + wp_style_add_data( $args['handle'], 'rtl', 'replace' ); + + if ( is_rtl() ) { + wp_style_add_data( $args['handle'], 'path', $rtl_file_path ); + } + } + } + + // Enqueue the stylesheet. + wp_enqueue_style( $args['handle'] ); + + return $content; + }; + + $hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts'; + if ( wp_should_load_separate_core_block_assets() ) { + /** + * Callback function to register and enqueue styles. + * + * @param string $content The block content. + * @param array $block The full block, including name and attributes. + * @return string Block content. + */ + $callback_separate = static function( $content, $block ) use ( $block_name, $callback ) { + if ( ! empty( $block['blockName'] ) && $block_name === $block['blockName'] ) { + return $callback( $content ); + } + return $content; + }; + + /* + * The filter's callback here is an anonymous function because + * using a named function in this case is not possible. + * + * The function cannot be unhooked, however, users are still able + * to dequeue the stylesheets registered/enqueued by the callback + * which is why in this case, using an anonymous function + * was deemed acceptable. + */ + add_filter( 'render_block', $callback_separate, 10, 2 ); + return; + } + + /* + * The filter's callback here is an anonymous function because + * using a named function in this case is not possible. + * + * The function cannot be unhooked, however, users are still able + * to dequeue the stylesheets registered/enqueued by the callback + * which is why in this case, using an anonymous function + * was deemed acceptable. + */ + add_filter( $hook, $callback ); + + // Enqueue assets in the editor. + add_action( 'enqueue_block_assets', $callback ); + } +} + +/** + * Allow multiple block styles. + * + * @since 5.9.0 + * + * @param array $metadata Metadata for registering a block type. + * + * @return array + */ +function gutenberg_multiple_block_styles( $metadata ) { + foreach ( array( 'style', 'editorStyle' ) as $key ) { + if ( ! empty( $metadata[ $key ] ) && is_array( $metadata[ $key ] ) ) { + $default_style = array_shift( $metadata[ $key ] ); + foreach ( $metadata[ $key ] as $handle ) { + $args = array( 'handle' => $handle ); + if ( 0 === strpos( $handle, 'file:' ) && isset( $metadata['file'] ) ) { + $style_path = remove_block_asset_path_prefix( $handle ); + $args = array( + 'handle' => sanitize_key( "{$metadata['name']}-{$style_path}" ), + 'src' => plugins_url( $style_path, $metadata['file'] ), + ); + } + + wp_enqueue_block_style( $metadata['name'], $args ); + } + + // Only return the 1st item in the array. + $metadata[ $key ] = $default_style; + } + } + return $metadata; +} +add_filter( 'block_type_metadata', 'gutenberg_multiple_block_styles' ); diff --git a/lib/compat/wordpress-6.0/blocks.php b/lib/compat/wordpress-6.0/blocks.php index 842dd50b7f6107..ab4a9d0dbd7faa 100644 --- a/lib/compat/wordpress-6.0/blocks.php +++ b/lib/compat/wordpress-6.0/blocks.php @@ -173,3 +173,107 @@ function gutenberg_block_type_metadata_view_script( $settings, $metadata ) { return $settings; } add_filter( 'block_type_metadata_settings', 'gutenberg_block_type_metadata_view_script', 10, 2 ); + +if ( ! function_exists( 'wp_enqueue_block_view_script' ) ) { + /** + * Enqueues a frontend script for a specific block. + * + * Scripts enqueued using this function will only get printed + * when the block gets rendered on the frontend. + * + * @since 6.0.0 + * + * @param string $block_name The block name, including namespace. + * @param array $args An array of arguments [handle,src,deps,ver,media,textdomain]. + * + * @return void + */ + function wp_enqueue_block_view_script( $block_name, $args ) { + $args = wp_parse_args( + $args, + array( + 'handle' => '', + 'src' => '', + 'deps' => array(), + 'ver' => false, + 'in_footer' => false, + + // Additional arg to allow translations for the script's textdomain. + 'textdomain' => '', + ) + ); + + /** + * Callback function to register and enqueue scripts. + * + * @param string $content When the callback is used for the render_block filter, + * the content needs to be returned so the function parameter + * is to ensure the content exists. + * @return string Block content. + */ + $callback = static function( $content, $block ) use ( $args, $block_name ) { + + // Sanity check. + if ( empty( $block['blockName'] ) || $block_name !== $block['blockName'] ) { + return $content; + } + + // Register the stylesheet. + if ( ! empty( $args['src'] ) ) { + wp_register_script( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['in_footer'] ); + } + + // Enqueue the stylesheet. + wp_enqueue_script( $args['handle'] ); + + // If a textdomain is defined, use it to set the script translations. + if ( ! empty( $args['textdomain'] ) && in_array( 'wp-i18n', $args['deps'], true ) ) { + wp_set_script_translations( $args['handle'], $args['textdomain'] ); + } + + return $content; + }; + + /* + * The filter's callback here is an anonymous function because + * using a named function in this case is not possible. + * + * The function cannot be unhooked, however, users are still able + * to dequeue the script registered/enqueued by the callback + * which is why in this case, using an anonymous function + * was deemed acceptable. + */ + add_filter( 'render_block', $callback, 10, 2 ); + } +} + +/** + * Allow multiple view scripts per block. + * + * Filters the metadata provided for registering a block type. + * + * @since 6.0.0 + * + * @param array $metadata Metadata for registering a block type. + * + * @return array + */ +function gutenberg_block_type_metadata_multiple_view_scripts( $metadata ) { + + // Early return if viewScript is empty, or not an array. + if ( ! isset( $metadata['viewScript'] ) || ! is_array( $metadata['viewScript'] ) ) { + return $metadata; + } + + // Register all viewScript items. + foreach ( $metadata['viewScript'] as $view_script ) { + $item_metadata = $metadata; + $item_metadata['viewScript'] = $view_script; + gutenberg_block_type_metadata_view_script( array(), $item_metadata ); + } + + // Proceed with the default behavior. + $metadata['viewScript'] = $metadata['viewScript'][0]; + return $metadata; +} +add_filter( 'block_type_metadata', 'gutenberg_block_type_metadata_multiple_view_scripts' ); From fd66bf8db249dfb22027834a7636fb1762d394a1 Mon Sep 17 00:00:00 2001 From: Joen A <1204802+jasmussen@users.noreply.github.com> Date: Fri, 8 Apr 2022 13:22:32 +0200 Subject: [PATCH 27/31] New Group, Stack, Row block descriptions. (#40176) * Try: New Group, Stack, Row block descriptions. * Adjust wording. --- docs/reference-guides/core-blocks.md | 2 +- packages/block-library/src/group/block.json | 2 +- packages/block-library/src/group/variations.js | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index fef3d7540cfe84..0bae5b5e25af9f 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -262,7 +262,7 @@ Display multiple images in a rich gallery. ([Source](https://github.com/WordPres ## Group -Combine blocks into a group. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/group)) +Gather blocks in a layout container. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/group)) - **Name:** core/group - **Category:** design diff --git a/packages/block-library/src/group/block.json b/packages/block-library/src/group/block.json index bd79422311dbb3..2e3a99f78ae3c4 100644 --- a/packages/block-library/src/group/block.json +++ b/packages/block-library/src/group/block.json @@ -4,7 +4,7 @@ "name": "core/group", "title": "Group", "category": "design", - "description": "Combine blocks into a group.", + "description": "Gather blocks in a layout container.", "keywords": [ "container", "wrapper", "row", "section" ], "textdomain": "default", "attributes": { diff --git a/packages/block-library/src/group/variations.js b/packages/block-library/src/group/variations.js index 3797a515c458f8..7ed4477cec0672 100644 --- a/packages/block-library/src/group/variations.js +++ b/packages/block-library/src/group/variations.js @@ -8,7 +8,7 @@ const variations = [ { name: 'group', title: __( 'Group' ), - description: __( 'Blocks shown in a column.' ), + description: __( 'Gather blocks in a layout container.' ), attributes: { layout: { type: 'default' } }, scope: [ 'transform' ], isActive: ( blockAttributes ) => @@ -20,7 +20,7 @@ const variations = [ { name: 'group-row', title: __( 'Row' ), - description: __( 'Blocks shown in a row.' ), + description: __( 'Arrange blocks horizontally.' ), attributes: { layout: { type: 'flex', flexWrap: 'nowrap' } }, scope: [ 'inserter', 'transform' ], isActive: ( blockAttributes ) => @@ -32,7 +32,7 @@ const variations = [ { name: 'group-stack', title: __( 'Stack' ), - description: __( 'Blocks stacked vertically.' ), + description: __( 'Arrange blocks vertically.' ), attributes: { layout: { type: 'flex', orientation: 'vertical' } }, scope: [ 'inserter', 'transform' ], isActive: ( blockAttributes ) => From 8ebacc194a7d8a389b0ed815e3edc6d073582438 Mon Sep 17 00:00:00 2001 From: Justin Ahinon Date: Fri, 8 Apr 2022 13:37:28 +0200 Subject: [PATCH 28/31] Fix collapsing issue with the URL input suggestion container (#40147) * Fix collapsing issue with the URL input suggestion container * Update url input suggestion container --- packages/block-editor/src/components/url-input/style.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/url-input/style.scss b/packages/block-editor/src/components/url-input/style.scss index ba28441c3f7bf2..1942875fb21e3a 100644 --- a/packages/block-editor/src/components/url-input/style.scss +++ b/packages/block-editor/src/components/url-input/style.scss @@ -71,12 +71,13 @@ $input-size: 300px; .block-editor-url-input .components-spinner { display: none; @include break-small() { - display: inherit; + display: grid; } } .block-editor-url-input__suggestion { - padding: 4px $input-padding; + min-height: $button-size; + height: auto; color: $gray-700; display: block; font-size: $default-font-size; From d14832c144d45248c78b0132e18b41a166f4ff1f Mon Sep 17 00:00:00 2001 From: Ari Stathopoulos Date: Fri, 8 Apr 2022 14:41:02 +0300 Subject: [PATCH 29/31] Register webfonts defined in styles variations (#39886) * parse variations webfonts * Use merged_data to get user settings too * Add webfonts from all variations when in the editor. * We are now required to enqueue the webfont --- .../register-webfonts-from-theme-json.php | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/experimental/register-webfonts-from-theme-json.php b/lib/experimental/register-webfonts-from-theme-json.php index 3282a9eaebbc03..8d6701e418a2ef 100644 --- a/lib/experimental/register-webfonts-from-theme-json.php +++ b/lib/experimental/register-webfonts-from-theme-json.php @@ -9,18 +9,43 @@ * Register webfonts defined in theme.json. */ function gutenberg_register_webfonts_from_theme_json() { - // Get settings from theme.json. - $theme_settings = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_settings(); + // Get settings. + $settings = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->get_settings(); + + // If in the editor, add webfonts defined in variations. + if ( is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) { + $variations = WP_Theme_JSON_Resolver_Gutenberg::get_style_variations(); + + foreach ( $variations as $variation ) { + + // Sanity check: Skip if fontFamilies are not defined in the variation. + if ( + empty( $variation['settings'] ) || + empty( $variation['settings']['typography'] ) || + empty( $variation['settings']['typography']['fontFamilies'] ) + ) { + continue; + } + + // Merge the variation settings with the global settings. + $settings['typography'] = empty( $settings['typography'] ) ? array() : $settings['typography']; + $settings['typography']['fontFamilies'] = empty( $settings['typography']['fontFamilies'] ) ? array() : $settings['typography']['fontFamilies']; + $settings['typography']['fontFamilies'] = array_merge( $settings['typography']['fontFamilies'], $variation['settings']['typography']['fontFamilies'] ); + + // Make sure there are no duplicates. + $settings['typography']['fontFamilies'] = array_unique( $settings['typography']['fontFamilies'] ); + } + } // Bail out early if there are no settings for webfonts. - if ( empty( $theme_settings['typography'] ) || empty( $theme_settings['typography']['fontFamilies'] ) ) { + if ( empty( $settings['typography'] ) || empty( $settings['typography']['fontFamilies'] ) ) { return; } $webfonts = array(); // Look for fontFamilies. - foreach ( $theme_settings['typography']['fontFamilies'] as $font_families ) { + foreach ( $settings['typography']['fontFamilies'] as $font_families ) { foreach ( $font_families as $font_family ) { // Skip if fontFace is not defined. @@ -64,6 +89,7 @@ function gutenberg_register_webfonts_from_theme_json() { } foreach ( $webfonts as $webfont ) { wp_webfonts()->register_webfont( $webfont ); + wp_webfonts()->enqueue_webfont( $webfont['font-family'] ); } } From 9c7b2060734ef6faf41116c4fd42c259b5d53a58 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Fri, 8 Apr 2022 14:13:58 +0200 Subject: [PATCH 30/31] Disable vertical alignment controls for navigation and social links block (#40182) --- packages/block-library/src/navigation/block.json | 1 + packages/block-library/src/social-links/block.json | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/block-library/src/navigation/block.json b/packages/block-library/src/navigation/block.json index dc904dc25eee1e..c38ddf0cff789d 100644 --- a/packages/block-library/src/navigation/block.json +++ b/packages/block-library/src/navigation/block.json @@ -111,6 +111,7 @@ "__experimentalLayout": { "allowSwitching": false, "allowInheriting": false, + "allowVerticalAlignment": false, "default": { "type": "flex" } diff --git a/packages/block-library/src/social-links/block.json b/packages/block-library/src/social-links/block.json index 8382d874bc5675..8dd7df80a25253 100644 --- a/packages/block-library/src/social-links/block.json +++ b/packages/block-library/src/social-links/block.json @@ -51,6 +51,7 @@ "__experimentalLayout": { "allowSwitching": false, "allowInheriting": false, + "allowVerticalAlignment": false, "default": { "type": "flex" } From 740445e7009f6d7bba820aa843420c0a50e7973d Mon Sep 17 00:00:00 2001 From: Carlos Bravo <37012961+c4rl0sbr4v0@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:58:36 +0200 Subject: [PATCH 31/31] Add Avatar to theme schema (#40189) --- schemas/json/theme.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/schemas/json/theme.json b/schemas/json/theme.json index f2d8419f281db2..ce276b8f330474 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -376,6 +376,9 @@ "core/audio": { "$ref": "#/definitions/settingsPropertiesComplete" }, + "core/avatar": { + "$ref": "#/definitions/settingsPropertiesComplete" + }, "core/block": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -429,9 +432,6 @@ "core/comment-author-name": { "$ref": "#/definitions/settingsPropertiesComplete" }, - "core/comment-author-avatar": { - "$ref": "#/definitions/settingsPropertiesComplete" - }, "core/comment-content": { "$ref": "#/definitions/settingsPropertiesComplete" }, @@ -864,6 +864,9 @@ "core/audio": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, + "core/avatar": { + "$ref": "#/definitions/stylesPropertiesAndElementsComplete" + }, "core/block": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, @@ -891,9 +894,6 @@ "core/comment-author-name": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" }, - "core/comment-author-avatar": { - "$ref": "#/definitions/stylesPropertiesAndElementsComplete" - }, "core/comment-content": { "$ref": "#/definitions/stylesPropertiesAndElementsComplete" },