diff --git a/docs/how-to-guides/widgets/legacy-widget-block.md b/docs/how-to-guides/widgets/legacy-widget-block.md index c32136a10e4fef..21d22561d1509c 100644 --- a/docs/how-to-guides/widgets/legacy-widget-block.md +++ b/docs/how-to-guides/widgets/legacy-widget-block.md @@ -123,3 +123,49 @@ function hide_example_widget( $widget_types ) { } add_filter( 'widget_types_to_hide_from_legacy_widget_block', 'hide_example_widget' ); ``` + +## Using the Legacy Widget block in other block editors (Advanced) + +You may optionally allow the Legacy Widget block in other block editors such as +the WordPress post editor. This is not enabled by default. + +First, ensure that any styles and scripts required by the legacy widgets are +loaded onto the page. A convenient way of doing this is to manually perform all +of the hooks that ordinarily run when a user browses to the widgets WP Admin +screen. + +```php +add_action( 'admin_print_styles', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'admin_print_styles-widgets.php' ); + } +} ); +add_action( 'admin_print_scripts', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'load-widgets.php' ); + do_action( 'widgets.php' ); + do_action( 'sidebar_admin_setup' ); + do_action( 'admin_print_scripts-widgets.php' ); + } +} ); +add_action( 'admin_print_footer_scripts', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'admin_print_footer_scripts-widgets.php' ); + } +} ); +add_action( 'admin_footer', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'admin_footer-widgets.php' ); + } +} ); +``` + +Then, register the Legacy Widget block using `registerLegacyWidgetBlock` which +is defined in the `@wordpress/widgets` package. + +```php +add_action( 'enqueue_block_editor_assets', function() { + wp_enqueue_script( 'wp-widgets' ); + wp_add_inline_script( 'wp-widgets', 'wp.widgets.registerLegacyWidgetBlock()' ); +} ); +``` diff --git a/lib/blocks.php b/lib/blocks.php index 8f838b24fff7fb..e6e2968a23fd4d 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -5,24 +5,6 @@ * @package gutenberg */ -/* - * Fixes the priority of register_block_core_legacy_widget(). - * - * This hook was incorrectly added to Core with priority 20. #32300 fixes this - * but causes block registration warnings in the Gutenberg plugin until the - * changes are made in Core. - * - * This temporary fix can be removed after the changes to - * @wordpress/block-library in #32300 have been published to npm and updated in - * Core. - * - * See https://github.com/WordPress/gutenberg/pull/32300. - */ -if ( 20 === has_action( 'init', 'register_block_core_legacy_widget' ) ) { - remove_action( 'init', 'register_block_core_legacy_widget', 20 ); - add_action( 'init', 'register_block_core_legacy_widget', 10 ); -} - /** * Substitutes the implementation of a core-registered block type, if exists, * with the built result from the plugin. @@ -74,7 +56,6 @@ function gutenberg_reregister_core_block_types() { 'file.php' => 'core/file', 'latest-comments.php' => 'core/latest-comments', 'latest-posts.php' => 'core/latest-posts', - 'legacy-widget.php' => 'core/legacy-widget', 'loginout.php' => 'core/loginout', 'navigation.php' => 'core/navigation', 'navigation-link.php' => 'core/navigation-link', @@ -120,8 +101,14 @@ function gutenberg_reregister_core_block_types() { 'block_folders' => array( 'widget-area', ), + 'block_names' => array(), + ), + __DIR__ . '/../build/widgets/blocks/' => array( + 'block_folders' => array( + 'legacy-widget', + ), 'block_names' => array( - 'widget-area.php' => 'core/widget-area', + 'legacy-widget.php' => 'core/legacy-widget', ), ), ); diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 16c41bb15b160a..236afa3ef15999 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -34,6 +34,12 @@ class WP_Theme_JSON_Gutenberg { */ const ROOT_BLOCK_SELECTOR = 'body'; + const VALID_ORIGINS = array( + 'core', + 'theme', + 'user', + ); + const VALID_TOP_LEVEL_KEYS = array( 'customTemplates', 'templateParts', @@ -272,9 +278,14 @@ class WP_Theme_JSON_Gutenberg { /** * Constructor. * - * @param array $theme_json A structure that follows the theme.json schema. + * @param array $theme_json A structure that follows the theme.json schema. + * @param string $origin What source of data this object represents. One of core, theme, or user. Default: theme. */ - public function __construct( $theme_json = array() ) { + public function __construct( $theme_json = array(), $origin = 'theme' ) { + if ( ! in_array( $origin, self::VALID_ORIGINS, true ) ) { + $origin = 'theme'; + } + // The old format is not meant to be ported to core. // We can remove it at that point. if ( ! isset( $theme_json['version'] ) || 0 === $theme_json['version'] ) { @@ -284,6 +295,18 @@ public function __construct( $theme_json = array() ) { $valid_block_names = array_keys( self::get_blocks_metadata() ); $valid_element_names = array_keys( self::ELEMENTS ); $this->theme_json = self::sanitize( $theme_json, $valid_block_names, $valid_element_names ); + + // Internally, presets are keyed by origin. + $nodes = self::get_setting_nodes( $this->theme_json ); + foreach ( $nodes as $node ) { + foreach ( self::PRESETS_METADATA as $preset ) { + $path = array_merge( $node['path'], $preset['path'] ); + $preset = _wp_array_get( $this->theme_json, $path, array() ); + if ( ! empty( $preset ) ) { + gutenberg_experimental_set( $this->theme_json, $path, array( $origin => $preset ) ); + } + } + } } /** @@ -641,6 +664,33 @@ private static function append_to_selector( $selector, $to_append ) { return implode( ',', $new_selectors ); } + /** + * Function that given an array of presets keyed by origin + * and the value key of the preset returns an array where each key is + * the a preset slug and each value is the preset value. + * + * @param array $preset_per_origin Array of presets keyed by origin. + * @param string $value_key The property of the preset that contains its value. + * + * @return array Array of presets where each key is a slug and each value is the preset value. + */ + private static function get_merged_preset_by_slug( $preset_per_origin, $value_key ) { + $result = array(); + foreach ( self::VALID_ORIGINS as $origin ) { + if ( ! isset( $preset_per_origin[ $origin ] ) ) { + continue; + } + foreach ( $preset_per_origin[ $origin ] as $preset ) { + // We don't want to use kebabCase here, + // see https://github.com/WordPress/gutenberg/issues/32347 + // However, we need to make sure the generated class or css variable + // doesn't contain spaces. + $result[ preg_replace( '/\s+/', '-', $preset['slug'] ) ] = $preset[ $value_key ]; + } + } + return $result; + } + /** * Given a settings array, it returns the generated rulesets * for the preset classes. @@ -659,19 +709,16 @@ private static function compute_preset_classes( $settings, $selector ) { $stylesheet = ''; foreach ( self::PRESETS_METADATA as $preset ) { - $values = _wp_array_get( $settings, $preset['path'], array() ); - foreach ( $values as $value ) { - foreach ( $preset['classes'] as $class ) { + $preset_per_origin = _wp_array_get( $settings, $preset['path'], array() ); + $preset_by_slug = self::get_merged_preset_by_slug( $preset_per_origin, $preset['value_key'] ); + foreach ( $preset['classes'] as $class ) { + foreach ( $preset_by_slug as $slug => $value ) { $stylesheet .= self::to_ruleset( - // We don't want to use kebabCase here, - // see https://github.com/WordPress/gutenberg/issues/32347 - // However, we need to make sure the generated class - // doesn't contain spaces. - self::append_to_selector( $selector, '.has-' . preg_replace( '/\s+/', '-', $value['slug'] ) . '-' . $class['class_suffix'] ), + self::append_to_selector( $selector, '.has-' . $slug . '-' . $class['class_suffix'] ), array( array( 'name' => $class['property_name'], - 'value' => $value[ $preset['value_key'] ] . ' !important', + 'value' => $value . ' !important', ), ) ); @@ -701,11 +748,12 @@ private static function compute_preset_classes( $settings, $selector ) { private static function compute_preset_vars( $settings ) { $declarations = array(); foreach ( self::PRESETS_METADATA as $preset ) { - $values = _wp_array_get( $settings, $preset['path'], array() ); - foreach ( $values as $value ) { + $preset_per_origin = _wp_array_get( $settings, $preset['path'], array() ); + $preset_by_slug = self::get_merged_preset_by_slug( $preset_per_origin, $preset['value_key'] ); + foreach ( $preset_by_slug as $slug => $value ) { $declarations[] = array( - 'name' => '--wp--preset--' . $preset['css_var_infix'] . '--' . $value['slug'], - 'value' => $value[ $preset['value_key'] ], + 'name' => '--wp--preset--' . $preset['css_var_infix'] . '--' . $slug, + 'value' => $value, ); } } @@ -1101,86 +1149,35 @@ public function get_stylesheet( $type = 'all' ) { /** * Merge new incoming data. * - * @param WP_Theme_JSON_Gutenberg $incoming Data to merge. - * @param string $update_or_remove Whether update or remove existing colors - * for which the incoming data has a duplicated slug. + * @param WP_Theme_JSON $incoming Data to merge. */ - public function merge( $incoming, $update_or_remove = 'remove' ) { - $incoming_data = $incoming->get_raw_data(); - $existing_data = $this->theme_json; + public function merge( $incoming ) { + $incoming_data = $incoming->get_raw_data(); + $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); // The array_replace_recursive algorithm merges at the leaf level. // For leaf values that are arrays it will use the numeric indexes for replacement. - $this->theme_json = array_replace_recursive( $this->theme_json, $incoming_data ); - - // There are a few cases in which we want to merge things differently - // from what array_replace_recursive does. - - // Some incoming properties should replace the existing. + // In those cases, we want to replace the existing with the incoming value, if it exists. $to_replace = array(); $to_replace[] = array( 'custom' ); $to_replace[] = array( 'spacing', 'units' ); - $to_replace[] = array( 'typography', 'fontSizes' ); - $to_replace[] = array( 'typography', 'fontFamilies' ); - - // Some others should be appended to the existing. - // If the slug is the same than an existing element, - // the $update_or_remove param is used to decide - // what to do with the existing element: - // either remove it and append the incoming, - // or update it with the incoming. - $to_append = array(); - $to_append[] = array( 'color', 'duotone' ); - $to_append[] = array( 'color', 'gradients' ); - $to_append[] = array( 'color', 'palette' ); + $to_replace[] = array( 'color', 'duotone' ); + foreach ( self::VALID_ORIGINS as $origin ) { + $to_replace[] = array( 'color', 'palette', $origin ); + $to_replace[] = array( 'color', 'gradients', $origin ); + $to_replace[] = array( 'typography', 'fontSizes', $origin ); + $to_replace[] = array( 'typography', 'fontFamilies', $origin ); + } $nodes = self::get_setting_nodes( $this->theme_json ); foreach ( $nodes as $metadata ) { - foreach ( $to_replace as $path_to_replace ) { - $path = array_merge( $metadata['path'], $path_to_replace ); + foreach ( $to_replace as $property_path ) { + $path = array_merge( $metadata['path'], $property_path ); $node = _wp_array_get( $incoming_data, $path, array() ); if ( ! empty( $node ) ) { gutenberg_experimental_set( $this->theme_json, $path, $node ); } } - foreach ( $to_append as $path_to_append ) { - $path = array_merge( $metadata['path'], $path_to_append ); - $incoming_node = _wp_array_get( $incoming_data, $path, array() ); - $existing_node = _wp_array_get( $existing_data, $path, array() ); - - if ( empty( $incoming_node ) && empty( $existing_node ) ) { - continue; - } - - $index_table = array(); - $existing_slugs = array(); - $merged = array(); - foreach ( $existing_node as $key => $value ) { - $index_table[ $value['slug'] ] = $key; - $existing_slugs[] = $value['slug']; - $merged[ $key ] = $value; - } - - $to_remove = array(); - foreach ( $incoming_node as $value ) { - if ( ! in_array( $value['slug'], $existing_slugs, true ) ) { - $merged[] = $value; - } elseif ( 'update' === $update_or_remove ) { - $merged[ $index_table[ $value['slug'] ] ] = $value; - } else { - $merged[] = $value; - $to_remove[] = $index_table[ $value['slug'] ]; - } - } - - // Remove the duplicated values and pack the sparsed array. - foreach ( $to_remove as $index ) { - unset( $merged[ $index ] ); - } - $merged = array_values( $merged ); - - gutenberg_experimental_set( $this->theme_json, $path, $merged ); - } } } @@ -1277,14 +1274,26 @@ private static function is_safe_css_declaration( $property_name, $property_value /** * Removes insecure data from theme.json. + * + * @param array $theme_json Structure to sanitize. + * + * @return array Sanitized structure. */ - public function remove_insecure_properties() { + public static function remove_insecure_properties( $theme_json ) { $sanitized = array(); + if ( ! isset( $theme_json['version'] ) || 0 === $theme_json['version'] ) { + $theme_json = WP_Theme_JSON_Schema_V0::parse( $theme_json ); + } + + $valid_block_names = array_keys( self::get_blocks_metadata() ); + $valid_element_names = array_keys( self::ELEMENTS ); + $theme_json = self::sanitize( $theme_json, $valid_block_names, $valid_element_names ); + $blocks_metadata = self::get_blocks_metadata(); - $style_nodes = self::get_style_nodes( $this->theme_json, $blocks_metadata ); + $style_nodes = self::get_style_nodes( $theme_json, $blocks_metadata ); foreach ( $style_nodes as $metadata ) { - $input = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + $input = _wp_array_get( $theme_json, $metadata['path'], array() ); if ( empty( $input ) ) { continue; } @@ -1295,9 +1304,9 @@ public function remove_insecure_properties() { } } - $setting_nodes = self::get_setting_nodes( $this->theme_json ); + $setting_nodes = self::get_setting_nodes( $theme_json ); foreach ( $setting_nodes as $metadata ) { - $input = _wp_array_get( $this->theme_json, $metadata['path'], array() ); + $input = _wp_array_get( $theme_json, $metadata['path'], array() ); if ( empty( $input ) ) { continue; } @@ -1309,17 +1318,18 @@ public function remove_insecure_properties() { } if ( empty( $sanitized['styles'] ) ) { - unset( $this->theme_json['styles'] ); + unset( $theme_json['styles'] ); } else { - $this->theme_json['styles'] = $sanitized['styles']; + $theme_json['styles'] = $sanitized['styles']; } if ( empty( $sanitized['settings'] ) ) { - unset( $this->theme_json['settings'] ); + unset( $theme_json['settings'] ); } else { - $this->theme_json['settings'] = $sanitized['settings']; + $theme_json['settings'] = $sanitized['settings']; } + return $theme_json; } /** diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index bcdedda86f30b7..1f6cfe6d30ce85 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -250,7 +250,7 @@ public static function get_core_data() { $config = self::read_json_file( __DIR__ . '/experimental-default-theme.json' ); $config = self::translate( $config ); - self::$core = new WP_Theme_JSON_Gutenberg( $config ); + self::$core = new WP_Theme_JSON_Gutenberg( $config, 'core' ); return self::$core; } @@ -368,7 +368,7 @@ public static function get_user_data() { $json_decoding_error = json_last_error(); if ( JSON_ERROR_NONE !== $json_decoding_error ) { trigger_error( 'Error when decoding a theme.json schema for user data. ' . json_last_error_msg() ); - return new WP_Theme_JSON_Gutenberg( $config ); + return new WP_Theme_JSON_Gutenberg( $config, 'user' ); } // Very important to verify if the flag isGlobalStylesUserThemeJSON is true. @@ -382,7 +382,7 @@ public static function get_user_data() { $config = $decoded_data; } } - self::$user = new WP_Theme_JSON_Gutenberg( $config ); + self::$user = new WP_Theme_JSON_Gutenberg( $config, 'user' ); return self::$user; } @@ -419,7 +419,7 @@ public static function get_merged_data( $settings = array(), $origin = 'user' ) $result->merge( self::get_theme_data( $theme_support_data ) ); if ( 'user' === $origin ) { - $result->merge( self::get_user_data(), 'update' ); + $result->merge( self::get_user_data() ); } return $result; diff --git a/lib/client-assets.php b/lib/client-assets.php index c52c5ae04f2b04..3a366796d6218c 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -447,7 +447,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-edit-widgets', gutenberg_url( 'build/edit-widgets/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks', 'wp-reusable-blocks' ), + array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks', 'wp-reusable-blocks', 'wp-widgets' ), $version ); $styles->add_data( 'wp-edit-widgets', 'rtl', 'replace' ); @@ -465,7 +465,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-customize-widgets', gutenberg_url( 'build/customize-widgets/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks' ), + array( 'wp-components', 'wp-block-editor', 'wp-edit-blocks', 'wp-widgets' ), $version ); $styles->add_data( 'wp-customize-widgets', 'rtl', 'replace' ); @@ -478,6 +478,14 @@ function gutenberg_register_packages_styles( $styles ) { $version ); $styles->add_data( 'wp-reusable-block', 'rtl', 'replace' ); + + gutenberg_override_style( + $styles, + 'wp-widgets', + gutenberg_url( 'build/widgets/style.css' ), + array( 'wp-components' ) + ); + $styles->add_data( 'wp-widgets', 'rtl', 'replace' ); } add_action( 'wp_default_styles', 'gutenberg_register_packages_styles' ); diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json index dc4dbbaf058529..d92547a1468952 100644 --- a/lib/experimental-default-theme.json +++ b/lib/experimental-default-theme.json @@ -6,148 +6,124 @@ { "name": "Black", "slug": "black", - "color": "#000000", - "origin": "core" + "color": "#000000" }, { "name": "Cyan bluish gray", "slug": "cyan-bluish-gray", - "color": "#abb8c3", - "origin": "core" + "color": "#abb8c3" }, { "name": "White", "slug": "white", - "color": "#ffffff", - "origin": "core" + "color": "#ffffff" }, { "name": "Pale pink", "slug": "pale-pink", - "color": "#f78da7", - "origin": "core" + "color": "#f78da7" }, { "name": "Vivid red", "slug": "vivid-red", - "color": "#cf2e2e", - "origin": "core" + "color": "#cf2e2e" }, { "name": "Luminous vivid orange", "slug": "luminous-vivid-orange", - "color": "#ff6900", - "origin": "core" + "color": "#ff6900" }, { "name": "Luminous vivid amber", "slug": "luminous-vivid-amber", - "color": "#fcb900", - "origin": "core" + "color": "#fcb900" }, { "name": "Light green cyan", "slug": "light-green-cyan", - "color": "#7bdcb5", - "origin": "core" + "color": "#7bdcb5" }, { "name": "Vivid green cyan", "slug": "vivid-green-cyan", - "color": "#00d084", - "origin": "core" + "color": "#00d084" }, { "name": "Pale cyan blue", "slug": "pale-cyan-blue", - "color": "#8ed1fc", - "origin": "core" + "color": "#8ed1fc" }, { "name": "Vivid cyan blue", "slug": "vivid-cyan-blue", - "color": "#0693e3", - "origin": "core" + "color": "#0693e3" }, { "name": "Vivid purple", "slug": "vivid-purple", - "color": "#9b51e0", - "origin": "core" + "color": "#9b51e0" } ], "gradients": [ { "name": "Vivid cyan blue to vivid purple", "gradient": "linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)", - "slug": "vivid-cyan-blue-to-vivid-purple", - "origin": "core" + "slug": "vivid-cyan-blue-to-vivid-purple" }, { "name": "Light green cyan to vivid green cyan", "gradient": "linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)", - "slug": "light-green-cyan-to-vivid-green-cyan", - "origin": "core" + "slug": "light-green-cyan-to-vivid-green-cyan" }, { "name": "Luminous vivid amber to luminous vivid orange", "gradient": "linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)", - "slug": "luminous-vivid-amber-to-luminous-vivid-orange", - "origin": "core" + "slug": "luminous-vivid-amber-to-luminous-vivid-orange" }, { "name": "Luminous vivid orange to vivid red", "gradient": "linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)", - "slug": "luminous-vivid-orange-to-vivid-red", - "origin": "core" + "slug": "luminous-vivid-orange-to-vivid-red" }, { "name": "Very light gray to cyan bluish gray", "gradient": "linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%)", - "slug": "very-light-gray-to-cyan-bluish-gray", - "origin": "core" + "slug": "very-light-gray-to-cyan-bluish-gray" }, { "name": "Cool to warm spectrum", "gradient": "linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%)", - "slug": "cool-to-warm-spectrum", - "origin": "core" + "slug": "cool-to-warm-spectrum" }, { "name": "Blush light purple", "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)", - "slug": "blush-light-purple", - "origin": "core" + "slug": "blush-light-purple" }, { "name": "Blush bordeaux", "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)", - "slug": "blush-bordeaux", - "origin": "core" + "slug": "blush-bordeaux" }, { "name": "Luminous dusk", "gradient": "linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%)", - "slug": "luminous-dusk", - "origin": "core" + "slug": "luminous-dusk" }, { "name": "Pale ocean", "gradient": "linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)", - "slug": "pale-ocean", - "origin": "core" + "slug": "pale-ocean" }, { "name": "Electric grass", "gradient": "linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%)", - "slug": "electric-grass", - "origin": "core" + "slug": "electric-grass" }, { "name": "Midnight", "gradient": "linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)", - "slug": "midnight", - "origin": "core" + "slug": "midnight" } ], "duotone": [ diff --git a/lib/full-site-editing/templates.php b/lib/full-site-editing/templates.php index e6f185bda76bb2..d6ee991fc503cc 100644 --- a/lib/full-site-editing/templates.php +++ b/lib/full-site-editing/templates.php @@ -199,11 +199,21 @@ function set_unique_slug_on_create_template( $post_id ) { * @return void */ function gutenberg_the_skip_link() { + // Early exit if on WP 5.8+. + if ( function_exists( 'the_block_template_skip_link' ) ) { + return; + } - // Early exit if not an FSE theme. + // Early exit if not a block theme. if ( ! gutenberg_supports_block_templates() ) { return; } + + // Early exit if not a block template. + global $_wp_current_template_content; + if ( ! $_wp_current_template_content ) { + return; + } ?> get_settings(); + if ( isset( $settings['__experimentalFeatures']['color']['palette'] ) ) { - $settings['colors'] = $settings['__experimentalFeatures']['color']['palette']; - unset( $settings['__experimentalFeatures']['color']['palette'] ); + $colors_by_origin = $settings['__experimentalFeatures']['color']['palette']; + $settings['colors'] = isset( $colors_by_origin['user'] ) ? + $colors_by_origin['user'] : ( + isset( $colors_by_origin['theme'] ) ? + $colors_by_origin['theme'] : + $colors_by_origin['core'] + ); } + if ( isset( $settings['__experimentalFeatures']['color']['gradients'] ) ) { - $settings['gradients'] = $settings['__experimentalFeatures']['color']['gradients']; - unset( $settings['__experimentalFeatures']['color']['gradients'] ); + $gradients_by_origin = $settings['__experimentalFeatures']['color']['gradients']; + $settings['gradients'] = isset( $gradients_by_origin['user'] ) ? + $gradients_by_origin['user'] : ( + isset( $gradients_by_origin['theme'] ) ? + $gradients_by_origin['theme'] : + $gradients_by_origin['core'] + ); + } + + if ( isset( $settings['__experimentalFeatures']['typography']['fontSizes'] ) ) { + $font_sizes_by_origin = $settings['__experimentalFeatures']['typography']['fontSizes']; + $settings['fontSizes'] = isset( $font_sizes_by_origin['user'] ) ? + $font_sizes_by_origin['user'] : ( + isset( $font_sizes_by_origin['theme'] ) ? + $font_sizes_by_origin['theme'] : + $font_sizes_by_origin['core'] + ); } + if ( isset( $settings['__experimentalFeatures']['color']['custom'] ) ) { $settings['disableCustomColors'] = ! $settings['__experimentalFeatures']['color']['custom']; unset( $settings['__experimentalFeatures']['color']['custom'] ); @@ -152,10 +175,6 @@ function_exists( 'gutenberg_is_edit_site_page' ) && $settings['disableCustomGradients'] = ! $settings['__experimentalFeatures']['color']['customGradient']; unset( $settings['__experimentalFeatures']['color']['customGradient'] ); } - if ( isset( $settings['__experimentalFeatures']['typography']['fontSizes'] ) ) { - $settings['fontSizes'] = $settings['__experimentalFeatures']['typography']['fontSizes']; - unset( $settings['__experimentalFeatures']['typography']['fontSizes'] ); - } if ( isset( $settings['__experimentalFeatures']['typography']['customFontSize'] ) ) { $settings['disableCustomFontSizes'] = ! $settings['__experimentalFeatures']['typography']['customFontSize']; unset( $settings['__experimentalFeatures']['typography']['customFontSize'] ); @@ -209,9 +228,9 @@ function gutenberg_global_styles_filter_post( $content ) { $decoded_data['isGlobalStylesUserThemeJSON'] ) { unset( $decoded_data['isGlobalStylesUserThemeJSON'] ); - $theme_json = new WP_Theme_JSON_Gutenberg( $decoded_data ); - $theme_json->remove_insecure_properties(); - $data_to_encode = $theme_json->get_raw_data(); + + $data_to_encode = WP_Theme_JSON_Gutenberg::remove_insecure_properties( $decoded_data ); + $data_to_encode['isGlobalStylesUserThemeJSON'] = true; return wp_json_encode( $data_to_encode ); } diff --git a/lib/widgets-page.php b/lib/widgets-page.php index 06376c2c71e396..1a4089eea66fd7 100644 --- a/lib/widgets-page.php +++ b/lib/widgets-page.php @@ -103,3 +103,59 @@ function gutenberg_widgets_editor_load_block_editor_scripts_and_styles( $is_bloc function gutenberg_widgets_editor_add_admin_body_classes( $classes ) { return "$classes block-editor-page wp-embed-responsive"; } + +/** + * Emulates the Widgets screen `admin_print_styles` when at the block editor + * screen. + */ +function gutenberg_block_editor_admin_print_styles() { + if ( is_callable( 'get_current_screen' ) && 'appearance_page_gutenberg-widgets' === get_current_screen()->base ) { + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_styles-widgets.php' ); + } +} +add_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' ); + +/** + * Emulates the Widgets screen `admin_print_scripts` when at the block editor + * screen. + */ +function gutenberg_block_editor_admin_print_scripts() { + if ( is_callable( 'get_current_screen' ) && 'appearance_page_gutenberg-widgets' === get_current_screen()->base ) { + /** This action is documented in wp-admin/includes/ajax-actions.php */ + do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + /** This action is documented in wp-admin/includes/ajax-actions.php */ + do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + /** This action is documented in wp-admin/widgets.php */ + do_action( 'sidebar_admin_setup' ); + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_scripts-widgets.php' ); + } +} +add_action( 'admin_print_scripts', 'gutenberg_block_editor_admin_print_scripts' ); + +/** + * Emulates the Widgets screen `admin_print_footer_scripts` when at the block + * editor screen. + */ +function gutenberg_block_editor_admin_print_footer_scripts() { + if ( is_callable( 'get_current_screen' ) && 'appearance_page_gutenberg-widgets' === get_current_screen()->base ) { + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_print_footer_scripts-widgets.php' ); + } +} +add_action( 'admin_print_footer_scripts', 'gutenberg_block_editor_admin_print_footer_scripts' ); + +/** + * Emulates the Widgets screen `admin_footer` when at the block editor screen. + */ +function gutenberg_block_editor_admin_footer() { + if ( is_callable( 'get_current_screen' ) && 'appearance_page_gutenberg-widgets' === get_current_screen()->base ) { + /** This action is documented in wp-admin/admin-footer.php */ + // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores + do_action( 'admin_footer-widgets.php' ); + } +} +add_action( 'admin_footer', 'gutenberg_block_editor_admin_footer' ); diff --git a/lib/widgets.php b/lib/widgets.php index c61d2c1106edc6..314b4f30c2ff8b 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -11,6 +11,12 @@ * @return boolean True if a screen containing the block editor is being loaded. */ function gutenberg_is_block_editor() { + _deprecated_function( + 'gutenberg_is_block_editor', + '10.8', + 'WP_Screen::is_block_editor' + ); + // If get_current_screen does not exist, we are neither in the standard block editor for posts, or the widget block editor. // We can safely return false. if ( ! function_exists( 'get_current_screen' ) ) { @@ -44,79 +50,6 @@ function gutenberg_use_widgets_block_editor() { ); } -/** - * Emulates the Widgets screen `admin_print_styles` when at the block editor - * screen. - */ -function gutenberg_block_editor_admin_print_styles() { - if ( gutenberg_is_block_editor() ) { - /** This action is documented in wp-admin/admin-footer.php */ - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_print_styles-widgets.php' ); - } -} -add_action( 'admin_print_styles', 'gutenberg_block_editor_admin_print_styles' ); - -/** - * Emulates the Widgets screen `admin_print_scripts` when at the block editor - * screen. - */ -function gutenberg_block_editor_admin_print_scripts() { - if ( gutenberg_is_block_editor() ) { - /** This action is documented in wp-admin/includes/ajax-actions.php */ - do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - /** This action is documented in wp-admin/includes/ajax-actions.php */ - do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - /** This action is documented in wp-admin/widgets.php */ - do_action( 'sidebar_admin_setup' ); - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_print_scripts-widgets.php' ); - } -} -add_action( 'admin_print_scripts', 'gutenberg_block_editor_admin_print_scripts' ); - -/** - * Emulates the Widgets screen `admin_print_footer_scripts` when at the block - * editor screen. - */ -function gutenberg_block_editor_admin_print_footer_scripts() { - if ( gutenberg_is_block_editor() ) { - /** This action is documented in wp-admin/admin-footer.php */ - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_print_footer_scripts-widgets.php' ); - } -} -add_action( 'admin_print_footer_scripts', 'gutenberg_block_editor_admin_print_footer_scripts' ); - -/** - * Emulates the Widgets screen `admin_footer` when at the block editor screen. - */ -function gutenberg_block_editor_admin_footer() { - if ( gutenberg_is_block_editor() ) { - /** This action is documented in wp-admin/admin-footer.php */ - // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores - do_action( 'admin_footer-widgets.php' ); - } -} -add_action( 'admin_footer', 'gutenberg_block_editor_admin_footer' ); - -/** - * Adds a save widgets nonce required by the legacy widgets block. - */ -function gutenberg_print_save_widgets_nonce() { - // The function wpWidgets.save needs this nonce to work as expected. - echo implode( - "\n", - array( - '
', - wp_nonce_field( 'save-sidebar-widgets', '_wpnonce_widgets', false ), - '
', - ) - ); -} -add_action( 'admin_footer-widgets.php', 'gutenberg_print_save_widgets_nonce' ); - - /** * Returns the settings required by legacy widgets blocks. * @@ -137,7 +70,6 @@ function gutenberg_get_legacy_widget_settings() { 'media_image', 'media_gallery', 'media_video', - 'meta', 'search', 'text', 'categories', @@ -151,90 +83,11 @@ function gutenberg_get_legacy_widget_settings() { ) ); - // Backwards compatibility. Remove this in or after Gutenberg 10.5. - if ( has_filter( 'widgets_to_exclude_from_legacy_widget_block' ) ) { - /** - * Filters the list of widget classes that should **not** be offered by the legacy widget block. - * - * Returning an empty array will make all the widgets available. - * - * @param array $widgets An array of excluded widgets classnames. - * - * @since 5.6.0 - */ - $widgets_to_exclude_from_legacy_widget_block = apply_filters( - 'widgets_to_exclude_from_legacy_widget_block', - array( - 'WP_Widget_Block', - 'WP_Widget_Pages', - 'WP_Widget_Calendar', - 'WP_Widget_Archives', - 'WP_Widget_Media_Audio', - 'WP_Widget_Media_Image', - 'WP_Widget_Media_Gallery', - 'WP_Widget_Media_Video', - 'WP_Widget_Meta', - 'WP_Widget_Search', - 'WP_Widget_Text', - 'WP_Widget_Categories', - 'WP_Widget_Recent_Posts', - 'WP_Widget_Recent_Comments', - 'WP_Widget_RSS', - 'WP_Widget_Tag_Cloud', - 'WP_Nav_Menu_Widget', - 'WP_Widget_Custom_HTML', - ) - ); - - _deprecated_hook( - 'widgets_to_exclude_from_legacy_widget_block', - '10.3', - "wp.hooks.addFilter( 'legacyWidget.isWidgetTypeHidden', ... )" - ); - - foreach ( $wp_widget_factory->widgets as $widget ) { - if ( - in_array( get_class( $widget ), $widgets_to_exclude_from_legacy_widget_block, true ) && - ! in_array( $widget->id_base, $widget_types_to_hide_from_legacy_widget_block, true ) - ) { - $widget_types_to_hide_from_legacy_widget_block[] = $widget->id_base; - } - } - } - $settings['widgetTypesToHideFromLegacyWidgetBlock'] = $widget_types_to_hide_from_legacy_widget_block; return $settings; } -/** - * Extends default editor settings with values supporting legacy widgets. - * - * This can be removed when plugin support requires WordPress 5.8.0+. - * - * @param array $settings Default editor settings. - * - * @return array Filtered editor settings. - */ -function gutenberg_legacy_widget_settings( $settings ) { - return array_merge( $settings, gutenberg_get_legacy_widget_settings() ); -} -// This can be removed when plugin support requires WordPress 5.8.0+. -if ( function_exists( 'get_block_editor_settings' ) ) { - add_filter( 'block_editor_settings_all', 'gutenberg_legacy_widget_settings' ); -} else { - add_filter( 'block_editor_settings', 'gutenberg_legacy_widget_settings' ); -} - -/** - * Function to enqueue admin-widgets as part of the block editor assets. - */ -function gutenberg_enqueue_widget_scripts() { - wp_enqueue_script( 'admin-widgets' ); -} - -add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_widget_scripts' ); - /** * Overrides dynamic_sidebar_params to make sure Blocks are not wrapped in
tag. * @@ -308,4 +161,7 @@ function gutenberg_set_show_instance_in_rest_on_core_widgets() { } } } -add_action( 'widgets_init', 'gutenberg_set_show_instance_in_rest_on_core_widgets' ); + +if ( ! function_exists( 'wp_use_widgets_block_editor' ) ) { + add_action( 'widgets_init', 'gutenberg_set_show_instance_in_rest_on_core_widgets' ); +} diff --git a/package-lock.json b/package-lock.json index 5254251b971ef4..a6961d1e495acc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14279,12 +14279,20 @@ "version": "file:packages/widgets", "requires": { "@babel/runtime": "^7.13.10", + "@wordpress/api-fetch": "file:packages/api-fetch", + "@wordpress/block-editor": "file:packages/block-editor", "@wordpress/blocks": "file:packages/blocks", "@wordpress/components": "file:packages/components", + "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", + "@wordpress/element": "file:packages/element", "@wordpress/i18n": "file:packages/i18n", - "@wordpress/icons": "file:packages/icons" + "@wordpress/icons": "file:packages/icons", + "@wordpress/notices": "file:packages/notices", + "@wordpress/url": "file:packages/url", + "classnames": "^2.2.5", + "lodash": "^4.17.21" } }, "@wordpress/wordcount": { diff --git a/packages/block-editor/CHANGELOG.md b/packages/block-editor/CHANGELOG.md index 73badf8f522fab..d143158c96b437 100644 --- a/packages/block-editor/CHANGELOG.md +++ b/packages/block-editor/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Features + +- `ButtonBlockerAppender` is now `ButtonBlockAppender`, the original name was a typo, but is still being exported for backward compatibility. + ## 6.1.0 (2021-05-20) ## 6.0.0 (2021-05-14) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 9ecd72ea0cb307..a8a19f6f830211 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -104,6 +104,11 @@ Undocumented declaration. Block breadcrumb component, displaying the hierarchy of the current block selection as a breadcrumb. +_Parameters_ + +- _props_ `Object`: Component props. +- _props.rootLabelText_ `string`: Translated label for the root element of the breadcrumb trail. + _Returns_ - `WPElement`: Block Breadcrumb. @@ -248,12 +253,18 @@ Undocumented declaration. Undocumented declaration. -# **ButtonBlockerAppender** +# **ButtonBlockAppender** _Related_ - +# **ButtonBlockerAppender** + +> **Deprecated** + +Use `ButtonBlockAppender` instead. + # **ColorPalette** Undocumented declaration. diff --git a/packages/block-editor/src/components/block-breadcrumb/README.md b/packages/block-editor/src/components/block-breadcrumb/README.md index 98b432973b33d9..298ff8fdf5ea61 100644 --- a/packages/block-editor/src/components/block-breadcrumb/README.md +++ b/packages/block-editor/src/components/block-breadcrumb/README.md @@ -13,6 +13,15 @@ The block breadcrumb trail displays the hierarchy of the current block selection ## Development guidelines +#### Props + +##### rootLabelText + +Label text for the root element (the first `
  • `) of the breadcrumb trail. + +- Type: `String` +- Required: No + ### Usage Renders a block breadcrumb with default style. diff --git a/packages/block-editor/src/components/block-breadcrumb/index.js b/packages/block-editor/src/components/block-breadcrumb/index.js index 422fbeffc0ed5d..bfc4b45303889d 100644 --- a/packages/block-editor/src/components/block-breadcrumb/index.js +++ b/packages/block-editor/src/components/block-breadcrumb/index.js @@ -14,9 +14,11 @@ import { store as blockEditorStore } from '../../store'; /** * Block breadcrumb component, displaying the hierarchy of the current block selection as a breadcrumb. * - * @return {WPElement} Block Breadcrumb. + * @param {Object} props Component props. + * @param {string} props.rootLabelText Translated label for the root element of the breadcrumb trail. + * @return {WPElement} Block Breadcrumb. */ -function BlockBreadcrumb() { +function BlockBreadcrumb( { rootLabelText } ) { const { selectBlock, clearSelectedBlock } = useDispatch( blockEditorStore ); const { clientId, parents, hasSelection } = useSelect( ( select ) => { const { @@ -31,6 +33,7 @@ function BlockBreadcrumb() { hasSelection: !! getSelectionStart().clientId, }; }, [] ); + const rootLabel = rootLabelText || __( 'Document' ); /* * Disable reason: The `list` ARIA role is redundant but @@ -57,10 +60,10 @@ function BlockBreadcrumb() { isTertiary onClick={ clearSelectedBlock } > - { __( 'Document' ) } + { rootLabel } ) } - { ! hasSelection && __( 'Document' ) } + { ! hasSelection && rootLabel }
  • { parents.map( ( parentClientId ) => (
  • diff --git a/packages/block-editor/src/components/block-breadcrumb/test/__snapshots__/index.js.snap b/packages/block-editor/src/components/block-breadcrumb/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000000..85dd26bc0a7f3b --- /dev/null +++ b/packages/block-editor/src/components/block-breadcrumb/test/__snapshots__/index.js.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BlockBreadcrumb should render correctly 1`] = ` +
      +
    • + Document +
    • +
    +`; diff --git a/packages/block-editor/src/components/block-breadcrumb/test/index.js b/packages/block-editor/src/components/block-breadcrumb/test/index.js new file mode 100644 index 00000000000000..a46392920b5fcb --- /dev/null +++ b/packages/block-editor/src/components/block-breadcrumb/test/index.js @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import BlockBreadcrumb from '../'; + +describe( 'BlockBreadcrumb', () => { + it( 'should render correctly', () => { + const { container } = render( ); + + expect( container.firstChild ).toMatchSnapshot(); + } ); + + describe( 'Root label text', () => { + test( 'should display default label of "Document"', () => { + render( ); + + const rootLabelTextDefault = screen.getByText( 'Document' ); + + expect( rootLabelTextDefault ).toBeInTheDocument(); + } ); + + test( 'should display `rootLabelText` value', () => { + render( ); + + const rootLabelText = screen.getByText( 'Tuhinga' ); + const rootLabelTextDefault = screen.queryByText( 'Document' ); + + expect( rootLabelTextDefault ).toBeNull(); + expect( rootLabelText ).toBeInTheDocument(); + } ); + } ); +} ); diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 96e46284d6b95d..46e5d56f9f0317 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -27,25 +27,20 @@ export const IntersectionObserver = createContext(); function Root( { className, children } ) { const isLargeViewport = useViewportMatch( 'medium' ); - const { - isTyping, - isOutlineMode, - isFocusMode, - isNavigationMode, - } = useSelect( ( select ) => { - const { - isTyping: _isTyping, - getSettings, - isNavigationMode: _isNavigationMode, - } = select( blockEditorStore ); - const { outlineMode, focusMode } = getSettings(); - return { - isTyping: _isTyping(), - isOutlineMode: outlineMode, - isFocusMode: focusMode, - isNavigationMode: _isNavigationMode(), - }; - }, [] ); + const { isOutlineMode, isFocusMode, isNavigationMode } = useSelect( + ( select ) => { + const { getSettings, isNavigationMode: _isNavigationMode } = select( + blockEditorStore + ); + const { outlineMode, focusMode } = getSettings(); + return { + isOutlineMode: outlineMode, + isFocusMode: focusMode, + isNavigationMode: _isNavigationMode(), + }; + }, + [] + ); return (
    - + ); diff --git a/packages/block-editor/src/components/block-list/style.scss b/packages/block-editor/src/components/block-list/style.scss index ebe5d59097395d..e579e7d7786048 100644 --- a/packages/block-editor/src/components/block-list/style.scss +++ b/packages/block-editor/src/components/block-list/style.scss @@ -257,7 +257,7 @@ } } -.is-outline-mode:not(.is-typing) .block-editor-block-list__block { +.is-outline-mode .block-editor-block-list__block:not(.remove-outline) { &.is-hovered { cursor: default; diff --git a/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js b/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js index c3a99b4ec5e04c..1c24ae12b5b22b 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js +++ b/packages/block-editor/src/components/block-list/use-block-props/use-block-class-names.js @@ -32,10 +32,12 @@ export function useBlockClassNames( clientId ) { getBlockName, getSettings, hasSelectedInnerBlock, + isTyping, __experimentalGetActiveBlockIdByBlockNames: getActiveBlockIdByBlockNames, } = select( blockEditorStore ); const { __experimentalSpotlightEntityBlocks: spotlightEntityBlocks, + outlineMode, } = getSettings(); const isDragging = isBlockBeingDragged( clientId ); const isSelected = isBlockSelected( clientId ); @@ -59,6 +61,7 @@ export function useBlockClassNames( clientId ) { 'has-active-entity': activeEntityBlockId, // Determine if there is an active entity area to spotlight. 'is-active-entity': activeEntityBlockId === clientId, + 'remove-outline': isSelected && outlineMode && isTyping(), } ); }, [ clientId ] diff --git a/packages/block-editor/src/components/block-list/use-in-between-inserter.js b/packages/block-editor/src/components/block-list/use-in-between-inserter.js index 0dfdab359d7a77..618811ec91ba64 100644 --- a/packages/block-editor/src/components/block-list/use-in-between-inserter.js +++ b/packages/block-editor/src/components/block-list/use-in-between-inserter.js @@ -14,12 +14,17 @@ import { InsertionPointOpenRef } from '../block-tools/insertion-point'; export function useInBetweenInserter() { const openRef = useContext( InsertionPointOpenRef ); + const hasReducedUI = useSelect( + ( select ) => select( blockEditorStore ).getSettings().hasReducedUI, + [] + ); const { getBlockListSettings, getBlockRootClientId, getBlockIndex, isBlockInsertionPointVisible, isMultiSelecting, + getSelectedBlockClientIds, } = useSelect( blockEditorStore ); const { showInsertionPoint, hideInsertionPoint } = useDispatch( blockEditorStore @@ -27,6 +32,10 @@ export function useInBetweenInserter() { return useRefEffect( ( node ) => { + if ( hasReducedUI ) { + return; + } + function onMouseMove( event ) { if ( openRef.current ) { return; @@ -98,6 +107,12 @@ export function useInBetweenInserter() { return; } + // Don't show the inserter when hovering above (conflicts with + // block toolbar) or inside selected block(s). + if ( getSelectedBlockClientIds().includes( clientId ) ) { + return; + } + const elementRect = element.getBoundingClientRect(); if ( @@ -145,6 +160,7 @@ export function useInBetweenInserter() { isMultiSelecting, showInsertionPoint, hideInsertionPoint, + getSelectedBlockClientIds, ] ); } diff --git a/packages/block-editor/src/components/block-tools/block-popover.js b/packages/block-editor/src/components/block-tools/block-popover.js index dc803f7670aba6..8371cfff9bbba8 100644 --- a/packages/block-editor/src/components/block-tools/block-popover.js +++ b/packages/block-editor/src/components/block-tools/block-popover.js @@ -64,6 +64,24 @@ function BlockPopover( { hasFixedToolbar, lastClientId, } = useSelect( selector, [] ); + const isInsertionPointVisible = useSelect( + ( select ) => { + const { + isBlockInsertionPointVisible, + getBlockInsertionPoint, + getBlockOrder, + } = select( blockEditorStore ); + + if ( ! isBlockInsertionPointVisible() ) { + return false; + } + + const insertionPoint = getBlockInsertionPoint(); + const order = getBlockOrder( insertionPoint.rootClientId ); + return order[ insertionPoint.index ] === clientId; + }, + [ clientId ] + ); const isLargeViewport = useViewportMatch( 'medium' ); const [ isToolbarForced, setIsToolbarForced ] = useState( false ); const [ isInserterShown, setIsInserterShown ] = useState( false ); @@ -184,7 +202,9 @@ function BlockPopover( { position={ popoverPosition } focusOnMount={ false } anchorRef={ anchorRef } - className="block-editor-block-list__block-popover" + className={ classnames( 'block-editor-block-list__block-popover', { + 'is-insertion-point-visible': isInsertionPointVisible, + } ) } __unstableStickyBoundaryElement={ stickyBoundaryElement } // Render in the old slot if needed for backward compatibility, // otherwise render in place (not in the the default popover slot). diff --git a/packages/block-editor/src/components/block-tools/insertion-point.js b/packages/block-editor/src/components/block-tools/insertion-point.js index 134430c301a61e..b836beba7d3ae0 100644 --- a/packages/block-editor/src/components/block-tools/insertion-point.js +++ b/packages/block-editor/src/components/block-tools/insertion-point.js @@ -36,7 +36,6 @@ function InsertionPointPopover( { const ref = useRef(); const { orientation, - isHidden, previousClientId, nextClientId, rootClientId, @@ -45,47 +44,36 @@ function InsertionPointPopover( { const { getBlockOrder, getBlockListSettings, - getMultiSelectedBlockClientIds, - getSelectedBlockClientId, - hasMultiSelection, - getSettings, getBlockInsertionPoint, + isBlockBeingDragged, + getPreviousBlockClientId, + getNextBlockClientId, } = select( blockEditorStore ); const insertionPoint = getBlockInsertionPoint(); const order = getBlockOrder( insertionPoint.rootClientId ); - const targetClientId = order[ insertionPoint.index - 1 ]; - const targetRootClientId = insertionPoint.rootClientId; - const blockOrder = getBlockOrder( targetRootClientId ); - if ( ! blockOrder.length ) { + + if ( ! order.length ) { return {}; } - const previous = targetClientId - ? targetClientId - : blockOrder[ blockOrder.length - 1 ]; - const isLast = previous === blockOrder[ blockOrder.length - 1 ]; - const next = isLast - ? null - : blockOrder[ blockOrder.indexOf( previous ) + 1 ]; - const { hasReducedUI } = getSettings(); - const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds(); - const selectedBlockClientId = getSelectedBlockClientId(); - const blockOrientation = - getBlockListSettings( targetRootClientId )?.orientation || - 'vertical'; + + let _previousClientId = order[ insertionPoint.index - 1 ]; + let _nextClientId = order[ insertionPoint.index ]; + + while ( isBlockBeingDragged( _previousClientId ) ) { + _previousClientId = getPreviousBlockClientId( _previousClientId ); + } + + while ( isBlockBeingDragged( _nextClientId ) ) { + _nextClientId = getNextBlockClientId( _nextClientId ); + } return { - previousClientId: previous, - nextClientId: next, - isHidden: - hasReducedUI || - ( hasMultiSelection() - ? next && multiSelectedBlockClientIds.includes( next ) - : next && - blockOrientation === 'vertical' && - next === selectedBlockClientId ), - orientation: blockOrientation, - clientId: targetClientId, - rootClientId: targetRootClientId, + previousClientId: _previousClientId, + nextClientId: _nextClientId, + orientation: + getBlockListSettings( insertionPoint.rootClientId ) + ?.orientation || 'vertical', + rootClientId: insertionPoint.rootClientId, isInserterShown: insertionPoint?.__unstableWithInserter, }; }, [] ); @@ -193,14 +181,7 @@ function InsertionPointPopover( { // Only show the inserter when there's a `nextElement` (a block after the // insertion point). At the end of the block list the trailing appender // should serve the purpose of inserting blocks. - const showInsertionPointInserter = - ! isHidden && nextElement && isInserterShown; - - // Show the indicator if the insertion point inserter is visible, or if - // the `showInsertionPoint` state is `true`. The latter is generally true - // when hovering blocks for insertion in the block library. - const showInsertionPointIndicator = - showInsertionPointInserter || ! isHidden; + const showInsertionPointInserter = nextElement && isInserterShown; /* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events */ // While ideally it would be enough to capture the @@ -231,9 +212,7 @@ function InsertionPointPopover( { } ) } style={ style } > - { showInsertionPointIndicator && ( -
    - ) } +
    { showInsertionPointInserter && (
    { - const { isMultiSelecting, isBlockInsertionPointVisible } = select( - blockEditorStore - ); - - return isBlockInsertionPointVisible() && ! isMultiSelecting(); + return select( blockEditorStore ).isBlockInsertionPointVisible(); }, [] ); return ( diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index e511a41fabe92c..072b085f387bc1 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -321,6 +321,11 @@ } } + // Hide the block toolbar if the insertion point is shown. + &.is-insertion-point-visible { + visibility: hidden; + } + .is-dragging-components-draggable & { opacity: 0; // Use a minimal duration to delay hiding the element, see hide-during-dragging animation for more details. diff --git a/packages/block-editor/src/components/button-block-appender/index.js b/packages/block-editor/src/components/button-block-appender/index.js index eecd1bf51af174..6fa49f5f614252 100644 --- a/packages/block-editor/src/components/button-block-appender/index.js +++ b/packages/block-editor/src/components/button-block-appender/index.js @@ -10,6 +10,7 @@ import { Button, Tooltip, VisuallyHidden } from '@wordpress/components'; import { forwardRef } from '@wordpress/element'; import { _x, sprintf } from '@wordpress/i18n'; import { Icon, plus } from '@wordpress/icons'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -81,6 +82,19 @@ function ButtonBlockAppender( ); } +/** + * Use `ButtonBlockAppender` instead. + * + * @deprecated + */ +export const ButtonBlockerAppender = forwardRef( ( props, ref ) => { + deprecated( `wp.blockEditor.ButtonBlockerAppender`, { + alternative: 'wp.blockEditor.ButtonBlockAppender', + } ); + + return ButtonBlockAppender( props, ref ); +} ); + /** * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/button-block-appender/README.md */ diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 51d9ba8c1a0dbe..bf906202779fee 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -34,7 +34,11 @@ export { BlockVerticalAlignmentControl, } from './block-vertical-alignment-control'; export { default as __experimentalBorderStyleControl } from './border-style-control'; -export { default as ButtonBlockerAppender } from './button-block-appender'; +export { + // This is a typo, but kept here for back-compat. + ButtonBlockerAppender, + default as ButtonBlockAppender, +} from './button-block-appender'; export { default as ColorPalette } from './color-palette'; export { default as ColorPaletteControl } from './color-palette/control'; export { default as ContrastChecker } from './contrast-checker'; diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index 391f78ac97aa3c..62e13c9b6b0af9 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -59,16 +59,7 @@ export function getNearestBlockIndex( elements, position, orientation ) { // If the user is dropping to the trailing edge of the block // add 1 to the index to represent dragging after. const isTrailingEdge = edge === 'bottom' || edge === 'right'; - let offset = isTrailingEdge ? 1 : 0; - - // If the target is the dragged block itself and another 1 to - // index as the dragged block is set to `display: none` and - // should be skipped in the calculation. - const isTargetDraggedBlock = - isTrailingEdge && - elements[ index + 1 ] && - elements[ index + 1 ].classList.contains( 'is-dragging' ); - offset += isTargetDraggedBlock ? 1 : 0; + const offset = isTrailingEdge ? 1 : 0; // Update the currently known best candidate. candidateDistance = distance; @@ -98,20 +89,15 @@ export default function useBlockDropZone( { } = {} ) { const [ targetBlockIndex, setTargetBlockIndex ] = useState( null ); - const { isLockedAll, orientation } = useSelect( + const isLockedAll = useSelect( ( select ) => { - const { getBlockListSettings, getTemplateLock } = select( - blockEditorStore - ); - return { - isLockedAll: getTemplateLock( targetRootClientId ) === 'all', - orientation: getBlockListSettings( targetRootClientId ) - ?.orientation, - }; + const { getTemplateLock } = select( blockEditorStore ); + return getTemplateLock( targetRootClientId ) === 'all'; }, [ targetRootClientId ] ); + const { getBlockListSettings } = useSelect( blockEditorStore ); const { showInsertionPoint, hideInsertionPoint } = useDispatch( blockEditorStore ); @@ -123,7 +109,7 @@ export default function useBlockDropZone( { const targetIndex = getNearestBlockIndex( blockElements, { x: event.clientX, y: event.clientY }, - orientation + getBlockListSettings( targetRootClientId )?.orientation ); setTargetBlockIndex( targetIndex === undefined ? 0 : targetIndex ); @@ -144,6 +130,11 @@ export default function useBlockDropZone( { // https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget throttled( event, event.currentTarget ); }, + onDragLeave() { + throttled.cancel(); + hideInsertionPoint(); + setTargetBlockIndex( null ); + }, onDragEnd() { throttled.cancel(); hideInsertionPoint(); diff --git a/packages/block-editor/src/components/use-block-drop-zone/test/index.js b/packages/block-editor/src/components/use-block-drop-zone/test/index.js index c0aac7b5827d92..42a9476be0cd41 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/test/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/test/index.js @@ -209,27 +209,6 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBe( 4 ); } ); - - it( 'skips the block being dragged by checking for the `is-dragging` classname', () => { - const position = { x: 0, y: 450 }; - - const verticalElementsWithDraggedBlock = [ - ...verticalElements.slice( 0, 2 ), - { - ...verticalElements[ 2 ], - classList: createMockClassList( 'wp-block is-dragging' ), - }, - ...verticalElements.slice( 3, 4 ), - ]; - - const result = getNearestBlockIndex( - verticalElementsWithDraggedBlock, - position, - orientation - ); - - expect( result ).toBe( 3 ); - } ); } ); describe( 'Horizontal block lists', () => { @@ -342,26 +321,5 @@ describe( 'getNearestBlockIndex', () => { expect( result ).toBe( 4 ); } ); - - it( 'skips the block being dragged by checking for the `is-dragging` classname', () => { - const position = { x: 450, y: 0 }; - - const horizontalElementsWithDraggedBlock = [ - ...horizontalElements.slice( 0, 2 ), - { - ...horizontalElements[ 2 ], - classList: createMockClassList( 'wp-block is-dragging' ), - }, - ...horizontalElements.slice( 3, 4 ), - ]; - - const result = getNearestBlockIndex( - horizontalElementsWithDraggedBlock, - position, - orientation - ); - - expect( result ).toBe( 3 ); - } ); } ); } ); diff --git a/packages/block-editor/src/components/use-setting/index.js b/packages/block-editor/src/components/use-setting/index.js index 43ee185c396617..d302ceb277425f 100644 --- a/packages/block-editor/src/components/use-setting/index.js +++ b/packages/block-editor/src/components/use-setting/index.js @@ -49,18 +49,11 @@ const deprecatedFlags = { 'spacing.customPadding': ( settings ) => settings.enableCustomSpacing, }; -const filterColorsFromCoreOrigin = ( path, setting ) => { - if ( path !== 'color.palette' && path !== 'color.gradients' ) { - return setting; - } - - if ( ! Array.isArray( setting ) ) { - return setting; - } - - const colors = setting.filter( ( color ) => color?.origin !== 'core' ); - - return colors.length > 0 ? colors : setting; +const PATHS_WITH_MERGE = { + 'color.gradients': true, + 'color.palette': true, + 'typography.fontFamilies': true, + 'typography.fontSizes': true, }; /** @@ -90,10 +83,14 @@ export default function useSetting( path ) { const experimentalFeaturesResult = get( settings, blockPath ) ?? get( settings, defaultsPath ); if ( experimentalFeaturesResult !== undefined ) { - return filterColorsFromCoreOrigin( - path, - experimentalFeaturesResult - ); + if ( PATHS_WITH_MERGE[ path ] ) { + return ( + experimentalFeaturesResult.user ?? + experimentalFeaturesResult.theme ?? + experimentalFeaturesResult.core + ); + } + return experimentalFeaturesResult; } // 2 - Use deprecated settings, otherwise. @@ -101,10 +98,7 @@ export default function useSetting( path ) { ? deprecatedFlags[ path ]( settings ) : undefined; if ( deprecatedSettingsValue !== undefined ) { - return filterColorsFromCoreOrigin( - path, - deprecatedSettingsValue - ); + return deprecatedSettingsValue; } // 3 - Fall back for typography.dropCap: diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 69da6c75860edf..ec1bb964de3c0a 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -277,6 +277,22 @@ export function stopMultiSelect() { * @param {string} end Last block of the multiselection. */ export function* multiSelect( start, end ) { + const startBlockRootClientId = yield controls.select( + blockEditorStoreName, + 'getBlockRootClientId', + start + ); + const endBlockRootClientId = yield controls.select( + blockEditorStoreName, + 'getBlockRootClientId', + end + ); + + // Only allow block multi-selections at the same level. + if ( startBlockRootClientId !== endBlockRootClientId ) { + return; + } + yield { type: 'MULTI_SELECT', start, diff --git a/packages/block-editor/src/store/test/actions.js b/packages/block-editor/src/store/test/actions.js index 6433f15e6523e7..c651122f77ad72 100644 --- a/packages/block-editor/src/store/test/actions.js +++ b/packages/block-editor/src/store/test/actions.js @@ -147,16 +147,60 @@ describe( 'actions', () => { } ); } ); describe( 'multiSelect', () => { - it( 'should return MULTI_SELECT action', () => { + it( 'should return MULTI_SELECT action if blocks have the same root client id', () => { const start = 'start'; const end = 'end'; - const fulfillment = multiSelect( start, end ); - expect( fulfillment.next().value ).toEqual( { + const multiSelectGenerator = multiSelect( start, end ); + + expect( multiSelectGenerator.next().value ).toEqual( + controls.select( + blockEditorStoreName, + 'getBlockRootClientId', + start + ) + ); + + expect( multiSelectGenerator.next( 'parent' ).value ).toEqual( + controls.select( + blockEditorStoreName, + 'getBlockRootClientId', + end + ) + ); + + expect( multiSelectGenerator.next( 'parent' ).value ).toEqual( { type: 'MULTI_SELECT', start, end, } ); } ); + + it( 'should return undefined if blocks have different root client ids', () => { + const start = 'start'; + const end = 'end'; + const multiSelectGenerator = multiSelect( start, end ); + + expect( multiSelectGenerator.next().value ).toEqual( + controls.select( + blockEditorStoreName, + 'getBlockRootClientId', + start + ) + ); + + expect( multiSelectGenerator.next( 'parent' ).value ).toEqual( + controls.select( + blockEditorStoreName, + 'getBlockRootClientId', + end + ) + ); + + expect( multiSelectGenerator.next( 'another parent' ) ).toEqual( { + done: true, + value: undefined, + } ); + } ); } ); describe( 'clearSelectedBlock', () => { diff --git a/packages/block-library/CHANGELOG.md b/packages/block-library/CHANGELOG.md index 46b6aa9443a5ee..62ed002f5d028e 100644 --- a/packages/block-library/CHANGELOG.md +++ b/packages/block-library/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +## Breaking Changes + +- Removes the `core/legacy-widget` block. This is now in `@wordpress/widgets` + via `registerLegacyWidgetBlock()`. + ## 3.2.0 (2021-05-24) ### New Features diff --git a/packages/block-library/src/editor.scss b/packages/block-library/src/editor.scss index 4118213572e232..49d89ff9e7c3f9 100644 --- a/packages/block-library/src/editor.scss +++ b/packages/block-library/src/editor.scss @@ -20,7 +20,6 @@ @import "./html/editor.scss"; @import "./image/editor.scss"; @import "./latest-posts/editor.scss"; -@import "./legacy-widget/editor.scss"; @import "./media-text/editor.scss"; @import "./more/editor.scss"; @import "./navigation/editor.scss"; diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 73713305c10308..0716373e244b8b 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -38,7 +38,6 @@ import * as navigationLink from './navigation-link'; import * as homeLink from './home-link'; import * as latestComments from './latest-comments'; import * as latestPosts from './latest-posts'; -import * as legacyWidget from './legacy-widget'; import * as logInOut from './loginout'; import * as list from './list'; import * as missing from './missing'; @@ -148,7 +147,6 @@ export const __experimentalGetCoreBlocks = () => [ mediaText, latestComments, latestPosts, - legacyWidget, missing, more, nextpage, diff --git a/packages/block-library/src/social-links/style.scss b/packages/block-library/src/social-links/style.scss index eb1d5bc57934c3..eee1000a913f24 100644 --- a/packages/block-library/src/social-links/style.scss +++ b/packages/block-library/src/social-links/style.scss @@ -69,7 +69,7 @@ } } -.wp-social-link { +.wp-block-social-link { display: block; border-radius: 9999px; // This makes it pill-shaped instead of oval, in cases where the image fed is not perfectly sized. transition: transform 0.1s ease; @@ -84,20 +84,22 @@ transition: transform 0.1s ease; } - a, - a:hover, - a:active, - a:visited, - svg { - color: currentColor; - fill: currentColor; - } - &:hover { transform: scale(1.1); } } +// This needs specificity because themes usually override it with things like .widget-area a. +.wp-block-social-links .wp-block-social-link .wp-block-social-link-anchor { + &, + &:hover, + &:active, + &:visited, + svg { + color: currentColor; + fill: currentColor; + } +} // Provide colors for a range of icons. .wp-block-social-links:not(.is-style-logos-only) { diff --git a/packages/compose/src/hooks/use-drop-zone/index.js b/packages/compose/src/hooks/use-drop-zone/index.js index bbfc0ba461a89c..f2af24c549bf6a 100644 --- a/packages/compose/src/hooks/use-drop-zone/index.js +++ b/packages/compose/src/hooks/use-drop-zone/index.js @@ -56,7 +56,31 @@ export default function useDropZone( { const { ownerDocument } = element; - function maybeDragStart( event ) { + /** + * Checks if an element is in the drop zone. + * + * @param {HTMLElement|null} elementToCheck + * + * @return {boolean} True if in drop zone, false if not. + */ + function isElementInZone( elementToCheck ) { + if ( + ! elementToCheck || + ! element.contains( elementToCheck ) + ) { + return false; + } + + do { + if ( elementToCheck.dataset.isDropZone ) { + return elementToCheck === element; + } + } while ( ( elementToCheck = elementToCheck.parentElement ) ); + + return false; + } + + function maybeDragStart( /** @type {DragEvent} */ event ) { if ( isDragging ) { return; } @@ -68,6 +92,13 @@ export default function useDropZone( { maybeDragStart ); + // Note that `dragend` doesn't fire consistently for file and + // HTML drag events where the drag origin is outside the browser + // window. In Firefox it may also not fire if the originating + // node is removed. + ownerDocument.addEventListener( 'dragend', maybeDragEnd ); + ownerDocument.addEventListener( 'mousemove', maybeDragEnd ); + if ( onDragStartRef.current ) { onDragStartRef.current( event ); } @@ -106,7 +137,7 @@ export default function useDropZone( { // leaving the drop zone, which means the `relatedTarget` // (element that has been entered) should be outside the drop // zone. - if ( element.contains( event.relatedTarget ) ) { + if ( isElementInZone( event.relatedTarget ) ) { return; } @@ -146,33 +177,31 @@ export default function useDropZone( { isDragging = false; ownerDocument.addEventListener( 'dragenter', maybeDragStart ); + ownerDocument.removeEventListener( 'dragend', maybeDragEnd ); + ownerDocument.removeEventListener( 'mousemove', maybeDragEnd ); if ( onDragEndRef.current ) { onDragEndRef.current( event ); } } + element.dataset.isDropZone = 'true'; element.addEventListener( 'drop', onDrop ); element.addEventListener( 'dragenter', onDragEnter ); element.addEventListener( 'dragover', onDragOver ); element.addEventListener( 'dragleave', onDragLeave ); - // Note that `dragend` doesn't fire consistently for file and HTML - // drag events where the drag origin is outside the browser window. - // In Firefox it may also not fire if the originating node is - // removed. - ownerDocument.addEventListener( 'dragend', maybeDragEnd ); - ownerDocument.addEventListener( 'mouseup', maybeDragEnd ); // The `dragstart` event doesn't fire if the drag started outside // the document. ownerDocument.addEventListener( 'dragenter', maybeDragStart ); return () => { + delete element.dataset.isDropZone; element.removeEventListener( 'drop', onDrop ); element.removeEventListener( 'dragenter', onDragEnter ); element.removeEventListener( 'dragover', onDragOver ); element.removeEventListener( 'dragleave', onDragLeave ); ownerDocument.removeEventListener( 'dragend', maybeDragEnd ); - ownerDocument.removeEventListener( 'mouseup', maybeDragEnd ); + ownerDocument.removeEventListener( 'mousemove', maybeDragEnd ); ownerDocument.addEventListener( 'dragenter', maybeDragStart ); }; }, diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js index 8ba9182a9209dd..40592021000aff 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js @@ -18,6 +18,7 @@ import { WritingFlow, BlockEditorKeyboardShortcuts, __unstableBlockSettingsMenuFirstItem, + ButtonBlockAppender, } from '@wordpress/block-editor'; import { uploadMedia } from '@wordpress/media-utils'; @@ -116,7 +117,9 @@ export default function SidebarBlockEditor( { - + diff --git a/packages/customize-widgets/src/index.js b/packages/customize-widgets/src/index.js index 1b2d02b3c81fd9..3a725ccdda2ba6 100644 --- a/packages/customize-widgets/src/index.js +++ b/packages/customize-widgets/src/index.js @@ -7,7 +7,10 @@ import { __experimentalGetCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, } from '@wordpress/block-library'; -import { registerLegacyWidgetVariations } from '@wordpress/widgets'; +import { + registerLegacyWidgetBlock, + registerLegacyWidgetVariations, +} from '@wordpress/widgets'; /** * Internal dependencies @@ -30,11 +33,10 @@ export function initialize( editorName, blockEditorSettings ) { ( block ) => ! [ 'core/more' ].includes( block.name ) ); registerCoreBlocks( coreBlocks ); - + registerLegacyWidgetBlock(); if ( process.env.GUTENBERG_PHASE === 2 ) { __experimentalRegisterExperimentalCoreBlocks(); } - registerLegacyWidgetVariations( blockEditorSettings ); const SidebarControl = getSidebarControl( blockEditorSettings ); diff --git a/packages/data/src/components/use-select/index.js b/packages/data/src/components/use-select/index.js index b9249c671f328c..b0246aaf09f849 100644 --- a/packages/data/src/components/use-select/index.js +++ b/packages/data/src/components/use-select/index.js @@ -148,12 +148,11 @@ export default function useSelect( _mapSelect, deps ) { errorMessage += `\nThe error may be correlated with this previous error:\n`; errorMessage += `${ latestMapOutputError.current.stack }\n\n`; errorMessage += 'Original stack trace:'; - - throw new Error( errorMessage ); - } else { - // eslint-disable-next-line no-console - console.error( errorMessage ); } + + // eslint-disable-next-line no-console + console.error( errorMessage ); + mapOutput = latestMapOutput.current; } } diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.html b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.html deleted file mode 100644 index 293a884c37414f..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.json b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.json deleted file mode 100644 index fc919a1597d6ca..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "clientId": "_clientId_0", - "name": "core/legacy-widget", - "isValid": true, - "attributes": { - "id": null, - "idBase": "search", - "instance": { - "encoded": "YTowOnt9", - "hash": "b9b82f721929717273108125217fbcd9", - "raw": {} - } - }, - "innerBlocks": [], - "originalContent": "" - } -] diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.parsed.json b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.parsed.json deleted file mode 100644 index e97d26c0f43b22..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.parsed.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "blockName": "core/legacy-widget", - "attrs": { - "idBase": "search", - "instance": { - "encoded": "YTowOnt9", - "hash": "b9b82f721929717273108125217fbcd9", - "raw": {} - } - }, - "innerBlocks": [], - "innerHTML": "", - "innerContent": [] - } -] diff --git a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.serialized.html b/packages/e2e-tests/fixtures/blocks/core__legacy-widget.serialized.html deleted file mode 100644 index 293a884c37414f..00000000000000 --- a/packages/e2e-tests/fixtures/blocks/core__legacy-widget.serialized.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/e2e-tests/specs/editor/plugins/__snapshots__/wp-editor-meta-box.test.js.snap b/packages/e2e-tests/specs/editor/plugins/__snapshots__/wp-editor-meta-box.test.js.snap index 485862873b648d..29a3d82f5f0685 100644 --- a/packages/e2e-tests/specs/editor/plugins/__snapshots__/wp-editor-meta-box.test.js.snap +++ b/packages/e2e-tests/specs/editor/plugins/__snapshots__/wp-editor-meta-box.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`WP Editor Meta Boxes Should save the changes 1`] = `"

    Typing in a metabox

    "`; +exports[`WP Editor Meta Boxes Should save the changes 1`] = `"Typing in a metabox"`; diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js index eba80dd658a89b..f8d748eaa285ca 100644 --- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js @@ -9,6 +9,7 @@ import { getEditedPostContent, clickBlockToolbarButton, clickButton, + clickMenuItem, } from '@wordpress/e2e-test-utils'; async function getSelectedFlatIndices() { @@ -272,6 +273,19 @@ describe( 'Multi-block selection', () => { await testNativeSelection(); expect( await getSelectedFlatIndices() ).toEqual( [ 1, 2 ] ); + + // Group the blocks and test that multiselection also works for nested + // blocks. Checks for regressions of + // https://github.com/WordPress/gutenberg/issues/32056 + + await clickBlockToolbarButton( 'Options' ); + await clickMenuItem( 'Group' ); + await page.click( '[data-type="core/paragraph"]' ); + await page.keyboard.down( 'Shift' ); + await page.click( '[data-type="core/paragraph"]:nth-child(2)' ); + await page.keyboard.up( 'Shift' ); + await testNativeSelection(); + expect( await getSelectedFlatIndices() ).toEqual( [ 2, 3 ] ); } ); it( 'should select by dragging', async () => { diff --git a/packages/e2e-tests/specs/widgets/editing-widgets.test.js b/packages/e2e-tests/specs/widgets/editing-widgets.test.js index 4c0088d4ea4f2f..4471506c317a6f 100644 --- a/packages/e2e-tests/specs/widgets/editing-widgets.test.js +++ b/packages/e2e-tests/specs/widgets/editing-widgets.test.js @@ -9,6 +9,7 @@ import { showBlockToolbar, visitAdminPage, deleteAllWidgets, + pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; /** @@ -122,14 +123,17 @@ describe( 'Widgets screen', () => { ).toBe( true ); } - async function getInlineInserterButton() { - return await find( { - role: 'combobox', - name: 'Add block', + it( 'Should insert content using the global inserter', async () => { + const updateButton = await find( { + role: 'button', + name: 'Update', } ); - } - it( 'Should insert content using the global inserter', async () => { + // Update button should start out disabled. + expect( + await updateButton.evaluate( ( button ) => button.disabled ) + ).toBe( true ); + const widgetAreas = await findAll( { role: 'group', name: 'Block: Widget Area', @@ -146,6 +150,11 @@ describe( 'Widgets screen', () => { await addParagraphBlock.click(); + // Adding content should enable the Update button. + expect( + await updateButton.evaluate( ( button ) => button.disabled ) + ).toBe( false ); + let addedParagraphBlockInFirstWidgetArea = await find( { name: /^Empty block/, @@ -215,6 +224,12 @@ describe( 'Widgets screen', () => { // await page.keyboard.type( 'Third Paragraph' ); await saveWidgets(); + + // The Update button should be disabled again after saving. + expect( + await updateButton.evaluate( ( button ) => button.disabled ) + ).toBe( true ); + const serializedWidgetAreas = await getSerializedWidgetAreas(); expect( serializedWidgetAreas ).toMatchInlineSnapshot( ` Object { @@ -252,7 +267,10 @@ describe( 'Widgets screen', () => { 10 ); - let inlineInserterButton = await getInlineInserterButton(); + let inlineInserterButton = await find( { + role: 'combobox', + name: 'Add block', + } ); await inlineInserterButton.click(); let inlineQuickInserter = await find( { @@ -312,7 +330,13 @@ describe( 'Widgets screen', () => { secondParagraphBlockBoundingBox.y - 10 ); - inlineInserterButton = await getInlineInserterButton(); + // There will be 2 matches here. + // One is the in-between inserter, + // and the other one is the button block appender. + [ inlineInserterButton ] = await findAll( { + role: 'combobox', + name: 'Add block', + } ); await inlineInserterButton.click(); // TODO: Convert to find() API from puppeteer-testing-library. @@ -673,6 +697,62 @@ describe( 'Widgets screen', () => { } ` ); } ); + + it( 'Allows widget deletion to be undone', async () => { + const [ firstWidgetArea ] = await findAll( { + role: 'group', + name: 'Block: Widget Area', + } ); + + let addParagraphBlock = await getBlockInGlobalInserter( 'Paragraph' ); + await addParagraphBlock.click(); + + let addedParagraphBlockInFirstWidgetArea = await find( + { + name: /^Empty block/, + selector: '[data-block][data-type="core/paragraph"]', + }, + { + root: firstWidgetArea, + } + ); + await addedParagraphBlockInFirstWidgetArea.focus(); + await page.keyboard.type( 'First Paragraph' ); + + addParagraphBlock = await getBlockInGlobalInserter( 'Paragraph' ); + await addParagraphBlock.click(); + + addedParagraphBlockInFirstWidgetArea = await firstWidgetArea.$( + '[data-block][data-type="core/paragraph"][aria-label^="Empty block"]' + ); + await addedParagraphBlockInFirstWidgetArea.focus(); + await page.keyboard.type( 'Second Paragraph' ); + + await saveWidgets(); + + // Delete the last block and save again. + await pressKeyWithModifier( 'access', 'z' ); + await saveWidgets(); + + // Undo block deletion and save again + await pressKeyWithModifier( 'primary', 'z' ); + await saveWidgets(); + + // Reload the page to make sure changes were actually saved. + await page.reload(); + + const serializedWidgetAreas = await getSerializedWidgetAreas(); + expect( serializedWidgetAreas ).toMatchInlineSnapshot( ` + Object { + "sidebar-1": "
    +

    First Paragraph

    +
    +
    +

    Second Paragraph

    +
    ", + } + ` ); + } ); } ); /** diff --git a/packages/edit-post/src/components/sidebar/template/actions.js b/packages/edit-post/src/components/sidebar/template/actions.js index b7a0ccec82e7b1..968ead9b810012 100644 --- a/packages/edit-post/src/components/sidebar/template/actions.js +++ b/packages/edit-post/src/components/sidebar/template/actions.js @@ -67,7 +67,7 @@ function PostTemplateActions() {
    { isModalOpen && ( { setIsModalOpen( false ); @@ -98,18 +98,27 @@ function PostTemplateActions() { setIsModalOpen( false ); } } > - + + + + + +