From 80f8d9e6a2770a390c87a0c9d1e6d62d54f7e2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=B3=C5=82kowski?= Date: Mon, 8 Jan 2024 14:09:07 +0100 Subject: [PATCH 001/296] Modules: Load the import map polyfill when needed (#57256) * Modules: Load the import map polyfill when needed * Update lib/experimental/modules/class-gutenberg-modules.php Co-authored-by: Felix Arntz --------- Co-authored-by: Felix Arntz --- .../modules/class-gutenberg-modules.php | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/experimental/modules/class-gutenberg-modules.php b/lib/experimental/modules/class-gutenberg-modules.php index 0a2da03545776..5aa012f8e2f2c 100644 --- a/lib/experimental/modules/class-gutenberg-modules.php +++ b/lib/experimental/modules/class-gutenberg-modules.php @@ -152,15 +152,21 @@ public static function print_module_preloads() { * import maps (https://github.com/guybedford/es-module-shims/issues/406). */ public static function print_import_map_polyfill() { - $import_map = self::get_import_map(); - if ( ! empty( $import_map['imports'] ) ) { - wp_print_script_tag( - array( - 'src' => gutenberg_url( '/build/modules/importmap-polyfill.min.js' ), - 'defer' => true, - ) - ); - } + $test = 'HTMLScriptElement.supports && HTMLScriptElement.supports("importmap")'; + $src = gutenberg_url( '/build/modules/importmap-polyfill.min.js' ); + + echo ( + // Test presence of feature... + '' + ); } /** @@ -273,4 +279,4 @@ function gutenberg_dequeue_module( $module_identifier ) { add_action( $modules_position, array( 'Gutenberg_Modules', 'print_module_preloads' ) ); // Prints the script that loads the import map polyfill in the footer. -add_action( 'wp_footer', array( 'Gutenberg_Modules', 'print_import_map_polyfill' ), 11 ); +add_action( 'wp_head', array( 'Gutenberg_Modules', 'print_import_map_polyfill' ), 11 ); From 302c1487c0298a75b4a0094e77ec87256f15ccdf Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 8 Jan 2024 07:31:38 -0600 Subject: [PATCH 002/296] Font Library: unregister font collection (#54701) * remove font collection * add wp_unregister_font_collection function * format php * fix function comment * add tests for unregister_font_collection * removig unused variable * update wording on comments Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com> * keep track of unregistered font collection ids in an array * add tests to assure that get_font_collections returns an empty array if all the font collections were unregistered * fix tests * test the initial empty value of the font library collections * format * simplify unregistering of font collections and add _doing_it_wrong call * add translation domain * adding _doing_it_wrong if you are registering collections with the same id or unregistering a non existing collection id * updating tests * replace 6.4.0 by 6.5.0 in comments Co-authored-by: Vicente Canales <1157901+vcanales@users.noreply.github.com> * update version Co-authored-by: Grant Kinney * consolidate code as only one function to avoid code repetition * create base test case * format php * assertWPError * check that collection was not unregistered by mistake * calling parent test class mehtods * format php --------- Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com> Co-authored-by: Vicente Canales <1157901+vcanales@users.noreply.github.com> Co-authored-by: Grant Kinney --- .../font-library/class-wp-font-library.php | 50 +++++++++++++++-- .../fonts/font-library/font-library.php | 13 +++++ .../fonts/font-library/wpFontLibrary/base.php | 26 +++++++++ .../wpFontLibrary/getFontCollection.php | 8 +-- .../wpFontLibrary/getFontCollections.php | 26 +++------ .../wpFontLibrary/getFontsDir.php | 2 +- .../wpFontLibrary/getMimeTypes.php | 2 +- .../wpFontLibrary/registerFontCollection.php | 6 ++- .../wpFontLibrary/setUploadDir.php | 2 +- .../unregisterFontCollection.php | 54 +++++++++++++++++++ 10 files changed, 156 insertions(+), 33 deletions(-) create mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/base.php create mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php index 9320a554e510c..59ec5e93fa787 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -63,15 +63,57 @@ public static function get_expected_font_mime_types_per_php_version( $php_versio */ public static function register_font_collection( $config ) { $new_collection = new WP_Font_Collection( $config ); - - if ( isset( self::$collections[ $config['id'] ] ) ) { - return new WP_Error( 'font_collection_registration_error', 'Font collection already registered.' ); + if ( self::is_collection_registered( $config['id'] ) ) { + $error_message = sprintf( + /* translators: %s: Font collection id. */ + __( 'Font collection with id: "%s" is already registered.', 'default' ), + $config['id'] + ); + _doing_it_wrong( + __METHOD__, + $error_message, + '6.5.0' + ); + return new WP_Error( 'font_collection_registration_error', $error_message ); } - self::$collections[ $config['id'] ] = $new_collection; return $new_collection; } + /** + * Unregisters a previously registered font collection. + * + * @since 6.5.0 + * + * @param string $collection_id Font collection ID. + * @return bool True if the font collection was unregistered successfully and false otherwise. + */ + public static function unregister_font_collection( $collection_id ) { + if ( ! self::is_collection_registered( $collection_id ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Font collection id. */ + sprintf( __( 'Font collection "%s" not found.', 'default' ), $collection_id ), + '6.5.0' + ); + return false; + } + unset( self::$collections[ $collection_id ] ); + return true; + } + + /** + * Checks if a font collection is registered. + * + * @since 6.5.0 + * + * @param string $collection_id Font collection ID. + * @return bool True if the font collection is registered and false otherwise. + */ + private static function is_collection_registered( $collection_id ) { + return array_key_exists( $collection_id, self::$collections ); + } + /** * Gets all the font collections available. * diff --git a/lib/experimental/fonts/font-library/font-library.php b/lib/experimental/fonts/font-library/font-library.php index 709f63e9126cb..711a6bb40c282 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -60,6 +60,19 @@ function wp_register_font_collection( $config ) { } } +if ( ! function_exists( 'wp_unregister_font_collection' ) ) { + /** + * Unregisters a font collection from the Font Library. + * + * @since 6.5.0 + * + * @param string $collection_id The font collection ID. + */ + function wp_unregister_font_collection( $collection_id ) { + WP_Font_Library::unregister_font_collection( $collection_id ); + } + +} $default_font_collection = array( 'id' => 'default-font-collection', diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/base.php b/phpunit/tests/fonts/font-library/wpFontLibrary/base.php new file mode 100644 index 0000000000000..e8d970f5b3d39 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/base.php @@ -0,0 +1,26 @@ +getProperty( 'collections' ); + $property->setAccessible( true ); + $property->setValue( array() ); + } + + public function set_up() { + parent::set_up(); + $this->reset_font_collections(); + } + + public function tear_down() { + parent::tear_down(); + $this->reset_font_collections(); + } +} diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php index bfdb7258fa11a..00d5ca2dcb2e7 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php @@ -10,20 +10,16 @@ * * @covers WP_Font_Library::get_font_collection */ -class Tests_Fonts_WpFontLibrary_GetFontCollection extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_GetFontCollection extends WP_Font_Library_UnitTestCase { - public static function set_up_before_class() { + public function test_should_get_font_collection() { $my_font_collection_config = array( 'id' => 'my-font-collection', 'name' => 'My Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => path_join( __DIR__, 'my-font-collection-data.json' ), ); - wp_register_font_collection( $my_font_collection_config ); - } - - public function test_should_get_font_collection() { $font_collection = WP_Font_Library::get_font_collection( 'my-font-collection' ); $this->assertInstanceOf( 'WP_Font_Collection', $font_collection ); } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php index 97e66e64e8716..40eacba8e18c5 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php @@ -10,11 +10,13 @@ * * @covers WP_Font_Library::get_font_collections */ -class Tests_Fonts_WpFontLibrary_GetFontCollections extends WP_UnitTestCase { - - public static function set_up_before_class() { - $font_library = new WP_Font_Library(); +class Tests_Fonts_WpFontLibrary_GetFontCollections extends WP_Font_Library_UnitTestCase { + public function test_should_get_an_empty_list() { + $font_collections = WP_Font_Library::get_font_collections(); + $this->assertEmpty( $font_collections, 'Should return an empty array.' ); + } + public function test_should_get_mock_font_collection() { $my_font_collection_config = array( 'id' => 'my-font-collection', 'name' => 'My Font Collection', @@ -22,23 +24,11 @@ public static function set_up_before_class() { 'src' => path_join( __DIR__, 'my-font-collection-data.json' ), ); - $font_library::register_font_collection( $my_font_collection_config ); - } - - public function test_should_get_the_default_font_collection() { - $font_collections = WP_Font_Library::get_font_collections(); - $this->assertArrayHasKey( 'default-font-collection', $font_collections, 'Default Google Fonts collection should be registered' ); - $this->assertInstanceOf( 'WP_Font_Collection', $font_collections['default-font-collection'], 'The value of the array $font_collections[id] should be an instance of WP_Font_Collection class.' ); - } + WP_Font_Library::register_font_collection( $my_font_collection_config ); - public function test_should_get_the_right_number_of_collections() { $font_collections = WP_Font_Library::get_font_collections(); $this->assertNotEmpty( $font_collections, 'Sould return an array of font collections.' ); - $this->assertCount( 2, $font_collections, 'Should return an array with one font collection.' ); - } - - public function test_should_get_mock_font_collection() { - $font_collections = WP_Font_Library::get_font_collections(); + $this->assertCount( 1, $font_collections, 'Should return an array with one font collection.' ); $this->assertArrayHasKey( 'my-font-collection', $font_collections, 'The array should have the key of the registered font collection id.' ); $this->assertInstanceOf( 'WP_Font_Collection', $font_collections['my-font-collection'], 'The value of the array $font_collections[id] should be an instance of WP_Font_Collection class.' ); } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php index 4bbafc55a2147..1200200d7160b 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Library::get_fonts_dir */ -class Tests_Fonts_WpFontLibrary_GetFontsDir extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_GetFontsDir extends WP_Font_Library_UnitTestCase { public function test_get_fonts_dir() { $this->assertStringEndsWith( '/wp-content/fonts', WP_Font_Library::get_fonts_dir() ); diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php index 708134af69e92..485587060f16a 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getMimeTypes.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Family_Utils::get_expected_font_mime_types_per_php_version */ -class Tests_Fonts_WpFontsFamilyUtils_GetMimeTypes extends WP_UnitTestCase { +class Tests_Fonts_WpFontsFamilyUtils_GetMimeTypes extends WP_Font_Library_UnitTestCase { /** * diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php index 6bc5fbb8161ce..2569830f6bf2a 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Library::register_font_collection */ -class Tests_Fonts_WpFontLibrary_RegisterFontCollection extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_RegisterFontCollection extends WP_Font_Library_UnitTestCase { public function test_should_register_font_collection() { $config = array( @@ -70,8 +70,10 @@ public function test_should_return_error_if_id_is_repeated() { $collection1 = WP_Font_Library::register_font_collection( $config1 ); $this->assertInstanceOf( 'WP_Font_Collection', $collection1, 'A collection should be registered.' ); + // Expects a _doing_it_wrong notice. + $this->setExpectedIncorrectUsage( 'WP_Font_Library::register_font_collection' ); // Try to register a second collection with same id. $collection2 = WP_Font_Library::register_font_collection( $config2 ); - $this->assertWPError( $collection2, 'Second collection with the same id should fail.' ); + $this->assertWPError( $collection2, 'A WP_Error should be returned.' ); } } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php index daa4c84aad900..29d481d8afd6b 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php @@ -10,7 +10,7 @@ * * @covers WP_Font_Library::set_upload_dir */ -class Tests_Fonts_WpFontLibrary_SetUploadDir extends WP_UnitTestCase { +class Tests_Fonts_WpFontLibrary_SetUploadDir extends WP_Font_Library_UnitTestCase { public function test_should_set_fonts_upload_dir() { $defaults = array( diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php new file mode 100644 index 0000000000000..e6e16956814fb --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php @@ -0,0 +1,54 @@ + 'mock-font-collection-1', + 'name' => 'Mock Collection to be unregistered', + 'description' => 'A mock font collection to be unregistered.', + 'src' => 'my-collection-data.json', + ); + WP_Font_Library::register_font_collection( $config ); + + $config = array( + 'id' => 'mock-font-collection-2', + 'name' => 'Mock Collection', + 'description' => 'A mock font collection.', + 'src' => 'my-mock-data.json', + ); + WP_Font_Library::register_font_collection( $config ); + + // Unregister mock font collection. + WP_Font_Library::unregister_font_collection( 'mock-font-collection-1' ); + $collections = WP_Font_Library::get_font_collections(); + $this->assertArrayNotHasKey( 'mock-font-collection-1', $collections, 'Font collection was not unregistered.' ); + $this->assertArrayHasKey( 'mock-font-collection-2', $collections, 'Font collection was unregistered by mistake.' ); + + // Unregisters remaining mock font collection. + WP_Font_Library::unregister_font_collection( 'mock-font-collection-2' ); + $collections = WP_Font_Library::get_font_collections(); + $this->assertArrayNotHasKey( 'mock-font-collection-2', $collections, 'Mock font collection was not unregistered.' ); + + // Checks that all font collections were unregistered. + $this->assertEmpty( $collections, 'Font collections were not unregistered.' ); + } + + public function unregister_non_existing_collection() { + // Unregisters non existing font collection. + WP_Font_Library::unregister_font_collection( 'non-existing-collection' ); + $collections = WP_Font_Library::get_font_collections(); + $this->assertEmpty( $collections, 'Should not be registered collections.' ); + } +} From 6d4e6ddfccdaebbb75337564c331c402bb3b07d0 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 8 Jan 2024 08:12:23 -0600 Subject: [PATCH 003/296] Font Library: singularize install font families endpoint (#57569) * singularize install font families endpoint to accept only one font family instead of many in the same request * lint php * frontend changes to send only one font family per install request * restrict the upload of local font files to variants of only one font family * rename function to make it singular * fixing strings for translations * fix permission_callback value updated by mistake --- ...class-wp-rest-font-families-controller.php | 149 ++++----- .../font-library-modal/context.js | 14 +- .../font-library-modal/font-collection.js | 9 +- .../font-library-modal/local-fonts.js | 15 +- .../font-library-modal/resolvers.js | 2 +- .../font-library-modal/utils/index.js | 30 +- .../test/makeFormDataFromFontFamilies.spec.js | 62 ---- .../test/makeFormDataFromFontFamily.spec.js | 58 ++++ .../installFonts.php | 287 ++++++------------ 9 files changed, 258 insertions(+), 368 deletions(-) delete mode 100644 packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js create mode 100644 packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php index c92a0d2697f31..0147d80b7bde9 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php @@ -44,8 +44,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => function () { - return true;}, + 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), ), ) ); @@ -59,7 +58,7 @@ public function register_routes() { 'callback' => array( $this, 'install_fonts' ), 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), 'args' => array( - 'font_families' => array( + 'font_family_settings' => array( 'required' => true, 'type' => 'string', 'validate_callback' => array( $this, 'validate_install_font_families' ), @@ -92,85 +91,61 @@ public function register_routes() { * @param array $files Files to install. * @return array $error_messages Array of error messages. */ - private function get_validation_errors( $font_families, $files ) { + private function get_validation_errors( $font_family_settings, $files ) { $error_messages = array(); - if ( ! is_array( $font_families ) ) { - $error_messages[] = __( 'font_families should be an array of font families.', 'gutenberg' ); + if ( ! is_array( $font_family_settings ) ) { + $error_messages[] = __( 'font_family_settings should be a font family definition.', 'gutenberg' ); return $error_messages; } - // Checks if there is at least one font family. - if ( count( $font_families ) < 1 ) { - $error_messages[] = __( 'font_families should have at least one font family definition.', 'gutenberg' ); + if ( + ! isset( $font_family_settings['slug'] ) || + ! isset( $font_family_settings['name'] ) || + ! isset( $font_family_settings['fontFamily'] ) + ) { + $error_messages[] = __( 'Font family should have slug, name and fontFamily properties defined.', 'gutenberg' ); + return $error_messages; } - for ( $family_index = 0; $family_index < count( $font_families ); $family_index++ ) { - $font_family = $font_families[ $family_index ]; - - if ( - ! isset( $font_family['slug'] ) || - ! isset( $font_family['name'] ) || - ! isset( $font_family['fontFamily'] ) - ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have slug, name and fontFamily properties defined.', 'gutenberg' ), - $family_index - ); + if ( isset( $font_family_settings['fontFace'] ) ) { + if ( ! is_array( $font_family_settings['fontFace'] ) ) { + $error_messages[] = __( 'Font family should have fontFace property defined as an array.', 'gutenberg' ); } - if ( isset( $font_family['fontFace'] ) ) { - if ( ! is_array( $font_family['fontFace'] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have fontFace property defined as an array.', 'gutenberg' ), - $family_index - ); - continue; - } + if ( count( $font_family_settings['fontFace'] ) < 1 ) { + $error_messages[] = __( 'Font family should have at least one font face definition.', 'gutenberg' ); + } - if ( count( $font_family['fontFace'] ) < 1 ) { - $error_messages[] = sprintf( - // translators: 1: font family index. - __( 'Font family [%s] should have at least one font face definition.', 'gutenberg' ), - $family_index - ); - } + if ( ! empty( $font_family_settings['fontFace'] ) ) { + for ( $face_index = 0; $face_index < count( $font_family_settings['fontFace'] ); $face_index++ ) { - if ( ! empty( $font_family['fontFace'] ) ) { - for ( $face_index = 0; $face_index < count( $font_family['fontFace'] ); $face_index++ ) { + $font_face = $font_family_settings['fontFace'][ $face_index ]; + if ( ! isset( $font_face['fontWeight'] ) || ! isset( $font_face['fontStyle'] ) ) { + $error_messages[] = sprintf( + // translators: font face index. + __( 'Font family Font face [%1$s] should have fontWeight and fontStyle properties defined.', 'gutenberg' ), + $face_index + ); + } - $font_face = $font_family['fontFace'][ $face_index ]; - if ( ! isset( $font_face['fontWeight'] ) || ! isset( $font_face['fontStyle'] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] should have fontWeight and fontStyle properties defined.', 'gutenberg' ), - $family_index, - $face_index - ); - } + if ( isset( $font_face['downloadFromUrl'] ) && isset( $font_face['uploadedFile'] ) ) { + $error_messages[] = sprintf( + // translators: font face index. + __( 'Font family Font face [%1$s] should have only one of the downloadFromUrl or uploadedFile properties defined and not both.', 'gutenberg' ), + $face_index + ); + } - if ( isset( $font_face['downloadFromUrl'] ) && isset( $font_face['uploadedFile'] ) ) { + if ( isset( $font_face['uploadedFile'] ) ) { + if ( ! isset( $files[ $font_face['uploadedFile'] ] ) ) { $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] should have only one of the downloadFromUrl or uploadedFile properties defined and not both.', 'gutenberg' ), - $family_index, + // translators: font face index. + __( 'Font family Font face [%1$s] file is not defined in the request files.', 'gutenberg' ), $face_index ); } - - if ( isset( $font_face['uploadedFile'] ) ) { - if ( ! isset( $files[ $font_face['uploadedFile'] ] ) ) { - $error_messages[] = sprintf( - // translators: 1: font family index, 2: font face index. - __( 'Font family [%1$s] Font face [%2$s] file is not defined in the request files.', 'gutenberg' ), - $family_index, - $face_index - ); - } - } } } } @@ -189,9 +164,9 @@ private function get_validation_errors( $font_families, $files ) { * @return true|WP_Error True if the parameter is valid, WP_Error otherwise. */ public function validate_install_font_families( $param, $request ) { - $font_families = json_decode( $param, true ); - $files = $request->get_file_params(); - $error_messages = $this->get_validation_errors( $font_families, $files ); + $font_family_settings = json_decode( $param, true ); + $files = $request->get_file_params(); + $error_messages = $this->get_validation_errors( $font_family_settings, $files ); if ( empty( $error_messages ) ) { return true; @@ -327,17 +302,15 @@ private function has_write_permission() { * * @since 6.5.0 * - * @param array[] $font_families Font families to install. + * @param array[] $font_family_settings Font family definition. * @return bool Whether the request needs write permissions. */ - private function needs_write_permission( $font_families ) { - foreach ( $font_families as $font ) { - if ( isset( $font['fontFace'] ) ) { - foreach ( $font['fontFace'] as $face ) { - // If the font is being downloaded from a URL or uploaded, it needs write permissions. - if ( isset( $face['downloadFromUrl'] ) || isset( $face['uploadedFile'] ) ) { - return true; - } + private function needs_write_permission( $font_family_settings ) { + if ( isset( $font_family_settings['fontFace'] ) ) { + foreach ( $font_family_settings['fontFace'] as $face ) { + // If the font is being downloaded from a URL or uploaded, it needs write permissions. + if ( isset( $face['downloadFromUrl'] ) || isset( $face['uploadedFile'] ) ) { + return true; } } } @@ -358,20 +331,20 @@ private function needs_write_permission( $font_families ) { */ public function install_fonts( $request ) { // Get new fonts to install. - $fonts_param = $request->get_param( 'font_families' ); + $font_family_settings = $request->get_param( 'font_family_settings' ); /* * As this is receiving form data, the font families are encoded as a string. * The form data is used because local fonts need to use that format to * attach the files in the request. */ - $fonts_to_install = json_decode( $fonts_param, true ); + $font_family_settings = json_decode( $font_family_settings, true ); $successes = array(); $errors = array(); $response_status = 200; - if ( empty( $fonts_to_install ) ) { + if ( empty( $font_family_settings ) ) { $errors[] = new WP_Error( 'no_fonts_to_install', __( 'No fonts to install', 'gutenberg' ) @@ -379,7 +352,7 @@ public function install_fonts( $request ) { $response_status = 400; } - if ( $this->needs_write_permission( $fonts_to_install ) ) { + if ( $this->needs_write_permission( $font_family_settings ) ) { $upload_dir = WP_Font_Library::get_fonts_dir(); if ( ! $this->has_upload_directory() ) { if ( ! wp_mkdir_p( $upload_dir ) ) { @@ -415,15 +388,13 @@ public function install_fonts( $request ) { } // Get uploaded files (used when installing local fonts). - $files = $request->get_file_params(); - foreach ( $fonts_to_install as $font_data ) { - $font = new WP_Font_Family( $font_data ); - $result = $font->install( $files ); - if ( is_wp_error( $result ) ) { - $errors[] = $result; - } else { - $successes[] = $result; - } + $files = $request->get_file_params(); + $font = new WP_Font_Family( $font_family_settings ); + $result = $font->install( $files ); + if ( is_wp_error( $result ) ) { + $errors[] = $result; + } else { + $successes[] = $result; } $data = array( diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/context.js b/packages/edit-site/src/components/global-styles/font-library-modal/context.js index 58b8621adcf0c..e0749845788d6 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/context.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/context.js @@ -14,7 +14,7 @@ import { * Internal dependencies */ import { - fetchInstallFonts, + fetchInstallFont, fetchUninstallFonts, fetchFontCollections, fetchFontCollection, @@ -26,7 +26,7 @@ import { mergeFontFamilies, loadFontFaceInBrowser, getDisplaySrcFromFontFace, - makeFormDataFromFontFamilies, + makeFormDataFromFontFamily, } from './utils'; import { toggleFont } from './utils/toggleFont'; import getIntersectingFontFaces from './utils/get-intersecting-font-faces'; @@ -192,19 +192,19 @@ function FontLibraryProvider( { children } ) { return getActivatedFontsOutline( source )[ slug ] || []; }; - async function installFonts( fonts ) { + async function installFont( font ) { setIsInstalling( true ); try { // Prepare formData to install. - const formData = makeFormDataFromFontFamilies( fonts ); + const formData = makeFormDataFromFontFamily( font ); // Install the fonts (upload the font files to the server and create the post in the database). - const response = await fetchInstallFonts( formData ); + const response = await fetchInstallFont( formData ); const fontsInstalled = response?.successes || []; // Get intersecting font faces between the fonts we tried to installed and the fonts that were installed // (to avoid activating a non installed font). const fontToBeActivated = getIntersectingFontFaces( fontsInstalled, - fonts + [ font ] ); // Activate the font families (add the font families to the global styles). activateCustomFontFamilies( fontToBeActivated ); @@ -358,7 +358,7 @@ function FontLibraryProvider( { children } ) { isFontActivated, getFontFacesActivated, loadFontFaceAsset, - installFonts, + installFont, uninstallFont, toggleActivateFont, getAvailableFontsOutline, diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index 062ad232e3ca9..fc39e2e009653 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -54,7 +54,7 @@ function FontCollection( { id } ) { const [ renderConfirmDialog, setRenderConfirmDialog ] = useState( requiresPermission && ! getGoogleFontsPermissionFromStorage() ); - const { collections, getFontCollection, installFonts } = + const { collections, getFontCollection, installFont } = useContext( FontLibraryContext ); const selectedCollection = collections.find( ( collection ) => collection.id === id @@ -92,6 +92,11 @@ function FontCollection( { id } ) { setNotice( null ); }, [ id ] ); + useEffect( () => { + // If the selected fonts change, reset the selected fonts to install + setFontsToInstall( [] ); + }, [ selectedFont ] ); + // Reset notice after 5 seconds useEffect( () => { if ( notice && notice?.duration !== 0 ) { @@ -149,7 +154,7 @@ function FontCollection( { id } ) { }; const handleInstall = async () => { - const response = await installFonts( fontsToInstall ); + const response = await installFont( fontsToInstall[ 0 ] ); const installNotice = getNoticeFromInstallResponse( response ); setNotice( installNotice ); resetFontsToInstall(); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js index 4030dcfb69a77..d4221b420cb61 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/local-fonts.js @@ -29,7 +29,7 @@ import { unlock } from '../../../lock-unlock'; const { ProgressBar } = unlock( componentsPrivateApis ); function LocalFonts() { - const { installFonts } = useContext( FontLibraryContext ); + const { installFont } = useContext( FontLibraryContext ); const [ notice, setNotice ] = useState( null ); const [ isUploading, setIsUploading ] = useState( false ); const supportedFormats = @@ -153,7 +153,18 @@ function LocalFonts() { */ const handleInstall = async ( fontFaces ) => { const fontFamilies = makeFamiliesFromFaces( fontFaces ); - const response = await installFonts( fontFamilies ); + + if ( fontFamilies.length > 1 ) { + setNotice( { + type: 'error', + message: __( + 'Variants from only one font family can be uploaded at a time.' + ), + } ); + return; + } + + const response = await installFont( fontFamilies[ 0 ] ); const installNotice = getNoticeFromInstallResponse( response ); setNotice( installNotice ); setIsUploading( false ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js index 0ab4a7ba74224..2e7f413a6fa45 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/resolvers.js @@ -7,7 +7,7 @@ */ import apiFetch from '@wordpress/api-fetch'; -export async function fetchInstallFonts( data ) { +export async function fetchInstallFont( data ) { const config = { path: '/wp/v2/font-families', method: 'POST', diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js index 69db09d49a0ce..2874dd446efb4 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js @@ -130,16 +130,21 @@ export function getDisplaySrcFromFontFace( input, urlPrefix ) { return src; } -export function makeFormDataFromFontFamilies( fontFamilies ) { +export function makeFormDataFromFontFamily( fontFamily ) { const formData = new FormData(); - const newFontFamilies = fontFamilies.map( ( family, familyIndex ) => { - const { kebabCase } = unlock( componentsPrivateApis ); - family.slug = kebabCase( family.slug ); - if ( family?.fontFace ) { - family.fontFace = family.fontFace.map( ( face, faceIndex ) => { + const { kebabCase } = unlock( componentsPrivateApis ); + + const newFontFamily = { + ...fontFamily, + slug: kebabCase( fontFamily.slug ), + }; + + if ( newFontFamily?.fontFace ) { + const newFontFaces = newFontFamily.fontFace.map( + ( face, faceIndex ) => { if ( face.file ) { // Slugified file name because the it might contain spaces or characters treated differently on the server. - const fileId = `file-${ familyIndex }-${ faceIndex }`; + const fileId = `file-${ faceIndex }`; // Add the files to the formData formData.append( fileId, face.file, face.file.name ); // remove the file object from the face object the file is referenced by the uploadedFile key @@ -151,10 +156,11 @@ export function makeFormDataFromFontFamilies( fontFamilies ) { return newFace; } return face; - } ); - } - return family; - } ); - formData.append( 'font_families', JSON.stringify( newFontFamilies ) ); + } + ); + newFontFamily.fontFace = newFontFaces; + } + + formData.append( 'font_family_settings', JSON.stringify( newFontFamily ) ); return formData; } diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js deleted file mode 100644 index 4adae7889cc5e..0000000000000 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamilies.spec.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Internal dependencies - */ -import { makeFormDataFromFontFamilies } from '../index'; - -/* global File */ - -describe( 'makeFormDataFromFontFamilies', () => { - it( 'should process fontFamilies and return FormData', () => { - const mockFontFamilies = [ - { - slug: 'bebas', - name: 'Bebas', - fontFamily: 'Bebas', - fontFace: [ - { - file: new File( [ 'content' ], 'test-font1.woff2' ), - fontWeight: '500', - fontStyle: 'normal', - }, - { - file: new File( [ 'content' ], 'test-font2.woff2' ), - fontWeight: '400', - fontStyle: 'normal', - }, - ], - }, - ]; - - const formData = makeFormDataFromFontFamilies( mockFontFamilies ); - - expect( formData instanceof FormData ).toBeTruthy(); - - // Check if files are added correctly - expect( formData.get( 'file-0-0' ).name ).toBe( 'test-font1.woff2' ); - expect( formData.get( 'file-0-1' ).name ).toBe( 'test-font2.woff2' ); - - // Check if 'fontFamilies' key in FormData is correct - const expectedFontFamilies = [ - { - fontFace: [ - { - fontWeight: '500', - fontStyle: 'normal', - uploadedFile: 'file-0-0', - }, - { - fontWeight: '400', - fontStyle: 'normal', - uploadedFile: 'file-0-1', - }, - ], - slug: 'bebas', - name: 'Bebas', - fontFamily: 'Bebas', - }, - ]; - expect( JSON.parse( formData.get( 'font_families' ) ) ).toEqual( - expectedFontFamilies - ); - } ); -} ); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js new file mode 100644 index 0000000000000..9f38903c89759 --- /dev/null +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/test/makeFormDataFromFontFamily.spec.js @@ -0,0 +1,58 @@ +/** + * Internal dependencies + */ +import { makeFormDataFromFontFamily } from '../index'; + +/* global File */ + +describe( 'makeFormDataFromFontFamily', () => { + it( 'should process fontFamilies and return FormData', () => { + const mockFontFamily = { + slug: 'bebas', + name: 'Bebas', + fontFamily: 'Bebas', + fontFace: [ + { + file: new File( [ 'content' ], 'test-font1.woff2' ), + fontWeight: '500', + fontStyle: 'normal', + }, + { + file: new File( [ 'content' ], 'test-font2.woff2' ), + fontWeight: '400', + fontStyle: 'normal', + }, + ], + }; + + const formData = makeFormDataFromFontFamily( mockFontFamily ); + + expect( formData instanceof FormData ).toBeTruthy(); + + // Check if files are added correctly + expect( formData.get( 'file-0' ).name ).toBe( 'test-font1.woff2' ); + expect( formData.get( 'file-1' ).name ).toBe( 'test-font2.woff2' ); + + // Check if 'fontFamilies' key in FormData is correct + const expectedFontFamily = { + fontFace: [ + { + fontWeight: '500', + fontStyle: 'normal', + uploadedFile: 'file-0', + }, + { + fontWeight: '400', + fontStyle: 'normal', + uploadedFile: 'file-1', + }, + ], + slug: 'bebas', + name: 'Bebas', + fontFamily: 'Bebas', + }; + expect( JSON.parse( formData.get( 'font_family_settings' ) ) ).toEqual( + expectedFontFamily + ); + } ); +} ); diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php index d35022306f4e6..98c1cb6e13fe5 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/installFonts.php @@ -21,10 +21,10 @@ class Tests_Fonts_WPRESTFontFamiliesController_InstallFonts extends WP_REST_Font * @param array $files Font files to install. * @param array $expected_response Expected response data. */ - public function test_install_fonts( $font_families, $files, $expected_response ) { - $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); - $font_families_json = json_encode( $font_families ); - $install_request->set_param( 'font_families', $font_families_json ); + public function test_install_fonts( $font_family_settings, $files, $expected_response ) { + $install_request = new WP_REST_Request( 'POST', '/wp/v2/font-families' ); + $font_family_json = json_encode( $font_family_settings ); + $install_request->set_param( 'font_family_settings', $font_family_json ); $install_request->set_file_params( $files ); $response = rest_get_server()->dispatch( $install_request ); $data = $response->get_data(); @@ -68,38 +68,22 @@ public function data_install_fonts() { return array( 'google_fonts_to_download' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', ), ), ), - 'files' => array(), - 'expected_response' => array( + 'files' => array(), + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Piazzolla', @@ -114,55 +98,27 @@ public function data_install_fonts() { ), ), ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => '/wp-content/fonts/montserrat_normal_100.ttf', - ), - ), - ), ), 'errors' => array(), ), ), 'google_fonts_to_use_as_is' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', ), ), ), - 'files' => array(), - 'expected_response' => array( + 'files' => array(), + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Piazzolla', @@ -177,35 +133,19 @@ public function data_install_fonts() { ), ), ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => 'http://fonts.gstatic.com/s/montserrat/v25/JTUHjIg1_i6t8kCHKm4532VJOt5-QNFgpCtr6Uw-Y3tcoqK5.ttf', - - ), - ), - ), ), 'errors' => array(), ), ), 'fonts_without_font_faces' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Arial', - 'slug' => 'arial', - 'name' => 'Arial', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Arial', + 'slug' => 'arial', + 'name' => 'Arial', ), - 'files' => array(), - 'expected_response' => array( + 'files' => array(), + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Arial', @@ -218,35 +158,20 @@ public function data_install_fonts() { ), 'fonts_with_local_fonts_assets' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files0', - ), - ), - ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'uploadedFile' => 'files1', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files0', ), ), ), - 'files' => array( + 'files' => array( 'files0' => array( 'name' => 'piazzola1.ttf', 'type' => 'font/ttf', @@ -262,7 +187,7 @@ public function data_install_fonts() { 'size' => 123, ), ), - 'expected_response' => array( + 'expected_response' => array( 'successes' => array( array( 'fontFamily' => 'Piazzolla', @@ -277,20 +202,6 @@ public function data_install_fonts() { ), ), ), - array( - 'fontFamily' => 'Montserrat', - 'slug' => 'montserrat', - 'name' => 'Montserrat', - 'fontFace' => array( - array( - 'fontFamily' => 'Montserrat', - 'fontStyle' => 'normal', - 'fontWeight' => '100', - 'src' => '/wp-content/fonts/montserrat_normal_100.ttf', - ), - ), - ), - ), 'errors' => array(), ), @@ -325,15 +236,15 @@ public function data_install_with_improper_inputs() { return array( 'not a font families array' => array( - 'font_families' => 'This is not an array', + 'font_family_settings' => 'This is not an array', ), 'empty array' => array( - 'font_families' => array(), + 'font_family_settings' => array(), ), 'without slug' => array( - 'font_families' => array( + 'font_family_settings' => array( array( 'fontFamily' => 'Piazzolla', 'name' => 'Piazzolla', @@ -342,63 +253,55 @@ public function data_install_with_improper_inputs() { ), 'with improper font face property' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => 'This is not an array', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => 'This is not an array', ), ), 'with empty font face property' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array(), - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array(), ), ), 'fontface referencing uploaded file without uploaded files' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files0', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files0', ), ), ), - 'files' => array(), + 'files' => array(), ), 'fontface referencing uploaded file without uploaded files' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'name' => 'Piazzolla', - 'slug' => 'piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'uploadedFile' => 'files666', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'name' => 'Piazzolla', + 'slug' => 'piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'uploadedFile' => 'files666', ), ), ), - 'files' => array( + 'files' => array( 'files0' => array( 'name' => 'piazzola1.ttf', 'type' => 'font/ttf', @@ -410,20 +313,18 @@ public function data_install_with_improper_inputs() { ), 'fontface with incompatible properties (downloadFromUrl and uploadedFile together)' => array( - 'font_families' => array( - array( - 'fontFamily' => 'Piazzolla', - 'slug' => 'piazzolla', - 'name' => 'Piazzolla', - 'fontFace' => array( - array( - 'fontFamily' => 'Piazzolla', - 'fontStyle' => 'normal', - 'fontWeight' => '400', - 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', - 'uploadedFile' => 'files0', - ), + 'font_family_settings' => array( + 'fontFamily' => 'Piazzolla', + 'slug' => 'piazzolla', + 'name' => 'Piazzolla', + 'fontFace' => array( + array( + 'fontFamily' => 'Piazzolla', + 'fontStyle' => 'normal', + 'fontWeight' => '400', + 'src' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'downloadFromUrl' => 'http://fonts.gstatic.com/s/piazzolla/v33/N0b72SlTPu5rIkWIZjVgI-TckS03oGpPETyEJ88Rbvi0_TzOzKcQhZqx3gX9BRy5m5M.ttf', + 'uploadedFile' => 'files0', ), ), ), From 73474eba696b897627a0f7b3dd84a646e294e690 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:21:44 +0900 Subject: [PATCH 004/296] Add Template Modal: Update scroll related layout (#57617) --- packages/edit-site/src/components/add-new-template/style.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/add-new-template/style.scss b/packages/edit-site/src/components/add-new-template/style.scss index b1c2b669e24ce..4881745c92afa 100644 --- a/packages/edit-site/src/components/add-new-template/style.scss +++ b/packages/edit-site/src/components/add-new-template/style.scss @@ -39,7 +39,8 @@ .edit-site-custom-template-modal__suggestions_list { @include break-small() { - overflow: scroll; + max-height: $grid-unit-70 * 4; // Height of four buttons + overflow-y: auto; } &__list-item { From 1b5ebca728074569e1ddff9928badcc882627e1c Mon Sep 17 00:00:00 2001 From: James Koster Date: Mon, 8 Jan 2024 15:53:35 +0000 Subject: [PATCH 005/296] Data views: Make title display in grid views consistent (#57553) --- packages/dataviews/src/style.scss | 26 ++++++++++++------- .../edit-site/src/components/list/style.scss | 5 ---- .../src/components/page-pages/index.js | 2 +- .../src/components/page-pages/style.scss | 6 ----- .../page-patterns/dataviews-patterns.js | 5 +++- .../src/components/page-templates/index.js | 2 +- 6 files changed, 22 insertions(+), 24 deletions(-) diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index edf1500d2cc5a..5a2bccdef68f2 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -149,10 +149,22 @@ } .dataviews-view-grid__card { - h3 { // Todo: A better way to target this - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + .dataviews-view-grid__primary-field { + .dataviews-view-grid__title-field { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + font-size: $default-font-size; + width: 100%; + } + + .dataviews-view-grid__title-field a, + button.dataviews-view-grid__title-field { + font-weight: 500; + color: $gray-900; + text-decoration: none; + } } } @@ -173,12 +185,6 @@ .dataviews-view-grid__primary-field { min-height: $grid-unit-30; - - a { - color: $gray-900; - text-decoration: none; - font-weight: 500; - } } .dataviews-view-grid__fields { diff --git a/packages/edit-site/src/components/list/style.scss b/packages/edit-site/src/components/list/style.scss index 9d6e0f0f1d6e2..cfc65252c8e68 100644 --- a/packages/edit-site/src/components/list/style.scss +++ b/packages/edit-site/src/components/list/style.scss @@ -186,8 +186,3 @@ display: block; color: $gray-700; } - -.edit-site-list-title__customized-info { - font-size: $default-font-size; - font-weight: 500; -} diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 164b4daf8603c..8729922faabea 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -222,7 +222,7 @@ export default function PagePages() { { [ LAYOUT_TABLE, LAYOUT_GRID ].includes( view.type diff --git a/packages/edit-site/src/components/page-pages/style.scss b/packages/edit-site/src/components/page-pages/style.scss index 35ac8273dc555..933fdadb8d070 100644 --- a/packages/edit-site/src/components/page-pages/style.scss +++ b/packages/edit-site/src/components/page-pages/style.scss @@ -3,9 +3,3 @@ width: $grid-unit-40; height: $grid-unit-40; } - - -.edit-site-page-pages__list-view-title-field { - font-size: $default-font-size; - font-weight: 500; -} diff --git a/packages/edit-site/src/components/page-patterns/dataviews-patterns.js b/packages/edit-site/src/components/page-patterns/dataviews-patterns.js index bf18e0ffed111..ad474d882cfcf 100644 --- a/packages/edit-site/src/components/page-patterns/dataviews-patterns.js +++ b/packages/edit-site/src/components/page-patterns/dataviews-patterns.js @@ -199,7 +199,9 @@ function Title( { item, categoryId } ) { ) } { item.type === PATTERN_TYPES.theme ? ( - item.title + + { item.title } + ) : ( diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index 1aaf1e153d0c5..c0e0289311db6 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -103,7 +103,7 @@ function TemplateTitle( { item, viewType } ) { return ( - + Date: Mon, 8 Jan 2024 16:01:18 +0000 Subject: [PATCH 006/296] Update Table layout design details (#57644) --- packages/dataviews/src/pagination.js | 4 +-- packages/dataviews/src/style.scss | 30 +++++++++++++++---- packages/dataviews/src/view-table.js | 8 +++-- .../src/components/page-pages/style.scss | 5 ++-- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/dataviews/src/pagination.js b/packages/dataviews/src/pagination.js index 21aeda8a602a1..2c9cade42d89b 100644 --- a/packages/dataviews/src/pagination.js +++ b/packages/dataviews/src/pagination.js @@ -23,8 +23,8 @@ const Pagination = memo( function Pagination( { totalPages !== 1 && ( diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index 5a2bccdef68f2..049f006dd97bd 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -88,6 +88,11 @@ td:first-child, th:first-child { padding-left: $grid-unit-40; + + .dataviews-table-header-button, + .dataviews-table-header { + margin-left: - #{$grid-unit-10}; + } } td:last-child, @@ -112,18 +117,27 @@ th { position: sticky; top: -1px; - background-color: lighten($gray-100, 4%); + background-color: $white; box-shadow: inset 0 -#{$border-width} 0 $gray-100; - border-top: 1px solid $gray-100; - padding-top: $grid-unit-05; - padding-bottom: $grid-unit-05; + padding-top: $grid-unit-10; + padding-bottom: $grid-unit-10; z-index: 1; + font-size: 11px; + text-transform: uppercase; + font-weight: 500; + padding-left: $grid-unit-05; } } .dataviews-table-header-button { - padding: 0; - gap: $grid-unit-05; + padding: $grid-unit-05 $grid-unit-10; + font-size: 11px; + text-transform: uppercase; + font-weight: 500; + + &:not(:hover) { + color: $gray-900; + } span { speak: none; @@ -133,6 +147,10 @@ } } } + + .dataviews-table-header { + padding-left: $grid-unit-05; + } } .dataviews-grid-view { diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js index 083931bb5203e..0be2ee6767ddf 100644 --- a/packages/dataviews/src/view-table.js +++ b/packages/dataviews/src/view-table.js @@ -91,8 +91,8 @@ const HeaderMenu = forwardRef( function HeaderMenu( + { showAddPageModal && ( + + ) } + + } > Date: Wed, 10 Jan 2024 17:57:07 +0900 Subject: [PATCH 034/296] Image Block: Change upload icon label (#57704) --- packages/block-library/src/image/image.js | 2 +- test/e2e/specs/editor/blocks/image.spec.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index ea12457c2585d..d8788fde4844f 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -459,7 +459,7 @@ export default function Image( { diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js index adeabc860c834..9080a6dc19402 100644 --- a/test/e2e/specs/editor/blocks/image.spec.js +++ b/test/e2e/specs/editor/blocks/image.spec.js @@ -696,7 +696,9 @@ test.describe( 'Image', () => { await expect( linkDom ).toHaveAttribute( 'href', url ); } ); - test( 'should upload external image', async ( { editor } ) => { + test( 'should upload external image to media library', async ( { + editor, + } ) => { await editor.insertBlock( { name: 'core/image', attributes: { @@ -704,7 +706,7 @@ test.describe( 'Image', () => { }, } ); - await editor.clickBlockToolbarButton( 'Upload external image' ); + await editor.clickBlockToolbarButton( 'Upload image to media library' ); const imageBlock = editor.canvas.locator( 'role=document[name="Block: Image"i]' From 3eb3eca08a90c36603ade6a754c1ab2fc4e6d92a Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 10 Jan 2024 10:18:54 +0100 Subject: [PATCH 035/296] Use full text instead of abbreviation for min height setting. (#57680) --- .../src/components/global-styles/dimensions-panel.js | 4 ++-- packages/block-editor/src/components/height-control/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/global-styles/dimensions-panel.js b/packages/block-editor/src/components/global-styles/dimensions-panel.js index 47b5bd329725a..47e50aa515e3c 100644 --- a/packages/block-editor/src/components/global-styles/dimensions-panel.js +++ b/packages/block-editor/src/components/global-styles/dimensions-panel.js @@ -603,7 +603,7 @@ export default function DimensionsPanel( { { showMinHeightControl && ( diff --git a/packages/block-editor/src/components/height-control/README.md b/packages/block-editor/src/components/height-control/README.md index 67b52f1d56f9b..9be1741e8cdd8 100644 --- a/packages/block-editor/src/components/height-control/README.md +++ b/packages/block-editor/src/components/height-control/README.md @@ -43,7 +43,7 @@ A callback function that handles the application of the height value. - **Type:** `String` - **Default:** `'Height'` -A label for the height control. This is useful when using the height control for a feature that is controlled in the same way as height, but requires a different label. For example, "Min. height". +A label for the height control. This is useful when using the height control for a feature that is controlled in the same way as height, but requires a different label. For example, "Minimum height". ## Related components From 4c9b8cc09fc57f5d62b740b5eef3c6672f0b116a Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 10 Jan 2024 13:22:33 +0400 Subject: [PATCH 036/296] Editor: Use hooks instead of HoCs in 'PostVisibilityCheck' (#57705) --- .../src/components/post-visibility/check.js | 25 +++++-------- .../components/post-visibility/test/check.js | 37 ++++++++++++------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/packages/editor/src/components/post-visibility/check.js b/packages/editor/src/components/post-visibility/check.js index 4bf9bd03772da..116db0f546de2 100644 --- a/packages/editor/src/components/post-visibility/check.js +++ b/packages/editor/src/components/post-visibility/check.js @@ -1,26 +1,21 @@ /** * WordPress dependencies */ -import { compose } from '@wordpress/compose'; -import { withSelect } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies */ import { store as editorStore } from '../../store'; -export function PostVisibilityCheck( { hasPublishAction, render } ) { - const canEdit = hasPublishAction; +export default function PostVisibilityCheck( { render } ) { + const canEdit = useSelect( ( select ) => { + return ( + select( editorStore ).getCurrentPost()._links?.[ + 'wp:action-publish' + ] ?? false + ); + } ); + return render( { canEdit } ); } - -export default compose( [ - withSelect( ( select ) => { - const { getCurrentPost, getCurrentPostType } = select( editorStore ); - return { - hasPublishAction: - getCurrentPost()._links?.[ 'wp:action-publish' ] ?? false, - postType: getCurrentPostType(), - }; - } ), -] )( PostVisibilityCheck ); diff --git a/packages/editor/src/components/post-visibility/test/check.js b/packages/editor/src/components/post-visibility/test/check.js index 8ec0c2df04ec9..828e876cceb10 100644 --- a/packages/editor/src/components/post-visibility/test/check.js +++ b/packages/editor/src/components/post-visibility/test/check.js @@ -3,32 +3,43 @@ */ import { render, screen } from '@testing-library/react'; +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() ); + /** * Internal dependencies */ -import { PostVisibilityCheck } from '../check'; +import PostVisibilityCheck from '../check'; + +function setupMockSelect( hasPublishAction ) { + useSelect.mockImplementation( ( mapSelect ) => { + return mapSelect( () => ( { + getCurrentPost: () => ( { + _links: { + 'wp:action-publish': hasPublishAction, + }, + } ), + } ) ); + } ); +} describe( 'PostVisibilityCheck', () => { const renderProp = ( { canEdit } ) => ( canEdit ? 'yes' : 'no' ); it( "should not render the edit link if the user doesn't have the right capability", () => { - render( - - ); + setupMockSelect( false ); + render( ); expect( screen.queryByText( 'yes' ) ).not.toBeInTheDocument(); expect( screen.getByText( 'no' ) ).toBeVisible(); } ); it( 'should render if the user has the correct capability', () => { - render( - - ); + setupMockSelect( true ); + render( ); expect( screen.queryByText( 'no' ) ).not.toBeInTheDocument(); expect( screen.getByText( 'yes' ) ).toBeVisible(); } ); From 6ef4629f77ab3da6502508b4c9539a3912300f0e Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Wed, 10 Jan 2024 10:35:55 +0100 Subject: [PATCH 037/296] Clean up code editor CSS. (#57519) --- packages/edit-post/src/components/text-editor/style.scss | 6 ------ packages/edit-site/src/components/code-editor/style.scss | 4 ---- 2 files changed, 10 deletions(-) diff --git a/packages/edit-post/src/components/text-editor/style.scss b/packages/edit-post/src/components/text-editor/style.scss index c02e983057e6e..ab248317de1db 100644 --- a/packages/edit-post/src/components/text-editor/style.scss +++ b/packages/edit-post/src/components/text-editor/style.scss @@ -39,10 +39,8 @@ margin-right: auto; @include break-large() { - padding: $grid-unit-20 $grid-unit-30 #{ $grid-unit-60 * 2 } $grid-unit-30; padding: 0 $grid-unit-30 $grid-unit-30 $grid-unit-30; } - } // Exit code editor toolbar. @@ -70,8 +68,4 @@ font-size: $default-font-size; color: $gray-900; } - - .components-button svg { - order: 1; - } } diff --git a/packages/edit-site/src/components/code-editor/style.scss b/packages/edit-site/src/components/code-editor/style.scss index 0e79575c49f67..17431de27b896 100644 --- a/packages/edit-site/src/components/code-editor/style.scss +++ b/packages/edit-site/src/components/code-editor/style.scss @@ -41,10 +41,6 @@ font-size: $default-font-size; color: $gray-900; } - - .components-button svg { - order: 1; - } } } From 73a4716f429b5dce0190638049f5bd30f0b242f6 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Jan 2024 12:47:03 +0100 Subject: [PATCH 038/296] Scripts: Fix webpack not setting environment.module true (#57714) Webpack (via wp-scripts) may refuse to output external modules unless we set `output.environment.modules = true`: > The target environment doesn't support EcmaScriptModule syntax so it's not possible to use external type 'module' > while analyzing module external module "@wordpress/interactivity" for concatenation --- packages/scripts/config/webpack.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 3919558c2f05c..57bd258d32539 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -403,6 +403,10 @@ if ( hasExperimentalModulesFlag ) { ...baseConfig.output, module: true, chunkFormat: 'module', + environment: { + ...baseConfig.output.environment, + module: true, + }, library: { ...baseConfig.output.library, type: 'module', From b3e534e636cc286ca79a796d3a47164ff362302b Mon Sep 17 00:00:00 2001 From: Carlos Garcia Date: Wed, 10 Jan 2024 13:35:36 +0100 Subject: [PATCH 039/296] Add tests to cover enabling autoplay and loop setting in Audio block (#57715) --- .../test/__snapshots__/edit.native.js.snap | 12 ++++++++ .../src/audio/test/edit.native.js | 29 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap index dca3f782efc67..4cf28f7063ad3 100644 --- a/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/audio/test/__snapshots__/edit.native.js.snap @@ -532,3 +532,15 @@ exports[`Audio block renders placeholder without crashing 1`] = ` `; + +exports[`Audio block should enable autoplay setting 1`] = ` +" +
+" +`; + +exports[`Audio block should enable loop setting 1`] = ` +" +
+" +`; diff --git a/packages/block-library/src/audio/test/edit.native.js b/packages/block-library/src/audio/test/edit.native.js index c191fd2fff798..7296d595d7aaa 100644 --- a/packages/block-library/src/audio/test/edit.native.js +++ b/packages/block-library/src/audio/test/edit.native.js @@ -5,7 +5,10 @@ import { addBlock, dismissModal, fireEvent, + getBlock, + getEditorHtml, initializeEditor, + openBlockSettings, render, screen, setupCoreBlocks, @@ -31,6 +34,10 @@ jest.unmock( '@wordpress/react-native-aztec' ); const MEDIA_UPLOAD_STATE_FAILED = 3; +const AUDIO_BLOCK = ` +
+`; + let uploadCallBack; subscribeMediaUpload.mockImplementation( ( callback ) => { uploadCallBack = callback; @@ -100,4 +107,26 @@ describe( 'Audio block', () => { screen.getByText( 'Invalid URL. Audio file not found.' ) ).toBeVisible(); } ); + + it( 'should enable autoplay setting', async () => { + await initializeEditor( { initialHtml: AUDIO_BLOCK } ); + + const audioBlock = getBlock( screen, 'Audio' ); + fireEvent.press( audioBlock ); + await openBlockSettings( screen ); + + fireEvent.press( screen.getByText( 'Autoplay' ) ); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'should enable loop setting', async () => { + await initializeEditor( { initialHtml: AUDIO_BLOCK } ); + + const audioBlock = getBlock( screen, 'Audio' ); + fireEvent.press( audioBlock ); + await openBlockSettings( screen ); + + fireEvent.press( screen.getByText( 'Loop' ) ); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); } ); From db067e16df101128fe7588e483112c7030dfa83e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:51:21 +0100 Subject: [PATCH 040/296] DataViews: add footer to Pages sidebar (#57690) --- .../index.js | 77 +++++++++++++++++++ .../edit-site/src/components/sidebar/index.js | 9 +-- 2 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js new file mode 100644 index 0000000000000..171d59c108e9b --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { + __experimentalTruncate as Truncate, + __experimentalVStack as VStack, +} from '@wordpress/components'; +import { layout } from '@wordpress/icons'; +import { useMemo } from '@wordpress/element'; +import { useEntityRecords } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { useLink } from '../routes/link'; +import { TEMPLATE_POST_TYPE } from '../../utils/constants'; +import SidebarNavigationItem from '../sidebar-navigation-item'; +import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import DataViewsSidebarContent from '../sidebar-dataviews'; + +const PageItem = ( { postType = 'page', postId, ...props } ) => { + const linkInfo = useLink( + { + postType, + postId, + }, + { + backPath: '/page', + } + ); + return ; +}; + +export default function SidebarNavigationScreenPagesDataViews() { + const { records: templateRecords } = useEntityRecords( + 'postType', + TEMPLATE_POST_TYPE, + { + per_page: -1, + } + ); + const templates = useMemo( + () => + templateRecords?.filter( ( { slug } ) => + [ '404', 'search' ].includes( slug ) + ), + [ templateRecords ] + ); + + return ( + } + footer={ + + { templates?.map( ( item ) => ( + + + { decodeEntities( + item.title?.rendered || __( '(no title)' ) + ) } + + + ) ) } + + } + /> + ); +} diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index 0f986d486fbb8..73c6aea7e328c 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -7,7 +7,6 @@ import classNames from 'classnames'; * WordPress dependencies */ import { memo, useRef } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; import { __experimentalNavigatorProvider as NavigatorProvider, __experimentalNavigatorScreen as NavigatorScreen, @@ -32,9 +31,8 @@ import SidebarNavigationScreenTemplatesBrowse from '../sidebar-navigation-screen import SaveHub from '../save-hub'; import { unlock } from '../../lock-unlock'; import SidebarNavigationScreenPages from '../sidebar-navigation-screen-pages'; +import SidebarNavigationScreenPagesDataViews from '../sidebar-navigation-screen-pages-dataviews'; import SidebarNavigationScreenPage from '../sidebar-navigation-screen-page'; -import SidebarNavigationScreen from '../sidebar-navigation-screen'; -import DataViewsSidebarContent from '../sidebar-dataviews'; const { useLocation } = unlock( routerPrivateApis ); @@ -69,10 +67,7 @@ function SidebarScreens() { { window?.__experimentalAdminViews ? ( - } - /> + ) : ( ) } From 08f236d0319f503c1c784dc8050df058cbccdea9 Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Wed, 10 Jan 2024 15:13:22 +0100 Subject: [PATCH 041/296] Interactive template: Use viewModule (#57712) Use viewModule in the create-block interactivity template. - Add `viewModule` support to `@wordpress/create-block`. - Set the `--experimental-modules` option when building the templated block. - The plugin php template is updated to register with block metadata. --------- Co-authored-by: Luis Herranz --- .../CHANGELOG.md | 14 +++++++++----- .../create-block-interactive-template/README.md | 2 ++ .../block-templates/render.php.mustache | 5 ----- .../create-block-interactive-template/index.js | 6 ++++++ .../plugin-templates/$slug.php.mustache | 11 +---------- packages/create-block/CHANGELOG.md | 4 ++++ packages/create-block/lib/init-block.js | 2 ++ packages/create-block/lib/scaffold.js | 2 ++ packages/interactivity/docs/1-getting-started.md | 16 ++-------------- 9 files changed, 28 insertions(+), 34 deletions(-) diff --git a/packages/create-block-interactive-template/CHANGELOG.md b/packages/create-block-interactive-template/CHANGELOG.md index 47a8aec6c92a3..159c65e9ada19 100644 --- a/packages/create-block-interactive-template/CHANGELOG.md +++ b/packages/create-block-interactive-template/CHANGELOG.md @@ -2,20 +2,24 @@ ## Unreleased +### Enhancement + +- Update the template to use `viewModule` in block.json ([#57712](https://github.com/WordPress/gutenberg/pull/57712)). + ## 1.11.0 (2023-12-13) -- Add all files to the generated plugin zip. [#56943](https://github.com/WordPress/gutenberg/pull/56943) -- Prevent crash when Gutenberg plugin is not installed. [#56941](https://github.com/WordPress/gutenberg/pull/56941) +- Add all files to the generated plugin zip ([#56943](https://github.com/WordPress/gutenberg/pull/56943)). +- Prevent crash when Gutenberg plugin is not installed ([#56941](https://github.com/WordPress/gutenberg/pull/56941)). ## 1.10.1 (2023-12-07) -- Update template to use modules instead of scripts. [#56694](https://github.com/WordPress/gutenberg/pull/56694) +- Update template to use modules instead of scripts ([#56694](https://github.com/WordPress/gutenberg/pull/56694)). ## 1.10.0 (2023-11-29) ### Enhancement -- Update `view.js` and `render.php` templates to the new `store()` API. [#56613](https://github.com/WordPress/gutenberg/pull/56613) +- Update `view.js` and `render.php` templates to the new `store()` API ([#56613](https://github.com/WordPress/gutenberg/pull/56613)). ## 1.9.0 (2023-11-16) @@ -35,4 +39,4 @@ ### Enhancement -- Moves the `example` property into block.json by leveraging changes to create-block to now support `example`. [#52801](https://github.com/WordPress/gutenberg/pull/52801) +- Moves the `example` property into block.json by leveraging changes to create-block to now support `example` ([#52801](https://github.com/WordPress/gutenberg/pull/52801)). diff --git a/packages/create-block-interactive-template/README.md b/packages/create-block-interactive-template/README.md index cc0530c063054..adf3cab6594cc 100644 --- a/packages/create-block-interactive-template/README.md +++ b/packages/create-block-interactive-template/README.md @@ -10,6 +10,8 @@ This block template can be used by running the following command: npx @wordpress/create-block --template @wordpress/create-block-interactive-template ``` +It requires Gutenberg 17.5 or higher. + ## Contributing to this package This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects. diff --git a/packages/create-block-interactive-template/block-templates/render.php.mustache b/packages/create-block-interactive-template/block-templates/render.php.mustache index 0f6883a936240..960da619f790a 100644 --- a/packages/create-block-interactive-template/block-templates/render.php.mustache +++ b/packages/create-block-interactive-template/block-templates/render.php.mustache @@ -13,11 +13,6 @@ // Generate unique id for aria-controls. $unique_id = wp_unique_id( 'p-' ); - -// Enqueue the view file. -if (function_exists('gutenberg_enqueue_module')) { - gutenberg_enqueue_module( '{{namespace}}-view' ); -} ?>
!! value ) diff --git a/packages/create-block/lib/scaffold.js b/packages/create-block/lib/scaffold.js index 49d3cbf794777..bd9ba0396b75e 100644 --- a/packages/create-block/lib/scaffold.js +++ b/packages/create-block/lib/scaffold.js @@ -44,6 +44,7 @@ module.exports = async ( editorStyle, style, render, + viewModule, viewScript, variantVars, customPackageJSON, @@ -84,6 +85,7 @@ module.exports = async ( editorStyle, style, render, + viewModule, viewScript, variantVars, customPackageJSON, diff --git a/packages/interactivity/docs/1-getting-started.md b/packages/interactivity/docs/1-getting-started.md index 85af202180735..660671a8b10cd 100644 --- a/packages/interactivity/docs/1-getting-started.md +++ b/packages/interactivity/docs/1-getting-started.md @@ -26,18 +26,6 @@ We can scaffold a WordPress plugin that registers an interactive block (using th npx @wordpress/create-block@latest my-first-interactive-block --template @wordpress/create-block-interactive-template ``` -> **Note** -> The Interactivity API recently switched from [using modules instead of scripts in the frontend](https://github.com/WordPress/gutenberg/pull/56143). Therefore, in order to test this scaffolded block, you will need to add the following line to the `package.json` file of the generated plugin: - -```json -"files": [ - "src/view.js" -] -``` -> This should be updated in the [scripts package](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) soon. - - - #### 2. Generate the build When the plugin folder is generated, we should launch the build process to get the final version of the interactive block that can be used from WordPress. @@ -61,7 +49,7 @@ At this point you should be able to insert the "My First Interactive Block" bloc ## Requirements of the Interactivity API -To start working with the Interactivity API you'll need to have a [proper WordPress development environment for blocks](https://developer.wordpress.org/block-editor/getting-started/devenv/) and some specific code in your block, which should include: +To start working with the Interactivity API you'll need to have a [proper WordPress development environment for blocks](https://developer.wordpress.org/block-editor/getting-started/devenv/) and some specific code in your block, which should include: #### A local WordPress installation @@ -71,7 +59,7 @@ To get quickly started, [`wp-now`](https://www.npmjs.com/package/@wp-now/wp-now) #### Latest vesion of Gutenberg -The Interactivity API is currently only available as an experimental feature from Gutenberg 17.2, so you'll need to have Gutenberg 17.2 or higher version installed and activated in your WordPress installation. +The Interactivity API is currently only available as an experimental feature from Gutenberg, so you'll need to have Gutenberg 17.5 or higher version installed and activated in your WordPress installation. #### Node.js From ba367dcc86de157f74eebf419f167381b5d797b2 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 10 Jan 2024 15:28:52 +0100 Subject: [PATCH 042/296] Update @ariakit/react to v0.3.12 and @ariakit/test to v0.3.7 (#57547) * Update @ariakit/react to v0.3.12 and @ariakit/test to v0.3.7 * CHANGELOG * Use @ariakit/test for AlignmentMatrixControl unit tests * Use @ariakit/text for ToggleGroupControl unit tests * Refactor 'hoverOutside' method * Improve tooltip-related tests in `ToggleGroupControl` --- package-lock.json | 152 ++++++++---------- package.json | 2 +- packages/components/CHANGELOG.md | 1 + packages/components/package.json | 2 +- .../alignment-matrix-control/test/index.tsx | 26 ++- .../src/toggle-group-control/test/index.tsx | 103 ++++++------ 6 files changed, 140 insertions(+), 146 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2c4a65342a35..02de0a60ff4c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -86,7 +86,7 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "5.0.0", - "@ariakit/test": "^0.3.5", + "@ariakit/test": "^0.3.7", "@babel/core": "7.16.0", "@babel/plugin-proposal-export-namespace-from": "7.18.9", "@babel/plugin-syntax-jsx": "7.16.0", @@ -1628,13 +1628,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ariakit/core": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.10.tgz", + "integrity": "sha512-AcN+GSoVXuUOzKx5d3xPL3YsEHevh4PIO6QIt/mg/nRX1XQ6cvxQEiAjO/BJQm+/MVl7/VbuGBoTFjr0tPU6NQ==" + }, + "node_modules/@ariakit/react": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.12.tgz", + "integrity": "sha512-HxKMZZhWSkwwS/Sh9OdWyuNKQ2tjDAIQIy2KVI7IRa8ZQ6ze/4g3YLUHbfCxO7oDupXHfXaeZ4hWx8lP7l1U/g==", + "dependencies": { + "@ariakit/react-core": "0.3.12" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ariakit" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@ariakit/react-core": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.12.tgz", + "integrity": "sha512-w6P1A7TYb1fKUe9QbwaoTOWofl13g7TEuXdV4JyefJCQL1e9HQdEw9UL67I8aXRo8/cFHH94/z0N37t8hw5Ogg==", + "dependencies": { + "@ariakit/core": "0.3.10", + "@floating-ui/dom": "^1.0.0", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@ariakit/test": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.5.tgz", - "integrity": "sha512-7UCQBnJZ88JptkEnAXT7iSgtxEZiFwqdkKtxLCXDssTOJNatbFsnq0Jow324y41jGfAE2n4Lf5qY2FsZUPf9XQ==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.7.tgz", + "integrity": "sha512-rOa9pJA0ZfPPSI4SkDX41CsBcvxs6BmxgzFEElZWZo/uBBqtnr8ZL4oe5HySeZKEAHRH86XDqfxFISkhV76m5g==", "dev": true, "dependencies": { - "@ariakit/core": "0.3.8", + "@ariakit/core": "0.3.10", "@testing-library/dom": "^8.0.0 || ^9.0.0" }, "peerDependencies": { @@ -1650,12 +1685,6 @@ } } }, - "node_modules/@ariakit/test/node_modules/@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==", - "dev": true - }, "node_modules/@aw-web-design/x-default-browser": { "version": "1.4.126", "resolved": "https://registry.npmjs.org/@aw-web-design/x-default-browser/-/x-default-browser-1.4.126.tgz", @@ -54244,7 +54273,7 @@ "version": "25.14.0", "license": "GPL-2.0-or-later", "dependencies": { - "@ariakit/react": "^0.3.10", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -54303,41 +54332,6 @@ "react-dom": "^18.0.0" } }, - "packages/components/node_modules/@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==" - }, - "packages/components/node_modules/@ariakit/react": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.10.tgz", - "integrity": "sha512-XRY69IOm8Oy+HSPoaspcVLAhLo3ToLhhJKSLK1voTAZtSzu5kUeUf4nUPxTzYFsvirKORZgOLAeNwuo1gPr61g==", - "dependencies": { - "@ariakit/react-core": "0.3.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ariakit" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, - "packages/components/node_modules/@ariakit/react-core": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.10.tgz", - "integrity": "sha512-CzSffcNlOyS2xuy21UB6fgJXi5LriJ9JrTSJzcgJmE+P9/WfQlplJC3L75d8O2yKgaGPeFnQ0hhDA6ItsI98eQ==", - "dependencies": { - "@ariakit/core": "0.3.8", - "@floating-ui/dom": "^1.0.0", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, "packages/components/node_modules/@floating-ui/react-dom": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", @@ -57392,22 +57386,37 @@ } } }, + "@ariakit/core": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.10.tgz", + "integrity": "sha512-AcN+GSoVXuUOzKx5d3xPL3YsEHevh4PIO6QIt/mg/nRX1XQ6cvxQEiAjO/BJQm+/MVl7/VbuGBoTFjr0tPU6NQ==" + }, + "@ariakit/react": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.12.tgz", + "integrity": "sha512-HxKMZZhWSkwwS/Sh9OdWyuNKQ2tjDAIQIy2KVI7IRa8ZQ6ze/4g3YLUHbfCxO7oDupXHfXaeZ4hWx8lP7l1U/g==", + "requires": { + "@ariakit/react-core": "0.3.12" + } + }, + "@ariakit/react-core": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.12.tgz", + "integrity": "sha512-w6P1A7TYb1fKUe9QbwaoTOWofl13g7TEuXdV4JyefJCQL1e9HQdEw9UL67I8aXRo8/cFHH94/z0N37t8hw5Ogg==", + "requires": { + "@ariakit/core": "0.3.10", + "@floating-ui/dom": "^1.0.0", + "use-sync-external-store": "^1.2.0" + } + }, "@ariakit/test": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.5.tgz", - "integrity": "sha512-7UCQBnJZ88JptkEnAXT7iSgtxEZiFwqdkKtxLCXDssTOJNatbFsnq0Jow324y41jGfAE2n4Lf5qY2FsZUPf9XQ==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@ariakit/test/-/test-0.3.7.tgz", + "integrity": "sha512-rOa9pJA0ZfPPSI4SkDX41CsBcvxs6BmxgzFEElZWZo/uBBqtnr8ZL4oe5HySeZKEAHRH86XDqfxFISkhV76m5g==", "dev": true, "requires": { - "@ariakit/core": "0.3.8", + "@ariakit/core": "0.3.10", "@testing-library/dom": "^8.0.0 || ^9.0.0" - }, - "dependencies": { - "@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==", - "dev": true - } } }, "@aw-web-design/x-default-browser": { @@ -69363,7 +69372,7 @@ "@wordpress/components": { "version": "file:packages/components", "requires": { - "@ariakit/react": "^0.3.10", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", @@ -69415,29 +69424,6 @@ "valtio": "1.7.0" }, "dependencies": { - "@ariakit/core": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@ariakit/core/-/core-0.3.8.tgz", - "integrity": "sha512-LlSCwbyyozMX4ZEobpYGcv1LFqOdBTdTYPZw3lAVgLcFSNivsazi3NkKM9qNWNIu00MS+xTa0+RuIcuWAjlB2Q==" - }, - "@ariakit/react": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react/-/react-0.3.10.tgz", - "integrity": "sha512-XRY69IOm8Oy+HSPoaspcVLAhLo3ToLhhJKSLK1voTAZtSzu5kUeUf4nUPxTzYFsvirKORZgOLAeNwuo1gPr61g==", - "requires": { - "@ariakit/react-core": "0.3.10" - } - }, - "@ariakit/react-core": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/@ariakit/react-core/-/react-core-0.3.10.tgz", - "integrity": "sha512-CzSffcNlOyS2xuy21UB6fgJXi5LriJ9JrTSJzcgJmE+P9/WfQlplJC3L75d8O2yKgaGPeFnQ0hhDA6ItsI98eQ==", - "requires": { - "@ariakit/core": "0.3.8", - "@floating-ui/dom": "^1.0.0", - "use-sync-external-store": "^1.2.0" - } - }, "@floating-ui/react-dom": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz", diff --git a/package.json b/package.json index e98ad7cb1587f..f2f239762de97 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "5.0.0", - "@ariakit/test": "^0.3.5", + "@ariakit/test": "^0.3.7", "@babel/core": "7.16.0", "@babel/plugin-proposal-export-namespace-from": "7.18.9", "@babel/plugin-syntax-jsx": "7.16.0", diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 8805736c2e440..c7c1a515a64ce 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -24,6 +24,7 @@ ### Enhancements - Update `ariakit` to version `0.3.10` ([#57325](https://github.com/WordPress/gutenberg/pull/57325)). +- Update `@ariakit/react` to version `0.3.12` and @ariakit/test to version `0.3.7` ([#57547](https://github.com/WordPress/gutenberg/pull/57547)). - `DropdownMenuV2`: do not collapse suffix width ([#57238](https://github.com/WordPress/gutenberg/pull/57238)). - `DateTimePicker`: Adjustment of the dot position on DayButton and expansion of the button area. ([#55502](https://github.com/WordPress/gutenberg/pull/55502)). - `Modal`: Improve application of body class names ([#55430](https://github.com/WordPress/gutenberg/pull/55430)). diff --git a/packages/components/package.json b/packages/components/package.json index 885c1e455fea4..cd440998b9323 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -30,7 +30,7 @@ ], "types": "build-types", "dependencies": { - "@ariakit/react": "^0.3.10", + "@ariakit/react": "^0.3.12", "@babel/runtime": "^7.16.0", "@emotion/cache": "^11.7.1", "@emotion/css": "^11.7.1", diff --git a/packages/components/src/alignment-matrix-control/test/index.tsx b/packages/components/src/alignment-matrix-control/test/index.tsx index 6836bc7e45f95..a820b69b26c8f 100644 --- a/packages/components/src/alignment-matrix-control/test/index.tsx +++ b/packages/components/src/alignment-matrix-control/test/index.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { render, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { press, click } from '@ariakit/test'; /** * Internal dependencies @@ -37,11 +37,9 @@ describe( 'AlignmentMatrixControl', () => { } ); it( 'should be centered by default', async () => { - const user = userEvent.setup(); - await renderAndInitCompositeStore( ); - await user.tab(); + await press.Tab(); expect( getCell( 'center center' ) ).toHaveFocus(); } ); @@ -60,7 +58,6 @@ describe( 'AlignmentMatrixControl', () => { 'bottom center', 'bottom right', ] )( '%s', async ( alignment ) => { - const user = userEvent.setup(); const spy = jest.fn(); await renderAndInitCompositeStore( @@ -72,14 +69,13 @@ describe( 'AlignmentMatrixControl', () => { const cell = getCell( alignment ); - await user.click( cell ); + await click( cell ); expect( cell ).toHaveFocus(); expect( spy ).toHaveBeenCalledWith( alignment ); } ); it( 'unless already focused', async () => { - const user = userEvent.setup(); const spy = jest.fn(); await renderAndInitCompositeStore( @@ -91,7 +87,7 @@ describe( 'AlignmentMatrixControl', () => { const cell = getCell( 'center center' ); - await user.click( cell ); + await click( cell ); expect( cell ).toHaveFocus(); expect( spy ).not.toHaveBeenCalled(); @@ -106,16 +102,15 @@ describe( 'AlignmentMatrixControl', () => { [ 'ArrowLeft', 'center left' ], [ 'ArrowDown', 'bottom center' ], [ 'ArrowRight', 'center right' ], - ] )( '%s', async ( keyRef, cellRef ) => { - const user = userEvent.setup(); + ] as const )( '%s', async ( keyRef, cellRef ) => { const spy = jest.fn(); await renderAndInitCompositeStore( ); - await user.tab(); - await user.keyboard( `[${ keyRef }]` ); + await press.Tab(); + await press[ keyRef ](); expect( getCell( cellRef ) ).toHaveFocus(); expect( spy ).toHaveBeenCalledWith( cellRef ); @@ -128,8 +123,7 @@ describe( 'AlignmentMatrixControl', () => { [ 'ArrowLeft', 'top left' ], [ 'ArrowDown', 'bottom right' ], [ 'ArrowRight', 'bottom right' ], - ] )( '%s', async ( keyRef, cellRef ) => { - const user = userEvent.setup(); + ] as const )( '%s', async ( keyRef, cellRef ) => { const spy = jest.fn(); await renderAndInitCompositeStore( @@ -137,8 +131,8 @@ describe( 'AlignmentMatrixControl', () => { ); const cell = getCell( cellRef ); - await user.click( cell ); - await user.keyboard( `[${ keyRef }]` ); + await click( cell ); + await press[ keyRef ](); expect( cell ).toHaveFocus(); expect( spy ).toHaveBeenCalledWith( cellRef ); diff --git a/packages/components/src/toggle-group-control/test/index.tsx b/packages/components/src/toggle-group-control/test/index.tsx index b54b5764d4e0f..99a2dd8a00421 100644 --- a/packages/components/src/toggle-group-control/test/index.tsx +++ b/packages/components/src/toggle-group-control/test/index.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { press, click, hover, sleep } from '@ariakit/test'; /** * WordPress dependencies @@ -19,8 +19,13 @@ import { ToggleGroupControlOption, ToggleGroupControlOptionIcon, } from '../index'; +import { TOOLTIP_DELAY } from '../../tooltip'; import type { ToggleGroupControlProps } from '../types'; -import cleanupTooltip from '../../tooltip/test/utils'; + +const hoverOutside = async () => { + await hover( document.body ); + await hover( document.body, { clientX: 10, clientY: 10 } ); +}; const ControlledToggleGroupControl = ( { value: valueProp, @@ -113,7 +118,6 @@ describe.each( [ } ); } ); it( 'should call onChange with proper value', async () => { - const user = userEvent.setup(); const mockOnChange = jest.fn(); render( @@ -126,13 +130,12 @@ describe.each( [ ); - await user.click( screen.getByRole( 'radio', { name: 'R' } ) ); + await click( screen.getByRole( 'radio', { name: 'R' } ) ); expect( mockOnChange ).toHaveBeenCalledWith( 'rigas' ); } ); it( 'should render tooltip where `showTooltip` === `true`', async () => { - const user = userEvent.setup(); render( { optionsWithTooltip } @@ -143,19 +146,26 @@ describe.each( [ 'Click for Delicious Gnocchi' ); - await user.hover( firstRadio ); + await hover( firstRadio ); - const tooltip = await screen.findByText( - 'Click for Delicious Gnocchi' - ); + const tooltip = await screen.findByRole( 'tooltip', { + name: 'Click for Delicious Gnocchi', + } ); await waitFor( () => expect( tooltip ).toBeVisible() ); - await cleanupTooltip( user ); + // hover outside of radio + await hoverOutside(); + + // Tooltip should hide + expect( + screen.queryByRole( 'tooltip', { + name: 'Click for Delicious Gnocchi', + } ) + ).not.toBeInTheDocument(); } ); it( 'should not render tooltip', async () => { - const user = userEvent.setup(); render( { optionsWithTooltip } @@ -166,19 +176,24 @@ describe.each( [ 'Click for Sumptuous Caponata' ); - await user.hover( secondRadio ); + await hover( secondRadio ); - await waitFor( () => - expect( - screen.queryByText( 'Click for Sumptuous Caponata' ) - ).not.toBeInTheDocument() - ); + // Tooltip shouldn't show + expect( + screen.queryByText( 'Click for Sumptuous Caponata' ) + ).not.toBeInTheDocument(); + + // Advance time by default delay + await sleep( TOOLTIP_DELAY ); + + // Tooltip shouldn't show. + expect( + screen.queryByText( 'Click for Sumptuous Caponata' ) + ).not.toBeInTheDocument(); } ); if ( mode === 'controlled' ) { it( 'should reset values correctly', async () => { - const user = userEvent.setup(); - render( { options } @@ -188,25 +203,23 @@ describe.each( [ const rigasOption = screen.getByRole( 'radio', { name: 'R' } ); const jackOption = screen.getByRole( 'radio', { name: 'J' } ); - await user.click( rigasOption ); + await click( rigasOption ); expect( jackOption ).not.toBeChecked(); expect( rigasOption ).toBeChecked(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( rigasOption ).not.toBeChecked(); expect( jackOption ).toBeChecked(); - await user.click( screen.getByRole( 'button', { name: 'Reset' } ) ); + await click( screen.getByRole( 'button', { name: 'Reset' } ) ); expect( rigasOption ).not.toBeChecked(); expect( jackOption ).not.toBeChecked(); } ); it( 'should update correctly when triggered by external updates', async () => { - const user = userEvent.setup(); - render( { it( 'should not be deselectable', async () => { const mockOnChange = jest.fn(); - const user = userEvent.setup(); render( { - const user = userEvent.setup(); - render( - - { options } - + <> + + { options } + + + ); const rigas = screen.getByRole( 'radio', { name: 'R', } ); - await user.tab(); + await press.Tab(); expect( rigas ).toHaveFocus(); - await user.tab(); + await press.Tab(); + // When in controlled mode, there is an additional "Reset" button. const expectedFocusTarget = mode === 'uncontrolled' - ? rigas.ownerDocument.body + ? screen.getByRole( 'button', { + name: 'After ToggleGroupControl', + } ) : screen.getByRole( 'button', { name: 'Reset' } ); expect( expectedFocusTarget ).toHaveFocus(); @@ -301,7 +317,6 @@ describe.each( [ describe( 'isDeselectable = true', () => { it( 'should be deselectable', async () => { const mockOnChange = jest.fn(); - const user = userEvent.setup(); render( ); - await user.click( + await click( screen.getByRole( 'button', { name: 'R', pressed: true, @@ -323,7 +338,7 @@ describe.each( [ expect( mockOnChange ).toHaveBeenCalledTimes( 1 ); expect( mockOnChange ).toHaveBeenLastCalledWith( undefined ); - await user.click( + await click( screen.getByRole( 'button', { name: 'R', pressed: false, @@ -334,15 +349,13 @@ describe.each( [ } ); it( 'should tab to the next option button', async () => { - const user = userEvent.setup(); - render( { options } ); - await user.tab(); + await press.Tab(); expect( screen.getByRole( 'button', { name: 'R', @@ -350,7 +363,7 @@ describe.each( [ } ) ).toHaveFocus(); - await user.tab(); + await press.Tab(); expect( screen.getByRole( 'button', { name: 'J', @@ -359,7 +372,7 @@ describe.each( [ ).toHaveFocus(); // Focus should not move with arrow keys - await user.keyboard( '{ArrowLeft}' ); + await press.ArrowLeft(); expect( screen.getByRole( 'button', { name: 'J', From 6693377d080d8c581f279bfd7521f5b2bcf02149 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 10 Jan 2024 14:34:24 +0000 Subject: [PATCH 043/296] Bump plugin version to 17.5.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 35e416006bea5..9559f838608da 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.3 * Requires PHP: 7.0 - * Version: 17.4.1 + * Version: 17.5.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 02de0a60ff4c9..96a14ec8eeb50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.4.1", + "version": "17.5.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.4.1", + "version": "17.5.0-rc.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index f2f239762de97..684f35d408d3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.4.1", + "version": "17.5.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From e93f250746b14acaed14190cdb552960a6c490ce Mon Sep 17 00:00:00 2001 From: Gerardo Pacheco Date: Wed, 10 Jan 2024 15:49:08 +0100 Subject: [PATCH 044/296] [Mobile] - Fix missing custom gradient indicator in the color palette (#57605) * Mobile - Fix missing Custom indicator for custom gradients * Pass enableCustomColor in Cover block as true * Fix typo * Update Changelog * Fix condition * Update test to use the openBlockSettings helper * Simplify condition by adding optional chaining * Use flatMap --- .../test/__snapshots__/edit.native.js.snap | 6 +++ .../src/buttons/test/edit.native.js | 49 +++++++++++++++++++ .../block-library/src/cover/edit.native.js | 1 + .../src/color-palette/index.native.js | 25 +++++++--- .../color-settings/palette.screen.native.js | 12 +++-- packages/react-native-editor/CHANGELOG.md | 1 + 6 files changed, 82 insertions(+), 12 deletions(-) diff --git a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap index 1a55c807225d9..f04eacee4b91c 100644 --- a/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap +++ b/packages/block-library/src/buttons/test/__snapshots__/edit.native.js.snap @@ -6,6 +6,12 @@ exports[`Buttons block color customization sets a background color 1`] = ` " `; +exports[`Buttons block color customization sets a custom gradient background color 1`] = ` +" +
+" +`; + exports[`Buttons block color customization sets a gradient background color 1`] = ` "
diff --git a/packages/block-library/src/buttons/test/edit.native.js b/packages/block-library/src/buttons/test/edit.native.js index f393a31c7330a..af2ffe762e6a3 100644 --- a/packages/block-library/src/buttons/test/edit.native.js +++ b/packages/block-library/src/buttons/test/edit.native.js @@ -10,6 +10,7 @@ import { initializeEditor, triggerBlockListLayout, typeInRichText, + openBlockSettings, waitFor, } from 'test/helpers'; @@ -391,5 +392,53 @@ describe( 'Buttons block', () => { // Assert expect( getEditorHtml() ).toMatchSnapshot(); } ); + + it( 'sets a custom gradient background color', async () => { + // Arrange + const screen = await initializeEditor(); + await addBlock( screen, 'Buttons' ); + + // Act + const buttonsBlock = getBlock( screen, 'Buttons' ); + fireEvent.press( buttonsBlock ); + + // Trigger onLayout for the list + await triggerBlockListLayout( buttonsBlock ); + + const buttonBlock = await getBlock( screen, 'Button' ); + fireEvent.press( buttonBlock ); + + // Open Block Settings. + await openBlockSettings( screen ); + + // Open Text color settings + fireEvent.press( screen.getByLabelText( 'Background, Default' ) ); + + // Tap on the gradient segment + fireEvent.press( screen.getByLabelText( 'Gradient' ) ); + + // Tap one gradient color + fireEvent.press( + screen.getByLabelText( 'Light green cyan to vivid green cyan' ) + ); + + // Tap on Customize Gradient + fireEvent.press( screen.getByLabelText( /Customize Gradient/ ) ); + + // Change the current angle + fireEvent.press( screen.getByText( '135', { hidden: true } ) ); + const angleTextInput = screen.getByDisplayValue( '135', { + hidden: true, + } ); + fireEvent.changeText( angleTextInput, '200' ); + + // Go back to the settings list. + fireEvent.press( await screen.findByLabelText( 'Go back' ) ); + + // Assert + const customButton = await screen.findByText( 'CUSTOM' ); + expect( customButton ).toBeVisible(); + expect( getEditorHtml() ).toMatchSnapshot(); + } ); } ); } ); diff --git a/packages/block-library/src/cover/edit.native.js b/packages/block-library/src/cover/edit.native.js index 81ff43128b1a3..989c5ec3a0d33 100644 --- a/packages/block-library/src/cover/edit.native.js +++ b/packages/block-library/src/cover/edit.native.js @@ -538,6 +538,7 @@ const Cover = ( { { ( { shouldEnableBottomSheetScroll } ) => ( color ) ), ]; - const mergedColors = [ + const mergedGradients = [ + ...new Set( + ( defaultSettings.gradients ?? [] ).map( + ( { gradient } ) => gradient + ) + ), + ]; + const allAvailableColors = [ ...new Set( ( defaultSettings.allColors ?? [] ).map( ( { color } ) => color ) ), ]; - const defaultGradientColors = [ + const allAvailableGradients = [ ...new Set( - ( defaultSettings.gradients ?? [] ).map( + ( defaultSettings.allGradients ?? [] ).map( ( { gradient } ) => gradient ) ), ]; - const colors = isGradientSegment ? defaultGradientColors : defaultColors; + + const colors = isGradientSegment ? mergedGradients : mergedColors; + const allColors = isGradientSegment + ? allAvailableGradients + : allAvailableColors; const customIndicatorColor = isGradientSegment ? activeColor @@ -110,7 +121,7 @@ function ColorPalette( { function isSelectedCustom() { const isWithinColors = - activeColor && mergedColors && mergedColors.includes( activeColor ); + activeColor && allColors?.includes( activeColor ); if ( enableCustomColor && activeColor ) { if ( isGradientSegment ) { return isGradientColor && ! isWithinColors; diff --git a/packages/components/src/mobile/color-settings/palette.screen.native.js b/packages/components/src/mobile/color-settings/palette.screen.native.js index bc7187fd092b8..fcf03f9ecd448 100644 --- a/packages/components/src/mobile/color-settings/palette.screen.native.js +++ b/packages/components/src/mobile/color-settings/palette.screen.native.js @@ -29,7 +29,6 @@ import { colorsUtils } from './utils'; import styles from './style.scss'; const HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 }; -const THEME_PALETTE_NAME = 'Theme'; const PaletteScreen = () => { const route = useRoute(); @@ -48,7 +47,6 @@ const PaletteScreen = () => { const [ currentValue, setCurrentValue ] = useState( colorValue ); const isGradientColor = isGradient( currentValue ); const selectedSegmentIndex = isGradientColor ? 1 : 0; - const allAvailableColors = useMobileGlobalStylesColors(); const [ currentSegment, setCurrentSegment ] = useState( segments[ selectedSegmentIndex ] @@ -57,6 +55,10 @@ const PaletteScreen = () => { const currentSegmentColors = ! isGradientSegment ? defaultSettings.colors : defaultSettings.gradients; + const allAvailableColors = useMobileGlobalStylesColors(); + const allAvailableGradients = currentSegmentColors + .flatMap( ( { gradients } ) => gradients ) + .filter( Boolean ); const horizontalSeparatorStyle = usePreferredColorSchemeStyle( styles.horizontalSeparator, @@ -184,10 +186,10 @@ const PaletteScreen = () => { colors: palette.colors, gradients: palette.gradients, allColors: allAvailableColors, + allGradients: allAvailableGradients, }; - const enableCustomColor = - ! isGradientSegment && - palette.name === THEME_PALETTE_NAME; + // Limit to show the custom indicator to the first available palette + const enableCustomColor = paletteKey === 0; return ( Date: Wed, 10 Jan 2024 09:58:32 -0500 Subject: [PATCH 045/296] Font Library: filter fonts upload directory (#57697) * add global configuration variables for font directory * add multi-site based directory path for fonts * add docblock for get_multi_site_font_sub_dir * Rename function for accuracy. * Use filter instead of constant to determine where fonts are uploaded. * Format php. * simplify the filters code and output the same array as upload_filter does * remove tests no longer used * add a test case for the filter * rename function. Replaces the misleading 'subdir' term by 'dir'. --------- Co-authored-by: madhusudhand Co-authored-by: Matias Benedetto --- .../font-library/class-wp-font-family.php | 4 +- .../font-library/class-wp-font-library.php | 76 +++++++++++++------ .../font-library/wpFontLibrary/fontsDir.php | 70 +++++++++++++++++ .../wpFontLibrary/getFontsDir.php | 18 ----- .../wpFontLibrary/setUploadDir.php | 32 -------- 5 files changed, 126 insertions(+), 74 deletions(-) create mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php delete mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php delete mode 100644 phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php index a4204dfe1fa2c..e47cf0afdac1d 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family.php @@ -599,9 +599,9 @@ private function create_or_update_font_post() { */ public function install( $files = null ) { add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); - add_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + add_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) ); $were_assets_written = $this->download_or_move_font_faces( $files ); - remove_filter( 'upload_dir', array( 'WP_Font_Library', 'set_upload_dir' ) ); + remove_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) ); remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); if ( ! $were_assets_written ) { diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php index 59ec5e93fa787..99de81e0bd74a 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -141,40 +141,72 @@ public static function get_font_collection( $id ) { } /** - * Gets the upload directory for fonts. + * Returns an array containing the current fonts upload directory's path and URL. * * @since 6.5.0 * - * @return string Path of the upload directory for fonts. + * @param array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } + * + * @return array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } */ - public static function get_fonts_dir() { - return path_join( WP_CONTENT_DIR, 'fonts' ); + public static function fonts_dir( $defaults = array() ) { + $site_path = self::get_multi_site_dir(); + + // Sets the defaults. + $defaults['path'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['url'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['subdir'] = ''; + $defaults['basedir'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['baseurl'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['error'] = false; + + // Filters the fonts directory data. + return apply_filters( 'fonts_dir', $defaults ); } /** - * Sets the upload directory for fonts. + * Gets the Site dir for fonts, using the blog ID if multi-site, empty otherwise. * * @since 6.5.0 * - * @param array $defaults { - * Default upload directory. + * @return string Site dir path. + */ + private static function get_multi_site_dir() { + $font_sub_dir = ''; + if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { + $font_sub_dir = '/sites/' . get_current_blog_id(); + } + return $font_sub_dir; + } + + /** + * Gets the upload directory for fonts. * - * @type string $path Path to the directory. - * @type string $url URL for the directory. - * @type string $subdir Sub-directory of the directory. - * @type string $basedir Base directory. - * @type string $baseurl Base URL. - * } - * @return array Modified upload directory. + * @since 6.5.0 + * + * @return string Path of the upload directory for fonts. */ - public static function set_upload_dir( $defaults ) { - $defaults['basedir'] = WP_CONTENT_DIR; - $defaults['baseurl'] = content_url(); - $defaults['subdir'] = '/fonts'; - $defaults['path'] = self::get_fonts_dir(); - $defaults['url'] = $defaults['baseurl'] . '/fonts'; - - return $defaults; + public static function get_fonts_dir() { + $fonts_dir_settings = self::fonts_dir(); + return $fonts_dir_settings['path']; } /** diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php new file mode 100644 index 0000000000000..9926bb7409088 --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php @@ -0,0 +1,70 @@ +dir_defaults = array( + 'path' => path_join( WP_CONTENT_DIR, 'fonts' ), + 'url' => content_url( 'fonts' ), + 'subdir' => '', + 'basedir' => path_join( WP_CONTENT_DIR, 'fonts' ), + 'baseurl' => content_url( 'fonts' ), + 'error' => false, + ); + } + + public function test_fonts_dir() { + $fonts_dir = WP_Font_Library::fonts_dir(); + $this->assertEquals( $fonts_dir, $this->dir_defaults ); + } + + public function test_fonts_dir_with_filter() { + // Define a callback function to pass to the filter. + function set_new_values( $defaults ) { + $defaults['path'] = '/custom-path/fonts/my-custom-subdir'; + $defaults['url'] = 'http://example.com/custom-path/fonts/my-custom-subdir'; + $defaults['subdir'] = 'my-custom-subdir'; + $defaults['basedir'] = '/custom-path/fonts'; + $defaults['baseurl'] = 'http://example.com/custom-path/fonts'; + $defaults['error'] = false; + return $defaults; + } + + // Add the filter. + add_filter( 'fonts_dir', 'set_new_values' ); + + // Gets the fonts dir. + $fonts_dir = WP_Font_Library::fonts_dir(); + + $expected = array( + 'path' => '/custom-path/fonts/my-custom-subdir', + 'url' => 'http://example.com/custom-path/fonts/my-custom-subdir', + 'subdir' => 'my-custom-subdir', + 'basedir' => '/custom-path/fonts', + 'baseurl' => 'http://example.com/custom-path/fonts', + 'error' => false, + ); + + $this->assertEquals( $fonts_dir, $expected, 'The fonts_dir() method should return the expected values.' ); + + // Remove the filter. + remove_filter( 'fonts_dir', 'set_new_values' ); + + // Gets the fonts dir. + $fonts_dir = WP_Font_Library::fonts_dir(); + + $this->assertEquals( $fonts_dir, $this->dir_defaults, 'The fonts_dir() method should return the default values.' ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php deleted file mode 100644 index 1200200d7160b..0000000000000 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontsDir.php +++ /dev/null @@ -1,18 +0,0 @@ -assertStringEndsWith( '/wp-content/fonts', WP_Font_Library::get_fonts_dir() ); - } -} diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php b/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php deleted file mode 100644 index 29d481d8afd6b..0000000000000 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/setUploadDir.php +++ /dev/null @@ -1,32 +0,0 @@ - '/abc', - 'basedir' => '/any/path', - 'baseurl' => 'http://example.com/an/arbitrary/url', - 'path' => '/any/path/abc', - 'url' => 'http://example.com/an/arbitrary/url/abc', - ); - $expected = array( - 'subdir' => '/fonts', - 'basedir' => WP_CONTENT_DIR, - 'baseurl' => content_url(), - 'path' => path_join( WP_CONTENT_DIR, 'fonts' ), - 'url' => content_url() . '/fonts', - ); - $this->assertSame( $expected, WP_Font_Library::set_upload_dir( $defaults ) ); - } -} From 714e6b107670e5ee4d92b5126f99c54305fee03b Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 10 Jan 2024 16:08:05 +0100 Subject: [PATCH 046/296] Tooltip: no-op when nested inside another Tooltip component (#57202) * Tooltip: no-op when nested inside another Tooltip component * Fix Storybook control type * Use internal components context system * Add Storybook example * Snapshots * Add nested unit test * CHANGELOG * Avoid ESLint disable * Keep nested tooltip storybook example * isNestedInParentTooltip => isNestedInTooltip * toBeInTheDocument => toBeVisible * "inner" => "nested" * Forward rest props to the tooltip instead of the ancor, update snapshots * Format warning message * Add paragraph in docs * Complete comment --- packages/components/CHANGELOG.md | 1 + packages/components/src/tooltip/README.md | 4 ++ packages/components/src/tooltip/index.tsx | 56 ++++++++++++++++--- .../src/tooltip/stories/index.story.tsx | 19 ++++++- .../components/src/tooltip/test/index.tsx | 46 +++++++++++++++ packages/components/src/tooltip/types.ts | 4 ++ 6 files changed, 120 insertions(+), 10 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index c7c1a515a64ce..a8dd57900cfae 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -32,6 +32,7 @@ - `InputControl`, `NumberControl`, `UnitControl`, `SelectControl`, `TreeSelect`: Add `compact` size variant ([#57398](https://github.com/WordPress/gutenberg/pull/57398)). - `ToggleGroupControl`: Update button size in large variant to be 32px ([#57338](https://github.com/WordPress/gutenberg/pull/57338)). - `Tooltip`: improve unit tests ([#57345](https://github.com/WordPress/gutenberg/pull/57345)). +- `Tooltip`: no-op when nested inside other `Tooltip` components ([#57202](https://github.com/WordPress/gutenberg/pull/57202)). ### Experimental diff --git a/packages/components/src/tooltip/README.md b/packages/components/src/tooltip/README.md index 9b214e8fc6b00..ef2cd35d25543 100644 --- a/packages/components/src/tooltip/README.md +++ b/packages/components/src/tooltip/README.md @@ -16,6 +16,10 @@ const MyTooltip = () => ( ); ``` +### Nested tooltips + +In case one or more `Tooltip` components are rendered inside another `Tooltip` component, only the tooltip associated to the outermost `Tooltip` component will be rendered in the browser and shown to the user appropriately. The rest of the nested `Tooltip` components will simply no-op and pass-through their anchor. + ## Props The component accepts the following props: diff --git a/packages/components/src/tooltip/index.tsx b/packages/components/src/tooltip/index.tsx index 817d6d18812ee..1e652d9a42dbb 100644 --- a/packages/components/src/tooltip/index.tsx +++ b/packages/components/src/tooltip/index.tsx @@ -8,22 +8,37 @@ import * as Ariakit from '@ariakit/react'; * WordPress dependencies */ import { useInstanceId } from '@wordpress/compose'; -import { Children } from '@wordpress/element'; +import { Children, cloneElement } from '@wordpress/element'; import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ -import type { TooltipProps } from './types'; +import type { TooltipProps, TooltipInternalContext } from './types'; import Shortcut from '../shortcut'; import { positionToPlacement } from '../popover/utils'; +import { + contextConnect, + useContextSystem, + ContextSystemProvider, +} from '../context'; +import type { WordPressComponentProps } from '../context'; /** * Time over anchor to wait before showing tooltip */ export const TOOLTIP_DELAY = 700; -function Tooltip( props: TooltipProps ) { +const CONTEXT_VALUE = { + Tooltip: { + isNestedInTooltip: true, + }, +}; + +function UnconnectedTooltip( + props: WordPressComponentProps< TooltipProps, 'div', false >, + ref: React.ForwardedRef< any > +) { const { children, delay = TOOLTIP_DELAY, @@ -32,7 +47,15 @@ function Tooltip( props: TooltipProps ) { position, shortcut, text, - } = props; + + // From Internal Context system + isNestedInTooltip, + + ...restProps + } = useContextSystem< typeof props & TooltipInternalContext >( + props, + 'Tooltip' + ); const baseId = useInstanceId( Tooltip, 'tooltip' ); const describedById = text || shortcut ? baseId : undefined; @@ -43,7 +66,7 @@ function Tooltip( props: TooltipProps ) { if ( 'development' === process.env.NODE_ENV ) { // eslint-disable-next-line no-console console.error( - 'Tooltip should be called with only a single child element.' + 'wp-components.Tooltip should be called with only a single child element.' ); } } @@ -64,24 +87,37 @@ function Tooltip( props: TooltipProps ) { } computedPlacement = computedPlacement || 'bottom'; - const tooltipStore = Ariakit.useTooltipStore( { + // Removing the `Ariakit` namespace from the hook name allows ESLint to + // properly identify the hook, and apply the correct linting rules. + const useAriakitTooltipStore = Ariakit.useTooltipStore; + const tooltipStore = useAriakitTooltipStore( { placement: computedPlacement, showTimeout: delay, } ); + if ( isNestedInTooltip ) { + return isOnlyChild + ? cloneElement( children, { + ...restProps, + ref, + } ) + : children; + } + return ( - <> + { isOnlyChild ? undefined : children } { isOnlyChild && ( text || shortcut ) && ( ) } - + ); } +export const Tooltip = contextConnect( UnconnectedTooltip, 'Tooltip' ); + export default Tooltip; diff --git a/packages/components/src/tooltip/stories/index.story.tsx b/packages/components/src/tooltip/stories/index.story.tsx index 760f3dcc23e2f..b006bc03aced9 100644 --- a/packages/components/src/tooltip/stories/index.story.tsx +++ b/packages/components/src/tooltip/stories/index.story.tsx @@ -30,7 +30,7 @@ const meta: Meta< typeof Tooltip > = { 'bottom right', ], }, - shortcut: { control: { type: 'text' } }, + shortcut: { control: { type: 'object' } }, }, parameters: { controls: { expanded: true }, @@ -57,3 +57,20 @@ KeyboardShortcut.args = { ariaLabel: shortcutAriaLabel.primaryShift( ',' ), }, }; + +/** + * In case one or more `Tooltip` components are rendered inside another + * `Tooltip` component, only the tooltip associated to the outermost `Tooltip` + * component will be rendered in the browser and shown to the user + * appropriately. The rest of the nested `Tooltip` components will simply no-op + * and pass-through their anchor. + */ +export const Nested: StoryFn< typeof Tooltip > = Template.bind( {} ); +Nested.args = { + children: ( + + + + ), + text: 'Outer tooltip text', +}; diff --git a/packages/components/src/tooltip/test/index.tsx b/packages/components/src/tooltip/test/index.tsx index cbe144cfa53d4..ed6f7b5f7b4a1 100644 --- a/packages/components/src/tooltip/test/index.tsx +++ b/packages/components/src/tooltip/test/index.tsx @@ -436,4 +436,50 @@ describe( 'Tooltip', () => { await waitExpectTooltipToHide(); } ); } ); + + describe( 'nested', () => { + it( 'should render the outer tooltip and ignore nested tooltips', async () => { + render( + + + + + + + + ); + + // Hover the anchor. Only the outer tooltip should show. + await hover( + screen.getByRole( 'button', { + name: 'Tooltip anchor', + } ) + ); + + await waitFor( () => + expect( + screen.getByRole( 'tooltip', { name: 'Outer tooltip' } ) + ).toBeVisible() + ); + expect( + screen.queryByRole( 'tooltip', { name: 'Middle tooltip' } ) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole( 'tooltip', { name: 'Inner tooltip' } ) + ).not.toBeInTheDocument(); + expect( + screen.getByRole( 'button', { + description: 'Outer tooltip', + } ) + ).toBeVisible(); + + // Hover outside of the anchor, tooltip should hide + await hoverOutside(); + await waitFor( () => + expect( + screen.queryByRole( 'tooltip', { name: 'Outer tooltip' } ) + ).not.toBeInTheDocument() + ); + } ); + } ); } ); diff --git a/packages/components/src/tooltip/types.ts b/packages/components/src/tooltip/types.ts index 8708ae7005f5b..3d28a1a0e96c6 100644 --- a/packages/components/src/tooltip/types.ts +++ b/packages/components/src/tooltip/types.ts @@ -59,3 +59,7 @@ export type TooltipProps = { */ text?: string; }; + +export type TooltipInternalContext = { + isNestedInTooltip?: boolean; +}; From c527d4d72949112f02ba3b9c106b538a4c20061e Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 10 Jan 2024 15:54:18 +0000 Subject: [PATCH 047/296] Update Changelog for 17.5.0-rc.1 --- changelog.txt | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) diff --git a/changelog.txt b/changelog.txt index ba43ddc731d01..9268dc7edd1fb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,261 @@ == Changelog == += 17.5.0-rc.1 = + + + +## Changelog + +### Enhancements + +#### Editor Unification +- Editor: Add the show most used blocks preference to the site editor. ([57637](https://github.com/WordPress/gutenberg/pull/57637)) +- Editor: Migrate and unify the panel preferences. ([57529](https://github.com/WordPress/gutenberg/pull/57529)) +- Editor: Unify context text cursor preference. ([57479](https://github.com/WordPress/gutenberg/pull/57479)) +- Editor: Unify list view open preference. ([57504](https://github.com/WordPress/gutenberg/pull/57504)) +- Editor: Unify right click override preference. ([57468](https://github.com/WordPress/gutenberg/pull/57468)) +- Editor: Unify show icon labels preference. ([57480](https://github.com/WordPress/gutenberg/pull/57480)) +- Editor: Unify spotlight mode preference. ([57533](https://github.com/WordPress/gutenberg/pull/57533)) +- Editor: Unify the distraction free preference. ([57590](https://github.com/WordPress/gutenberg/pull/57590)) +- Editor: Unify the show block breadcrumbs preference. ([57506](https://github.com/WordPress/gutenberg/pull/57506)) +- Editor: Unify the top toolbar preference. ([57531](https://github.com/WordPress/gutenberg/pull/57531)) + +#### Components +- Components: Replace `TabPanel` with `Tabs` in inline color picker. ([57292](https://github.com/WordPress/gutenberg/pull/57292) +- Add `compact` size variant to InputControl-based components. ([57398](https://github.com/WordPress/gutenberg/pull/57398)) +- BaseControl: Connect to context system. ([57408](https://github.com/WordPress/gutenberg/pull/57408)) +- Replace `TabPanel` with `Tabs` in the Style Book. ([57287](https://github.com/WordPress/gutenberg/pull/57287)) +- Tooltip: Improve tests. ([57345](https://github.com/WordPress/gutenberg/pull/57345)) +- Update @ariakit/react to v0.3.12 and @ariakit/test to v0.3.7. ([57547](https://github.com/WordPress/gutenberg/pull/57547)) + +#### Font Library +- Font Library: Remove "has_font_mime_type" function. ([57364](https://github.com/WordPress/gutenberg/pull/57364)) +- Font Library: Update font uninstall modal text. ([57368](https://github.com/WordPress/gutenberg/pull/57368)) +- Font Library: Add progress-bar while uploading font assets. ([57463](https://github.com/WordPress/gutenberg/pull/57463)) +- Font Library: Singularize install font families endpoint. ([57569](https://github.com/WordPress/gutenberg/pull/57569)) +- Font Library: Unregister font collection. ([54701](https://github.com/WordPress/gutenberg/pull/54701)) + +#### Site Editor +- Add Template Modal: Update scroll related layout. ([57617](https://github.com/WordPress/gutenberg/pull/57617)) +- Components: Replace `TabPanel` with `Tabs` in the Font Library `Modal`. ([57181](https://github.com/WordPress/gutenberg/pull/57181)) + +#### Interactivity API +- Implement `wp_initial_state()`. ([57556](https://github.com/WordPress/gutenberg/pull/57556)) +- Server directive processing: Stop processing non-interactive blocks. ([56302](https://github.com/WordPress/gutenberg/pull/56302)) +- Interactive template: Use viewModule. ([57712](https://github.com/WordPress/gutenberg/pull/57712)) +- Navigation Block: Use dom.focus for focus control. ([57362](https://github.com/WordPress/gutenberg/pull/57362)) + + +#### Site Editor +- Site editor: Add padding to entity save panel header. ([57471](https://github.com/WordPress/gutenberg/pull/57471)) +- Site editor: Add margin to entity save panel header via a classname. ([57473](https://github.com/WordPress/gutenberg/pull/57473)) + + +#### Block Library +- Post Featured Image: Add a useFirstImageFromPost attribute. ([56573](https://github.com/WordPress/gutenberg/pull/56573)) +- Gallery Block: Add random order setting. ([57477](https://github.com/WordPress/gutenberg/pull/57477)) +- Image Block: Change upload icon label. ([57704](https://github.com/WordPress/gutenberg/pull/57704)) + + +### Bug Fixes + +- Avoid using a memoized selector without dependencies. ([57257](https://github.com/WordPress/gutenberg/pull/57257)) +- Core Data: Pass the 'options' argument to data action shortcuts. ([57383](https://github.com/WordPress/gutenberg/pull/57383)) +- Preferences: Update accessibility scope to "core". ([57563](https://github.com/WordPress/gutenberg/pull/57563)) + +#### Block Editor +- Fix Link UI displaying out of sync results. ([57522](https://github.com/WordPress/gutenberg/pull/57522)) +- Give iframe fallback background color. ([57330](https://github.com/WordPress/gutenberg/pull/57330)) +- Rich text: Add HTML string methods to RichTextData. ([57322](https://github.com/WordPress/gutenberg/pull/57322)) + +#### Block Library +- Footnotes: Fix wrong link when adding more than 9 footnotes. ([57599](https://github.com/WordPress/gutenberg/pull/57599)) +- Table: Remove unnecessary margin override in editor styles. ([57699](https://github.com/WordPress/gutenberg/pull/57699)) +- Template Part block: Fix template part path arg missing from actions. ([56790](https://github.com/WordPress/gutenberg/pull/56790)) + +#### Components +- DuotonePicker: Fix top margin when no duotone options. ([57489](https://github.com/WordPress/gutenberg/pull/57489)) +- NavigatorProvider: Exclude size value from contain CSS rule. ([57498](https://github.com/WordPress/gutenberg/pull/57498)) +- Snackbar: Fix icon positioning. ([57377](https://github.com/WordPress/gutenberg/pull/57377)) + +#### Patterns +- Pattern Overrides: Add `template-lock: All` to pattern inner blocks to prevent deletion/insertion. ([57661](https://github.com/WordPress/gutenberg/pull/57661)) +- Refactor the findOrCreate term method. ([57655](https://github.com/WordPress/gutenberg/pull/57655)) +- Edit source pattern in focus mode in post and site editors. ([57036](https://github.com/WordPress/gutenberg/pull/57036)) + + +#### Site Editor +- Make sure comamnd palette toggle does not disappear while being clicked. ([57420](https://github.com/WordPress/gutenberg/pull/57420)) +- Reinstate iframe CSS for editor canvas container. ([57503](https://github.com/WordPress/gutenberg/pull/57503)) + +#### Global Styles +- Use `is-layout` pattern on layout generated classname. ([57564](https://github.com/WordPress/gutenberg/pull/57564)) +- Global styles revisions: Add individual headings translations, update tests. ([57472](https://github.com/WordPress/gutenberg/pull/57472)) +- Global style revisions: Move change summary code and tests to block editor package. ([57411](https://github.com/WordPress/gutenberg/pull/57411)) +- Reduce specificity of block style variation selectors. ([57659](https://github.com/WordPress/gutenberg/pull/57659)) +- Background image block support: Add tests for size and repeat output. ([57474](https://github.com/WordPress/gutenberg/pull/57474)) + + +#### Post Editor +- Fix Template preview menu item accessibility. ([57456](https://github.com/WordPress/gutenberg/pull/57456)) +- Fullscreen mode description: Use full text instead of abbreviation. ([57518](https://github.com/WordPress/gutenberg/pull/57518)) +- Improve pre-publish checks naming consistency. ([57019](https://github.com/WordPress/gutenberg/pull/57019)) +- Make the Replace featured image button perceivable by assistive technologies. ([57453](https://github.com/WordPress/gutenberg/pull/57453)) + +#### Components +- Label the HeightControl. ([57683](https://github.com/WordPress/gutenberg/pull/57683)) +- NumberControl: Make increment and decrement buttons keyboard accessible. ([57402](https://github.com/WordPress/gutenberg/pull/57402)) + +#### Block Tools +- Update the position of the patterns tab in the inserter menu. ([55688](https://github.com/WordPress/gutenberg/pull/55688)) +- Use full text instead of abbreviation for min height setting. ([57680](https://github.com/WordPress/gutenberg/pull/57680)) +- ResizableEditor: Fix tab order for resize handles. ([57475](https://github.com/WordPress/gutenberg/pull/57475)) +- Keep Lock button it in the toolbar until unmounted. ([57229](https://github.com/WordPress/gutenberg/pull/57229)) +- Custom field connections: Better description on Experiments page. ([57501](https://github.com/WordPress/gutenberg/pull/57501)) + +### Performance + +#### Block Library +- File: Remove 'block-editor' store subscription. ([57511](https://github.com/WordPress/gutenberg/pull/57511)) +- Remove store subscriptions from Audio and Video blocks. ([57449](https://github.com/WordPress/gutenberg/pull/57449)) +- Site Logo: Remove unnecessary 'block-editor' store subscription. ([57513](https://github.com/WordPress/gutenberg/pull/57513)) +- Send numerical post id when uploading image. ([57388](https://github.com/WordPress/gutenberg/pull/57388)) +- PostFeaturedImage: Remove unnecessary 'block-editor' store subscription. ([57554](https://github.com/WordPress/gutenberg/pull/57554)) + +### Experiments + +#### Data Views +- DataViews: Use DropdownMenuRadioItem component when possible. ([57505](https://github.com/WordPress/gutenberg/pull/57505)) +- Align icon size + placement in Patterns data view. ([57548](https://github.com/WordPress/gutenberg/pull/57548)) +- DataViews: Add `duplicate pattern` action in patterns page. ([57592](https://github.com/WordPress/gutenberg/pull/57592)) +- DataViews: Add duplicate template pattern action. ([57638](https://github.com/WordPress/gutenberg/pull/57638)) +- DataViews: Add footer to Pages sidebar. ([57690](https://github.com/WordPress/gutenberg/pull/57690)) +- DataViews: Add new page button in `Pages`. ([57685](https://github.com/WordPress/gutenberg/pull/57685)) +- DataViews: Add sync filter in patterns page. ([57532](https://github.com/WordPress/gutenberg/pull/57532)) +- DataViews: Consolidate CSS selectors naming schema. ([57651](https://github.com/WordPress/gutenberg/pull/57651)) +- DataViews: Fallback to `(no title)` is there's no rendered title. ([57434](https://github.com/WordPress/gutenberg/pull/57434)) +- DataViews: Hide actions menu upon selecting a layout. ([57418](https://github.com/WordPress/gutenberg/pull/57418)) +- DataViews: Make `fields` dependant on `view.type`. ([57450](https://github.com/WordPress/gutenberg/pull/57450)) +- DataViews: Memoize `onSetSelection`. ([57458](https://github.com/WordPress/gutenberg/pull/57458)) +- DataViews: Prevent unnecessary re-renders of Pagination. ([57454](https://github.com/WordPress/gutenberg/pull/57454)) +- DataViews: Prevent unnecessary re-renders. ([57452](https://github.com/WordPress/gutenberg/pull/57452)) +- DataViews: Update names for `DropdownMenuRadioItemCustom`. ([57416](https://github.com/WordPress/gutenberg/pull/57416)) +- DataViews: Use i18n._x to clarify term "Duplicate". ([57686](https://github.com/WordPress/gutenberg/pull/57686)) +- DataViews: Use in patterns page. ([57333](https://github.com/WordPress/gutenberg/pull/57333)) +- Dataview: Change the stacking order of table header. ([57565](https://github.com/WordPress/gutenberg/pull/57565)) +- Dataviews: Add some client side data handling utils. ([57488](https://github.com/WordPress/gutenberg/pull/57488)) +- Make title display in grid views consistent. ([57553](https://github.com/WordPress/gutenberg/pull/57553)) +- Update Table layout design details. ([57644](https://github.com/WordPress/gutenberg/pull/57644)) +- Update pagination spacing in List layout. ([57670](https://github.com/WordPress/gutenberg/pull/57670)) +- Update table header gap. ([57671](https://github.com/WordPress/gutenberg/pull/57671)) +- [Dataviews] Table layout: Ensure focus is not lost on interaction. ([57340](https://github.com/WordPress/gutenberg/pull/57340)) + +#### Patterns +- [Pattern Overrides] Fix duplication of inner blocks. ([57538](https://github.com/WordPress/gutenberg/pull/57538)) +- [Pattern overrides] Allow multiple attributes overrides. ([57573](https://github.com/WordPress/gutenberg/pull/57573)) + + +### Documentation + +- Add links to additional local dev tools in Block Developement Environment readme. ([57682](https://github.com/WordPress/gutenberg/pull/57682)) +- Add new section to the Quick Start Guide about wp-env. ([57559](https://github.com/WordPress/gutenberg/pull/57559)) +- Block JSON schema: Add renaming key to supports definition. ([57373](https://github.com/WordPress/gutenberg/pull/57373)) +- Break out the Curating the Editor Experience doc into its own How-to Guides section. ([57289](https://github.com/WordPress/gutenberg/pull/57289)) +- Change the slug for the theme.json doc to avoid conflicts. ([57410](https://github.com/WordPress/gutenberg/pull/57410)) +- Docs/tutorial: Fix opposite condition for content generation in render.php. ([57445](https://github.com/WordPress/gutenberg/pull/57445)) +- Docs: Fundamentals of Block Development - Static or Dynamic rendering of a block. ([57250](https://github.com/WordPress/gutenberg/pull/57250)) +- Docs: Update sample code to fix React warning error on Tutorial page. ([57412](https://github.com/WordPress/gutenberg/pull/57412)) +- Fix formatting issue due to incorrect link parsing in the Quick Start Guide. ([57693](https://github.com/WordPress/gutenberg/pull/57693)) +- Fix incorrect heading level in Editor curation documentation. ([57409](https://github.com/WordPress/gutenberg/pull/57409)) +- Fix two typos in tutorial.md. ([57627](https://github.com/WordPress/gutenberg/pull/57627)) +- Fix: Create block getting started links. ([57551](https://github.com/WordPress/gutenberg/pull/57551)) +- Improve the static vs dynamic rendering comment in the block tutorial. ([57284](https://github.com/WordPress/gutenberg/pull/57284)) +- Update copyright year to 2024 in `license.md`. ([57481](https://github.com/WordPress/gutenberg/pull/57481)) +- Update the "Build your first block" tutorial based on user feedback. ([57403](https://github.com/WordPress/gutenberg/pull/57403)) +- Update: Material design icons link. ([57550](https://github.com/WordPress/gutenberg/pull/57550)) + + +### Code Quality +- Editor: Unify the DocumentTools component. ([57214](https://github.com/WordPress/gutenberg/pull/57214)) +- Make getLastFocus and setLastFocus private. ([57612](https://github.com/WordPress/gutenberg/pull/57612)) +- Remove deprecated `behaviors` syntax. ([57165](https://github.com/WordPress/gutenberg/pull/57165)) +- Avoid extra `useMarkPersistent` dispatch calls. ([57435](https://github.com/WordPress/gutenberg/pull/57435)) +- Clean up code editor CSS. ([57519](https://github.com/WordPress/gutenberg/pull/57519)) +- Combine selectors in 'useTransformCommands'. ([57424](https://github.com/WordPress/gutenberg/pull/57424)) + +#### Block Library +- Background image: Add has-background classname when background image is applied. ([57495](https://github.com/WordPress/gutenberg/pull/57495)) +- File: Remove unnecessary synchronization effect. ([57585](https://github.com/WordPress/gutenberg/pull/57585)) +- Navigation: Refactor mobile overlay breakpoints to JS. ([57520](https://github.com/WordPress/gutenberg/pull/57520)) +- Search Block: Remove unused `buttonBehavior` attribute. ([53467](https://github.com/WordPress/gutenberg/pull/53467)) + +#### Patterns +- Improve inserter pattern constants. ([57570](https://github.com/WordPress/gutenberg/pull/57570)) +- Remove duplicate setting for `getPostLinkProps` and prefer stable naming. ([57535](https://github.com/WordPress/gutenberg/pull/57535)) +- Rename `patternBlock` to `patternPost`. ([57568](https://github.com/WordPress/gutenberg/pull/57568)) + +#### Post Editor +- Editor: Use hooks instead of HoCs in 'PostVisibilityCheck'. ([57705](https://github.com/WordPress/gutenberg/pull/57705)) +- Quality: Avoid React warning when changing rendering mode. ([57413](https://github.com/WordPress/gutenberg/pull/57413)) + +#### Block Editor +- Editor: Unify the inserter sidebar. ([57466](https://github.com/WordPress/gutenberg/pull/57466)) +- Remove unused parameters from useOnBlockDrop. ([57527](https://github.com/WordPress/gutenberg/pull/57527)) + +#### List View +- Editor: Unify the list view sidebar between the post and site editors. ([57467](https://github.com/WordPress/gutenberg/pull/57467)) +- Add drag cursor to draggable list items. ([57493](https://github.com/WordPress/gutenberg/pull/57493)) + +### Tools + +- Dependency Extraction Webpack Plugin: Use `import` for module externals. ([57577](https://github.com/WordPress/gutenberg/pull/57577)) +- DependencyExtractionWebpackPlugin: Add true shorthand for requestToExternalModule. ([57593](https://github.com/WordPress/gutenberg/pull/57593)) +- DependencyExtractionWebpackPlugin: Use module for @wordpress/interactivity. ([57602](https://github.com/WordPress/gutenberg/pull/57602)) +- Fix webpack not setting environment.module true. ([57714](https://github.com/WordPress/gutenberg/pull/57714)) +- Modules: Load the import map polyfill when needed. ([57256](https://github.com/WordPress/gutenberg/pull/57256)) +- Blocks: Add handling for block.json viewModule. ([57437](https://github.com/WordPress/gutenberg/pull/57437)) + +#### Testing +- Allowed Patterns end-to-end test - move tests that run with a subset of allowed blocks into a group. ([57496](https://github.com/WordPress/gutenberg/pull/57496)) +- Clean up end-to-end tests package. ([57575](https://github.com/WordPress/gutenberg/pull/57575)) +- Fix flaky 'Post publish button' end-to-end test. ([57407](https://github.com/WordPress/gutenberg/pull/57407)) +- Migrate 'allowed patterns' end-to-end tests to Playwright. ([57399](https://github.com/WordPress/gutenberg/pull/57399)) +- Migrate 'block editor keyboard shortcuts' end-to-end tests to Playwright. ([57422](https://github.com/WordPress/gutenberg/pull/57422)) +- Migrate 'core settings' end-to-end tests to Playwright. ([57581](https://github.com/WordPress/gutenberg/pull/57581)) +- Migrate 'datepicker' end-to-end tests to Playwright. ([57545](https://github.com/WordPress/gutenberg/pull/57545)) +- Migrate 'dropdown menu' end-to-end tests to Playwright. ([57663](https://github.com/WordPress/gutenberg/pull/57663)) +- Migrate 'editor modes' end-to-end tests to Playwright. ([57574](https://github.com/WordPress/gutenberg/pull/57574)) +- Migrate 'invalid blocks' end-to-end tests to Playwright. ([57508](https://github.com/WordPress/gutenberg/pull/57508)) +- Migrate 'nux' end-to-end tests to Playwright. ([57542](https://github.com/WordPress/gutenberg/pull/57542)) +- Migrate 'preferences' end-to-end tests to Playwright. ([57446](https://github.com/WordPress/gutenberg/pull/57446)) +- Migrate 'publishing' end-to-end tests to Playwright. ([57521](https://github.com/WordPress/gutenberg/pull/57521)) +- Migrate 'scheduling' end-to-end tests to Playwright. ([57539](https://github.com/WordPress/gutenberg/pull/57539)) +- Migrate 'sidebar' end-to-end tests to Playwright. ([57448](https://github.com/WordPress/gutenberg/pull/57448)) +- Migrate 'taxonomies' end-to-end tests to Playwright. ([57662](https://github.com/WordPress/gutenberg/pull/57662)) +- Migrate `editing-widgets` to Playwright. ([57483](https://github.com/WordPress/gutenberg/pull/57483)) +- Migrate remaining 'publish panel' end-to-end tests to Playwright. ([57432](https://github.com/WordPress/gutenberg/pull/57432)) +- Update 'missing block' end-to-end tests to use the 'setContent' helper. ([57509](https://github.com/WordPress/gutenberg/pull/57509)) + +#### Build Tooling +- Group GitHub Action Dependabot updates. ([57591](https://github.com/WordPress/gutenberg/pull/57591)) +- WP Scripts: Build block.json viewModule. ([57461](https://github.com/WordPress/gutenberg/pull/57461)) +- Dependency Extraction Webpack Plugin: Add Module support. ([57199](https://github.com/WordPress/gutenberg/pull/57199)) + +## First time contributors + +The following PRs were merged by first time contributors: + +- @HrithikDalal: Font Library: Update font uninstall modal text. ([57368](https://github.com/WordPress/gutenberg/pull/57368)) +- @muhme: Fix two typos in tutorial.md. ([57627](https://github.com/WordPress/gutenberg/pull/57627)) + + +## Contributors + +The following contributors merged PRs in this release: + +@afercia @andrewhayward @andrewserong @atachibana @c4rl0sbr4v0 @carolinan @chad1008 @ciampo @DAreRodz @dcalhoun @derekblank @desrosj @ellatrix @fai-sal @fluiddot @geriux @getdave @glendaviesnz @gziolo @hbhalodia @HrithikDalal @jameskoster @jeryj @jorgefilipecosta @jsnajdr @juanmaguitar @kevin940726 @Mamaduka @matiasbenedetto @mcsf @michalczaplinski @mirka @muhme @ndiego @ntsekouras @oandregal @ockham @ramonjd @scruffian @sirreal @Soean @t-hamano @talldan @tellthemachines @youknowriad + + = 17.3.2 = ## Changelog From d8ef7d615049afa5c6903530c8099ca7b69073c7 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Wed, 10 Jan 2024 16:05:09 -0300 Subject: [PATCH 048/296] Font Library: add wp_get_font_dir() function. (#57730) * adding wp_font_dir() function * use wp_font_dir() instead of Font Library method * update php unit teests * rename function --- .../font-library/class-wp-font-family.php | 9 ++- .../font-library/class-wp-font-library.php | 67 ------------------- ...class-wp-rest-font-families-controller.php | 6 +- .../fonts/font-library/font-library.php | 49 ++++++++++++++ .../{wpFontLibrary => }/fontsDir.php | 22 +++--- .../fonts/font-library/wpFontFamily/base.php | 2 +- .../wpRestFontFamiliesController/base.php | 2 +- 7 files changed, 69 insertions(+), 88 deletions(-) rename phpunit/tests/fonts/font-library/{wpFontLibrary => }/fontsDir.php (67%) diff --git a/lib/experimental/fonts/font-library/class-wp-font-family.php b/lib/experimental/fonts/font-library/class-wp-font-family.php index e47cf0afdac1d..f64aebc0c8efa 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-family.php +++ b/lib/experimental/fonts/font-library/class-wp-font-family.php @@ -135,7 +135,7 @@ public function uninstall() { */ private static function delete_asset( $src ) { $filename = basename( $src ); - $file_path = path_join( WP_Font_Library::get_fonts_dir(), $filename ); + $file_path = path_join( wp_get_font_dir()['path'], $filename ); wp_delete_file( $file_path ); @@ -163,7 +163,6 @@ private static function delete_font_face_assets( $font_face ) { return true; } - /** * Gets the overrides for the 'wp_handle_upload' function. * @@ -394,7 +393,7 @@ private function download_or_move_font_faces( $files ) { // If the font face requires the use of the filesystem, create the fonts dir if it doesn't exist. if ( ! empty( $font_face['downloadFromUrl'] ) && ! empty( $font_face['uploadedFile'] ) ) { - wp_mkdir_p( WP_Font_Library::get_fonts_dir() ); + wp_mkdir_p( wp_get_font_dir()['path'] ); } // If installing google fonts, download the font face assets. @@ -599,9 +598,9 @@ private function create_or_update_font_post() { */ public function install( $files = null ) { add_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); - add_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) ); + add_filter( 'upload_dir', 'wp_get_font_dir' ); $were_assets_written = $this->download_or_move_font_faces( $files ); - remove_filter( 'upload_dir', array( 'WP_Font_Library', 'fonts_dir' ) ); + remove_filter( 'upload_dir', 'wp_get_font_dir' ); remove_filter( 'upload_mimes', array( 'WP_Font_Library', 'set_allowed_mime_types' ) ); if ( ! $were_assets_written ) { diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php index 99de81e0bd74a..01ee702673e57 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -140,74 +140,7 @@ public static function get_font_collection( $id ) { return new WP_Error( 'font_collection_not_found', 'Font collection not found.' ); } - /** - * Returns an array containing the current fonts upload directory's path and URL. - * - * @since 6.5.0 - * - * @param array $defaults { - * Array of information about the upload directory. - * - * @type string $path Base directory and subdirectory or full path to the fonts upload directory. - * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. - * @type string $subdir Subdirectory - * @type string $basedir Path without subdir. - * @type string $baseurl URL path without subdir. - * @type string|false $error False or error message. - * } - * - * @return array $defaults { - * Array of information about the upload directory. - * - * @type string $path Base directory and subdirectory or full path to the fonts upload directory. - * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. - * @type string $subdir Subdirectory - * @type string $basedir Path without subdir. - * @type string $baseurl URL path without subdir. - * @type string|false $error False or error message. - * } - */ - public static function fonts_dir( $defaults = array() ) { - $site_path = self::get_multi_site_dir(); - - // Sets the defaults. - $defaults['path'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; - $defaults['url'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; - $defaults['subdir'] = ''; - $defaults['basedir'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; - $defaults['baseurl'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; - $defaults['error'] = false; - - // Filters the fonts directory data. - return apply_filters( 'fonts_dir', $defaults ); - } - - /** - * Gets the Site dir for fonts, using the blog ID if multi-site, empty otherwise. - * - * @since 6.5.0 - * - * @return string Site dir path. - */ - private static function get_multi_site_dir() { - $font_sub_dir = ''; - if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { - $font_sub_dir = '/sites/' . get_current_blog_id(); - } - return $font_sub_dir; - } - /** - * Gets the upload directory for fonts. - * - * @since 6.5.0 - * - * @return string Path of the upload directory for fonts. - */ - public static function get_fonts_dir() { - $fonts_dir_settings = self::fonts_dir(); - return $fonts_dir_settings['path']; - } /** * Sets the allowed mime types for fonts. diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php index 0147d80b7bde9..ede8762c88c6d 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-families-controller.php @@ -276,7 +276,7 @@ public function update_font_library_permissions_check() { * @return bool Whether the font directory exists. */ private function has_upload_directory() { - $upload_dir = WP_Font_Library::get_fonts_dir(); + $upload_dir = wp_get_font_dir()['path']; return is_dir( $upload_dir ); } @@ -290,7 +290,7 @@ private function has_upload_directory() { private function has_write_permission() { // The update endpoints requires write access to the temp and the fonts directories. $temp_dir = get_temp_dir(); - $upload_dir = WP_Font_Library::get_fonts_dir(); + $upload_dir = wp_get_font_dir()['path']; if ( ! is_writable( $temp_dir ) || ! wp_is_writable( $upload_dir ) ) { return false; } @@ -353,7 +353,7 @@ public function install_fonts( $request ) { } if ( $this->needs_write_permission( $font_family_settings ) ) { - $upload_dir = WP_Font_Library::get_fonts_dir(); + $upload_dir = wp_get_font_dir()['path']; if ( ! $this->has_upload_directory() ) { if ( ! wp_mkdir_p( $upload_dir ) ) { $errors[] = new WP_Error( diff --git a/lib/experimental/fonts/font-library/font-library.php b/lib/experimental/fonts/font-library/font-library.php index 711a6bb40c282..4a2f1be69ef71 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -82,3 +82,52 @@ function wp_unregister_font_collection( $collection_id ) { ); wp_register_font_collection( $default_font_collection ); + +// @core-merge: This code should probably go into Core's src/wp-includes/functions.php. +if ( ! function_exists( 'wp_get_font_dir' ) ) { + /** + * Returns an array containing the current fonts upload directory's path and URL. + * + * @since 6.5.0 + * + * @param array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } + * + * @return array $defaults { + * Array of information about the upload directory. + * + * @type string $path Base directory and subdirectory or full path to the fonts upload directory. + * @type string $url Base URL and subdirectory or absolute URL to the fonts upload directory. + * @type string $subdir Subdirectory + * @type string $basedir Path without subdir. + * @type string $baseurl URL path without subdir. + * @type string|false $error False or error message. + * } + */ + function wp_get_font_dir( $defaults = array() ) { + // Multi site path + $site_path = ''; + if ( is_multisite() && ! ( is_main_network() && is_main_site() ) ) { + $site_path = '/sites/' . get_current_blog_id(); + } + + // Sets the defaults. + $defaults['path'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['url'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['subdir'] = ''; + $defaults['basedir'] = path_join( WP_CONTENT_DIR, 'fonts' ) . $site_path; + $defaults['baseurl'] = untrailingslashit( content_url( 'fonts' ) ) . $site_path; + $defaults['error'] = false; + + // Filters the fonts directory data. + return apply_filters( 'font_dir', $defaults ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php b/phpunit/tests/fonts/font-library/fontsDir.php similarity index 67% rename from phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php rename to phpunit/tests/fonts/font-library/fontsDir.php index 9926bb7409088..5c13f1d120f9a 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/fontsDir.php +++ b/phpunit/tests/fonts/font-library/fontsDir.php @@ -1,6 +1,6 @@ assertEquals( $fonts_dir, $this->dir_defaults ); + $font_dir = wp_get_font_dir(); + $this->assertEquals( $font_dir, $this->dir_defaults ); } public function test_fonts_dir_with_filter() { @@ -43,10 +43,10 @@ function set_new_values( $defaults ) { } // Add the filter. - add_filter( 'fonts_dir', 'set_new_values' ); + add_filter( 'font_dir', 'set_new_values' ); // Gets the fonts dir. - $fonts_dir = WP_Font_Library::fonts_dir(); + $font_dir = wp_get_font_dir(); $expected = array( 'path' => '/custom-path/fonts/my-custom-subdir', @@ -57,14 +57,14 @@ function set_new_values( $defaults ) { 'error' => false, ); - $this->assertEquals( $fonts_dir, $expected, 'The fonts_dir() method should return the expected values.' ); + $this->assertEquals( $font_dir, $expected, 'The wp_get_font_dir() method should return the expected values.' ); // Remove the filter. - remove_filter( 'fonts_dir', 'set_new_values' ); + remove_filter( 'font_dir', 'set_new_values' ); // Gets the fonts dir. - $fonts_dir = WP_Font_Library::fonts_dir(); + $font_dir = wp_get_font_dir(); - $this->assertEquals( $fonts_dir, $this->dir_defaults, 'The fonts_dir() method should return the default values.' ); + $this->assertEquals( $font_dir, $this->dir_defaults, 'The wp_get_font_dir() method should return the default values.' ); } } diff --git a/phpunit/tests/fonts/font-library/wpFontFamily/base.php b/phpunit/tests/fonts/font-library/wpFontFamily/base.php index 3650ac7dab997..3f6ff153fa12f 100644 --- a/phpunit/tests/fonts/font-library/wpFontFamily/base.php +++ b/phpunit/tests/fonts/font-library/wpFontFamily/base.php @@ -28,7 +28,7 @@ abstract class WP_Font_Family_UnitTestCase extends WP_UnitTestCase { public static function set_up_before_class() { parent::set_up_before_class(); - static::$fonts_dir = WP_Font_Library::get_fonts_dir(); + static::$fonts_dir = wp_get_font_dir()['path']; wp_mkdir_p( static::$fonts_dir ); } diff --git a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php index 5ab71a4379851..e2d190cd76af1 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php +++ b/phpunit/tests/fonts/font-library/wpRestFontFamiliesController/base.php @@ -18,7 +18,7 @@ abstract class WP_REST_Font_Families_Controller_UnitTestCase extends WP_UnitTest public function set_up() { parent::set_up(); - static::$fonts_dir = WP_Font_Library::get_fonts_dir(); + static::$fonts_dir = wp_get_font_dir()['path']; // Create a user with administrator role. $admin_id = $this->factory->user->create( From b892bd8be90d2ab26e4a3de9ae1fdbf7a3ec8542 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 10 Jan 2024 13:50:09 -0600 Subject: [PATCH 049/296] Display a disabled lock button if user cannot control lock state (#57274) --- .../src/components/block-lock/toolbar.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-lock/toolbar.js b/packages/block-editor/src/components/block-lock/toolbar.js index ccf04c5e5262d..69f0825ed4be3 100644 --- a/packages/block-editor/src/components/block-lock/toolbar.js +++ b/packages/block-editor/src/components/block-lock/toolbar.js @@ -33,16 +33,24 @@ export default function BlockLockToolbar( { clientId } ) { } }, [ isLocked ] ); - if ( ! canLock || ( ! isLocked && ! hasLockButtonShown.current ) ) { + if ( ! isLocked && ! hasLockButtonShown.current ) { return null; } + let label = isLocked ? __( 'Unlock' ) : __( 'Lock' ); + + if ( ! canLock && isLocked ) { + label = __( 'Locked' ); + } + return ( <> Date: Wed, 10 Jan 2024 20:28:26 -0500 Subject: [PATCH 050/296] docs: Note the mobile E2E tests `jq` dependency (#57741) --- packages/react-native-editor/__device-tests__/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native-editor/__device-tests__/README.md b/packages/react-native-editor/__device-tests__/README.md index e917a297a491c..719adbbcf9426 100644 --- a/packages/react-native-editor/__device-tests__/README.md +++ b/packages/react-native-editor/__device-tests__/README.md @@ -6,6 +6,7 @@ The Mobile Gutenberg (MG) project maintains a suite of automated end-to-end (E2E 1. Complete the [React Native Getting Started](https://reactnative.dev/docs/environment-setup) guide for both iOS and Android, which covers setting up Xcode, Android Studio, the Android SDK. 1. Open [Xcode settings](https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes#Install-and-manage-Simulator-runtimes-in-settings) to install the iOS 16.2 simulator runtime. +1. Install [`jq`](https://jqlang.github.io/jq/download/) via [Homebrew](https://brew.sh/) or your preferred package manager. 1. `npm run native test:e2e:setup` ## Running Tests From d03285c8f6c75c0d6801186c992bc243ddf874ae Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Wed, 10 Jan 2024 21:01:07 -0500 Subject: [PATCH 051/296] chore: Enable offline media upload features (#57731) * chore: Enable offline status indicator * docs: Add change log entry --- packages/edit-post/src/components/layout/index.native.js | 5 +---- packages/react-native-editor/CHANGELOG.md | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/edit-post/src/components/layout/index.native.js b/packages/edit-post/src/components/layout/index.native.js index 50472f7924930..6821aa754eea7 100644 --- a/packages/edit-post/src/components/layout/index.native.js +++ b/packages/edit-post/src/components/layout/index.native.js @@ -152,10 +152,7 @@ class Layout extends Component { onLayout={ this.onRootViewLayout } > - { - // eslint-disable-next-line no-undef - __DEV__ && - } + { isHtmlView ? this.renderHTML() : this.renderVisual() } { ! isHtmlView && Platform.OS === 'android' && ( diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index a62838b066bee..7a1f6997d2a13 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -12,6 +12,7 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [**] Image block media uploads display a custom error message when there is no internet connection [#56937] - [*] Fix missing custom color indicator for custom gradients [#57605] +- [**] Display a notice when a network connection unavailable [#56934] ## 1.110.0 - [*] [internal] Move InserterButton from components package to block-editor package [#56494] From 19ad158863c86152b7fb26ae575696b031068078 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:47:00 +0900 Subject: [PATCH 052/296] Add description to the save panel header when nothing is checked (#57716) * Add description to the save panel header when nothing is checked * Shortening the description --- .../src/components/entities-saved-states/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/editor/src/components/entities-saved-states/index.js b/packages/editor/src/components/entities-saved-states/index.js index b5f3ea5b433e1..8e531ce580101 100644 --- a/packages/editor/src/components/entities-saved-states/index.js +++ b/packages/editor/src/components/entities-saved-states/index.js @@ -213,13 +213,13 @@ export function EntitiesSavedStatesExtensible( { { __( 'Are you ready to save?' ) } { additionalPrompt } - { isDirty && ( -

- { __( - 'The following changes have been made to your site, templates, and content.' - ) } -

- ) } +

+ { isDirty + ? __( + 'The following changes have been made to your site, templates, and content.' + ) + : __( 'Select the items you want to save.' ) } +

{ sortedPartitionedSavables.map( ( list ) => { From beb10327bc8942d5e0a7572242846d8d49d67aac Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Thu, 11 Jan 2024 13:18:21 +0900 Subject: [PATCH 053/296] Gallery Block: Remove duplicate return statement (#57746) --- packages/block-library/src/gallery/index.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/block-library/src/gallery/index.php b/packages/block-library/src/gallery/index.php index 97877141ef333..342264de6fce3 100644 --- a/packages/block-library/src/gallery/index.php +++ b/packages/block-library/src/gallery/index.php @@ -42,9 +42,6 @@ function block_core_gallery_random_order( $parsed_block ) { if ( 'core/gallery' === $parsed_block['blockName'] && ! empty( $parsed_block['attrs']['randomOrder'] ) ) { shuffle( $parsed_block['innerBlocks'] ); } - - return $parsed_block; - return $parsed_block; } From d8676808e85f5e97e1c0bbca20145aaa791e7489 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Thu, 11 Jan 2024 12:47:45 +0200 Subject: [PATCH 054/296] Remove obsolete check from dataviews modal actions title (#57753) --- packages/dataviews/src/item-actions.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/dataviews/src/item-actions.js b/packages/dataviews/src/item-actions.js index 974c5c5d0d5c3..d3d2c62f504fd 100644 --- a/packages/dataviews/src/item-actions.js +++ b/packages/dataviews/src/item-actions.js @@ -59,10 +59,7 @@ function ActionWithModal( { action, item, ActionTrigger } ) { { isModalOpen && ( { setIsModalOpen( false ); From 940f0fe52cfd1e3599e507a7d3f83146914e978f Mon Sep 17 00:00:00 2001 From: Alex Lende Date: Thu, 11 Jan 2024 05:05:15 -0600 Subject: [PATCH 055/296] Add defaultFontSizes option to theme.json (#56661) * Add defaultFontSizes option for theme.json * Fix defaultFontSizes causing errors when missing * Simplify useMergedSettings * Simplify more * Remove the memo because it's probably fine * Try ignoring how overrides are handled in the dropdown * Minor performance improvement using reverse instead of toReversed * Link to a new issue with a more complete description * Refactor hack into its own funtion --- .../theme-json-reference/theme-json-living.md | 1 + lib/class-wp-theme-json-gutenberg.php | 29 +++++----- lib/theme.json | 1 + .../src/components/global-styles/hooks.js | 2 + .../global-styles/typography-panel.js | 58 ++++++++++++++----- packages/block-editor/src/hooks/utils.js | 20 +++++-- packages/block-editor/src/utils/object.js | 16 +++++ schemas/json/theme.json | 5 ++ 8 files changed, 101 insertions(+), 31 deletions(-) diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 4baa5a6009ded..ee88f779ace1c 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -176,6 +176,7 @@ Settings related to typography. | Property | Type | Default | Props | | --- | --- | --- |--- | +| defaultFontSizes | boolean | true | | | customFontSize | boolean | true | | | fontStyle | boolean | true | | | fontWeight | boolean | true | | diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index aa8de83df9597..a063ab34d9069 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -155,7 +155,7 @@ class WP_Theme_JSON_Gutenberg { ), array( 'path' => array( 'typography', 'fontSizes' ), - 'prevent_override' => false, + 'prevent_override' => array( 'typography', 'defaultFontSizes' ), 'use_default_names' => true, 'value_func' => 'gutenberg_get_typography_font_size_value', 'css_vars' => '--wp--preset--font-size--$slug', @@ -411,19 +411,20 @@ class WP_Theme_JSON_Gutenberg { 'defaultPresets' => null, ), 'typography' => array( - 'fluid' => null, - 'customFontSize' => null, - 'dropCap' => null, - 'fontFamilies' => null, - 'fontSizes' => null, - 'fontStyle' => null, - 'fontWeight' => null, - 'letterSpacing' => null, - 'lineHeight' => null, - 'textColumns' => null, - 'textDecoration' => null, - 'textTransform' => null, - 'writingMode' => null, + 'fluid' => null, + 'customFontSize' => null, + 'defaultFontSizes' => null, + 'dropCap' => null, + 'fontFamilies' => null, + 'fontSizes' => null, + 'fontStyle' => null, + 'fontWeight' => null, + 'letterSpacing' => null, + 'lineHeight' => null, + 'textColumns' => null, + 'textDecoration' => null, + 'textTransform' => null, + 'writingMode' => null, ), ); diff --git a/lib/theme.json b/lib/theme.json index c2ed7fdca39ed..b7bc3cb89e60f 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -236,6 +236,7 @@ }, "typography": { "customFontSize": true, + "defaultFontSizes": true, "dropCap": true, "fontSizes": [ { diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index 1fed98cfd229b..2652732807cfd 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -66,6 +66,7 @@ const VALID_SETTINGS = [ 'spacing.units', 'typography.fluid', 'typography.customFontSize', + 'typography.defaultFontSizes', 'typography.dropCap', 'typography.fontFamilies', 'typography.fontSizes', @@ -238,6 +239,7 @@ export function useSettingsForBlockElement( ...updatedSettings.typography, fontSizes: {}, customFontSize: false, + defaultFontSizes: false, }; } diff --git a/packages/block-editor/src/components/global-styles/typography-panel.js b/packages/block-editor/src/components/global-styles/typography-panel.js index 8e6755a6e4c2c..668e8b101be92 100644 --- a/packages/block-editor/src/components/global-styles/typography-panel.js +++ b/packages/block-editor/src/components/global-styles/typography-panel.js @@ -22,7 +22,7 @@ import TextTransformControl from '../text-transform-control'; import TextDecorationControl from '../text-decoration-control'; import WritingModeControl from '../writing-mode-control'; import { getValueFromVariable, TOOLSPANEL_DROPDOWNMENU_PROPS } from './utils'; -import { setImmutably } from '../../utils/object'; +import { setImmutably, uniqByProperty } from '../../utils/object'; const MIN_TEXT_COLUMNS = 1; const MAX_TEXT_COLUMNS = 6; @@ -53,7 +53,10 @@ export function useHasTypographyPanel( settings ) { function useHasFontSizeControl( settings ) { return ( - hasMergedOrigins( settings?.typography?.fontSizes ) || + ( settings?.typography?.defaultFontSizes !== false && + settings?.typography?.fontSizes?.default?.length ) || + settings?.typography?.fontSizes?.theme?.length || + settings?.typography?.fontSizes?.custom?.length || settings?.typography?.customFontSize ); } @@ -100,16 +103,45 @@ function useHasTextColumnsControl( settings ) { return settings?.typography?.textColumns; } -function getUniqueFontSizesBySlug( settings ) { - const fontSizes = settings?.typography?.fontSizes; - const mergedFontSizes = fontSizes ? mergeOrigins( fontSizes ) : []; - const uniqueSizes = []; - for ( const currentSize of mergedFontSizes ) { - if ( ! uniqueSizes.some( ( { slug } ) => slug === currentSize.slug ) ) { - uniqueSizes.push( currentSize ); - } - } - return uniqueSizes; +/** + * TODO: The reversing and filtering of default font sizes is a hack so the + * dropdown UI matches what is generated in the global styles CSS stylesheet. + * + * This is a temporary solution until #57733 is resolved. At which point, + * the mergedFontSizes would just need to be the concatenated array of all + * presets or a custom dropdown with sections for each. + * + * @see {@link https://github.com/WordPress/gutenberg/issues/57733} + * + * @param {Object} settings The global styles settings. + * + * @return {Array} The merged font sizes. + */ +function getMergedFontSizes( settings ) { + // The font size presets are merged in reverse order so that the duplicates + // that may defined later in the array have higher priority to match the CSS. + const mergedFontSizesAll = uniqByProperty( + [ + settings?.typography?.fontSizes?.custom, + settings?.typography?.fontSizes?.theme, + settings?.typography?.fontSizes?.default, + ].flatMap( ( presets ) => presets?.toReversed() ?? [] ), + 'slug' + ).reverse(); + + // Default presets exist in the global styles CSS no matter the setting, so + // filtering them out in the UI has to be done after merging. + const mergedFontSizes = + settings?.typography?.defaultFontSizes === false + ? mergedFontSizesAll.filter( + ( { slug } ) => + ! [ 'small', 'medium', 'large', 'x-large' ].includes( + slug + ) + ) + : mergedFontSizesAll; + + return mergedFontSizes; } function TypographyToolsPanel( { @@ -185,7 +217,7 @@ export default function TypographyPanel( { // Font Size const hasFontSizeEnabled = useHasFontSizeControl( settings ); const disableCustomFontSizes = ! settings?.typography?.customFontSize; - const mergedFontSizes = getUniqueFontSizesBySlug( settings ); + const mergedFontSizes = getMergedFontSizes( settings ); const fontSize = decodeValue( inheritedValue?.typography?.fontSize ); const setFontSize = ( newValue, metadata ) => { diff --git a/packages/block-editor/src/hooks/utils.js b/packages/block-editor/src/hooks/utils.js index 1c597836e9ec5..cd660c85826c2 100644 --- a/packages/block-editor/src/hooks/utils.js +++ b/packages/block-editor/src/hooks/utils.js @@ -177,7 +177,10 @@ export function useBlockSettings( name, parentLayout ) { backgroundImage, backgroundSize, fontFamilies, - fontSizes, + userFontSizes, + themeFontSizes, + defaultFontSizes, + defaultFontSizesEnabled, customFontSize, fontStyle, fontWeight, @@ -222,7 +225,10 @@ export function useBlockSettings( name, parentLayout ) { 'background.backgroundImage', 'background.backgroundSize', 'typography.fontFamilies', - 'typography.fontSizes', + 'typography.fontSizes.custom', + 'typography.fontSizes.theme', + 'typography.fontSizes.default', + 'typography.defaultFontSizes', 'typography.customFontSize', 'typography.fontStyle', 'typography.fontWeight', @@ -304,9 +310,12 @@ export function useBlockSettings( name, parentLayout ) { custom: fontFamilies, }, fontSizes: { - custom: fontSizes, + custom: userFontSizes, + theme: themeFontSizes, + default: defaultFontSizes, }, customFontSize, + defaultFontSizes: defaultFontSizesEnabled, fontStyle, fontWeight, lineHeight, @@ -341,7 +350,10 @@ export function useBlockSettings( name, parentLayout ) { backgroundImage, backgroundSize, fontFamilies, - fontSizes, + userFontSizes, + themeFontSizes, + defaultFontSizes, + defaultFontSizesEnabled, customFontSize, fontStyle, fontWeight, diff --git a/packages/block-editor/src/utils/object.js b/packages/block-editor/src/utils/object.js index 8f6c82a9c3991..c78fe0e656dfe 100644 --- a/packages/block-editor/src/utils/object.js +++ b/packages/block-editor/src/utils/object.js @@ -49,3 +49,19 @@ export const getValueFromObjectPath = ( object, path, defaultValue ) => { } ); return value ?? defaultValue; }; + +/** + * Helper util to filter out objects with duplicate values for a given property. + * + * @param {Object[]} array Array of objects to filter. + * @param {string} property Property to filter unique values by. + * + * @return {Object[]} Array of objects with unique values for the specified property. + */ +export function uniqByProperty( array, property ) { + const seen = new Set(); + return array.filter( ( item ) => { + const value = item[ property ]; + return seen.has( value ) ? false : seen.add( value ); + } ); +} diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 10695f493c40d..9f633fc77ec75 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -484,6 +484,11 @@ "description": "Settings related to typography.", "type": "object", "properties": { + "defaultFontSizes": { + "description": "Allow users to choose font sizes from the default font size presets.", + "type": "boolean", + "default": true + }, "customFontSize": { "description": "Allow users to set custom font sizes.", "type": "boolean", From 2762ec45309469c37d2c90aaf587c25f65b15965 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Thu, 11 Jan 2024 07:20:05 -0500 Subject: [PATCH 056/296] Font Library: Use data or src file to define font collection data (#57734) * Add ability to use data rather than a file to define font collection * format php * avoid returning data when getting the font collection config * fix rest endpoint get_font_collection --------- Co-authored-by: Matias Benedetto --- .../font-library/class-wp-font-collection.php | 59 +++++++++++--- ...ss-wp-rest-font-collections-controller.php | 15 ++-- .../fonts/font-library/font-library.php | 3 +- .../wpFontCollection/__construct.php | 2 +- .../wpFontCollection/getConfig.php | 76 +++++++++++++++++++ .../{getData.php => getConfigAndData.php} | 28 +++++-- 6 files changed, 156 insertions(+), 27 deletions(-) create mode 100644 phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php rename phpunit/tests/fonts/font-library/wpFontCollection/{getData.php => getConfigAndData.php} (71%) diff --git a/lib/experimental/fonts/font-library/class-wp-font-collection.php b/lib/experimental/fonts/font-library/class-wp-font-collection.php index e8cc7c98fe730..79b0691ede273 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-collection.php +++ b/lib/experimental/fonts/font-library/class-wp-font-collection.php @@ -35,7 +35,7 @@ class WP_Font_Collection { * @since 6.5.0 * * @param array $config Font collection config options. - * See {@see wp_register_font_collection()} for the supported fields. + * See {@see wp_register_font_collection()} for the supported fields. * @throws Exception If the required parameters are missing. */ public function __construct( $config ) { @@ -51,8 +51,8 @@ public function __construct( $config ) { throw new Exception( 'Font Collection config name is required as a non-empty string.' ); } - if ( empty( $config['src'] ) || ! is_string( $config['src'] ) ) { - throw new Exception( 'Font Collection config "src" option is required as a non-empty string.' ); + if ( ( empty( $config['src'] ) || ! is_string( $config['src'] ) ) && ( empty( $config['data'] ) ) ) { + throw new Exception( 'Font Collection config "src" option OR "data" option is required.' ); } $this->config = $config; @@ -63,21 +63,59 @@ public function __construct( $config ) { * * @since 6.5.0 * - * @return array An array containing the font collection config. + * @return array { + * An array of font collection config. + * + * @type string $id The font collection's unique ID. + * @type string $name The font collection's name. + * @type string $description The font collection's description. + * } */ public function get_config() { - return $this->config; + return array( + 'id' => $this->config['id'], + 'name' => $this->config['name'], + 'description' => $this->config['description'] ?? '', + ); } /** - * Gets the font collection data. + * Gets the font collection config and data. + * + * This function returns an array containing the font collection's unique ID, + * name, and its data as a PHP array. * * @since 6.5.0 * - * @return array|WP_Error An array containing the list of font families in theme.json format on success, + * @return array { + * An array of font collection config and data. + * + * @type string $id The font collection's unique ID. + * @type string $name The font collection's name. + * @type string $description The font collection's description. + * @type array $data The font collection's data as a PHP array. + * } + */ + public function get_config_and_data() { + $config_and_data = $this->get_config(); + $config_and_data['data'] = $this->load_data(); + return $config_and_data; + } + + /** + * Loads the font collection data. + * + * @since 6.5.0 + * + * @return array|WP_Error An array containing the list of font families in font-collection.json format on success, * else an instance of WP_Error on failure. */ - public function get_data() { + public function load_data() { + + if ( ! empty( $this->config['data'] ) ) { + return $this->config['data']; + } + // If the src is a URL, fetch the data from the URL. if ( str_contains( $this->config['src'], 'http' ) && str_contains( $this->config['src'], '://' ) ) { if ( ! wp_http_validate_url( $this->config['src'] ) ) { @@ -104,9 +142,6 @@ public function get_data() { } } - $collection_data = $this->get_config(); - $collection_data['data'] = $data; - unset( $collection_data['src'] ); - return $collection_data; + return $data; } } diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php index 2367cba0b870a..0efd56f61d43e 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php @@ -77,13 +77,16 @@ public function get_font_collection( $request ) { $collection->add_data( array( 'status' => 404 ) ); return $collection; } - $collection_with_data = $collection->get_data(); + $config_and_data = $collection->get_config_and_data(); + $collection_data = $config_and_data['data']; + // If there was an error getting the collection data, return the error. - if ( is_wp_error( $collection_with_data ) ) { - $collection_with_data->add_data( array( 'status' => 500 ) ); - return $collection_with_data; + if ( is_wp_error( $collection_data ) ) { + $collection_data->add_data( array( 'status' => 500 ) ); + return $collection_data; } - return new WP_REST_Response( $collection_with_data ); + + return new WP_REST_Response( $config_and_data ); } /** @@ -96,7 +99,7 @@ public function get_font_collection( $request ) { public function get_font_collections() { $collections = array(); foreach ( WP_Font_Library::get_font_collections() as $collection ) { - $collections[] = $collection->get_config(); + $collections[] = $collection->get_config_and_data(); } return new WP_REST_Response( $collections, 200 ); diff --git a/lib/experimental/fonts/font-library/font-library.php b/lib/experimental/fonts/font-library/font-library.php index 4a2f1be69ef71..c2545171dace0 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -50,7 +50,8 @@ function gutenberg_init_font_library_routes() { * Font collection associative array of configuration options. * * @type string $id The font collection's unique ID. - * @type string $src The font collection's data JSON file. + * @type string $src The font collection's data as a JSON file path. + * @type array $data The font collection's data as a PHP array. * } * @return WP_Font_Collection|WP_Error A font collection is it was registered * successfully, else WP_Error. diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php index 5c2b7b5c02793..21f529d71a297 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php @@ -84,7 +84,7 @@ public function data_should_throw_exception() { 'name' => 'My Collection', 'description' => 'My collection description', ), - 'Font Collection config "src" option is required as a non-empty string.', + 'Font Collection config "src" option OR "data" option is required.', ), ); diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php b/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php new file mode 100644 index 0000000000000..03f59b6a1a6eb --- /dev/null +++ b/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php @@ -0,0 +1,76 @@ +assertSame( $expected_data, $collection->get_config() ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_should_get_config() { + $mock_file = wp_tempnam( 'my-collection-data-' ); + file_put_contents( $mock_file, '{"this is mock data":true}' ); + + return array( + 'with a file' => array( + 'config' => array( + 'id' => 'my-collection', + 'name' => 'My Collection', + 'description' => 'My collection description', + 'src' => $mock_file, + ), + 'expected_data' => array( + 'id' => 'my-collection', + 'name' => 'My Collection', + 'description' => 'My collection description', + ), + ), + 'with a url' => array( + 'config' => array( + 'id' => 'my-collection-with-url', + 'name' => 'My Collection with URL', + 'description' => 'My collection description', + 'src' => 'https://localhost/fonts/mock-font-collection.json', + ), + 'expected_data' => array( + 'id' => 'my-collection-with-url', + 'name' => 'My Collection with URL', + 'description' => 'My collection description', + ), + ), + 'with data' => array( + 'config' => array( + 'id' => 'my-collection', + 'name' => 'My Collection', + 'description' => 'My collection description', + 'data' => array( 'this is mock data' => true ), + ), + 'expected_data' => array( + 'id' => 'my-collection', + 'name' => 'My Collection', + 'description' => 'My collection description', + ), + ), + ); + } +} diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getData.php b/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php similarity index 71% rename from phpunit/tests/fonts/font-library/wpFontCollection/getData.php rename to phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php index 4d0b2eb92b595..a71928193c9aa 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/getData.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php @@ -1,6 +1,6 @@ assertSame( $expected_data, $collection->get_data() ); + $this->assertSame( $expected_data, $collection->get_config_and_data() ); } /** @@ -62,7 +62,7 @@ public function test_should_get_data( $config, $expected_data ) { * * @return array[] */ - public function data_should_get_data() { + public function data_should_get_config_and_data() { $mock_file = wp_tempnam( 'my-collection-data-' ); file_put_contents( $mock_file, '{"this is mock data":true}' ); @@ -98,6 +98,20 @@ public function data_should_get_data() { ), ), ), + 'with data' => array( + 'config' => array( + 'id' => 'my-collection', + 'name' => 'My Collection', + 'description' => 'My collection description', + 'data' => array( 'this is mock data' => true ), + ), + 'expected_data' => array( + 'id' => 'my-collection', + 'name' => 'My Collection', + 'description' => 'My collection description', + 'data' => array( 'this is mock data' => true ), + ), + ), ); } } From 8599e15cd2d80da4f213b0c7e56884ab8171d6ad Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Thu, 11 Jan 2024 08:04:17 -0500 Subject: [PATCH 057/296] Use `slug` instead of `id` for Font Collection (#57735) * Add ability to use data rather than a file to define font collection * Use slug instead of id for Font Collections * format php * merge file changes * format php --------- Co-authored-by: Matias Benedetto --- .../font-library/class-wp-font-collection.php | 10 +++--- .../font-library/class-wp-font-library.php | 36 +++++++++---------- ...ss-wp-rest-font-collections-controller.php | 6 ++-- .../fonts/font-library/font-library.php | 2 +- .../wpFontCollection/__construct.php | 6 ++-- .../wpFontCollection/getConfig.php | 12 +++---- .../wpFontCollection/getConfigAndData.php | 12 +++---- .../wpFontLibrary/getFontCollection.php | 4 +-- .../wpFontLibrary/getFontCollections.php | 2 +- .../wpFontLibrary/registerFontCollection.php | 16 ++++----- .../unregisterFontCollection.php | 4 +-- .../getFontCollection.php | 8 ++--- .../getFontCollections.php | 4 +-- .../registerRoutes.php | 4 +-- 14 files changed, 63 insertions(+), 63 deletions(-) diff --git a/lib/experimental/fonts/font-library/class-wp-font-collection.php b/lib/experimental/fonts/font-library/class-wp-font-collection.php index 79b0691ede273..6189da5fa984b 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-collection.php +++ b/lib/experimental/fonts/font-library/class-wp-font-collection.php @@ -43,8 +43,8 @@ public function __construct( $config ) { throw new Exception( 'Font Collection config options is required as a non-empty array.' ); } - if ( empty( $config['id'] ) || ! is_string( $config['id'] ) ) { - throw new Exception( 'Font Collection config ID is required as a non-empty string.' ); + if ( empty( $config['slug'] ) || ! is_string( $config['slug'] ) ) { + throw new Exception( 'Font Collection config slug is required as a non-empty string.' ); } if ( empty( $config['name'] ) || ! is_string( $config['name'] ) ) { @@ -66,14 +66,14 @@ public function __construct( $config ) { * @return array { * An array of font collection config. * - * @type string $id The font collection's unique ID. + * @type string $slug The font collection's unique slug. * @type string $name The font collection's name. * @type string $description The font collection's description. * } */ public function get_config() { return array( - 'id' => $this->config['id'], + 'slug' => $this->config['slug'], 'name' => $this->config['name'], 'description' => $this->config['description'] ?? '', ); @@ -90,7 +90,7 @@ public function get_config() { * @return array { * An array of font collection config and data. * - * @type string $id The font collection's unique ID. + * @type string $slug The font collection's unique ID. * @type string $name The font collection's name. * @type string $description The font collection's description. * @type array $data The font collection's data as a PHP array. diff --git a/lib/experimental/fonts/font-library/class-wp-font-library.php b/lib/experimental/fonts/font-library/class-wp-font-library.php index 01ee702673e57..fd36f6ba073c4 100644 --- a/lib/experimental/fonts/font-library/class-wp-font-library.php +++ b/lib/experimental/fonts/font-library/class-wp-font-library.php @@ -63,11 +63,11 @@ public static function get_expected_font_mime_types_per_php_version( $php_versio */ public static function register_font_collection( $config ) { $new_collection = new WP_Font_Collection( $config ); - if ( self::is_collection_registered( $config['id'] ) ) { + if ( self::is_collection_registered( $config['slug'] ) ) { $error_message = sprintf( - /* translators: %s: Font collection id. */ - __( 'Font collection with id: "%s" is already registered.', 'default' ), - $config['id'] + /* translators: %s: Font collection slug. */ + __( 'Font collection with slug: "%s" is already registered.', 'default' ), + $config['slug'] ); _doing_it_wrong( __METHOD__, @@ -76,7 +76,7 @@ public static function register_font_collection( $config ) { ); return new WP_Error( 'font_collection_registration_error', $error_message ); } - self::$collections[ $config['id'] ] = $new_collection; + self::$collections[ $config['slug'] ] = $new_collection; return $new_collection; } @@ -85,20 +85,20 @@ public static function register_font_collection( $config ) { * * @since 6.5.0 * - * @param string $collection_id Font collection ID. + * @param string $collection_slug Font collection slug. * @return bool True if the font collection was unregistered successfully and false otherwise. */ - public static function unregister_font_collection( $collection_id ) { - if ( ! self::is_collection_registered( $collection_id ) ) { + public static function unregister_font_collection( $slug ) { + if ( ! self::is_collection_registered( $slug ) ) { _doing_it_wrong( __METHOD__, - /* translators: %s: Font collection id. */ - sprintf( __( 'Font collection "%s" not found.', 'default' ), $collection_id ), + /* translators: %s: Font collection slug. */ + sprintf( __( 'Font collection "%s" not found.', 'default' ), $slug ), '6.5.0' ); return false; } - unset( self::$collections[ $collection_id ] ); + unset( self::$collections[ $slug ] ); return true; } @@ -107,11 +107,11 @@ public static function unregister_font_collection( $collection_id ) { * * @since 6.5.0 * - * @param string $collection_id Font collection ID. + * @param string $slug Font collection slug. * @return bool True if the font collection is registered and false otherwise. */ - private static function is_collection_registered( $collection_id ) { - return array_key_exists( $collection_id, self::$collections ); + private static function is_collection_registered( $slug ) { + return array_key_exists( $slug, self::$collections ); } /** @@ -130,12 +130,12 @@ public static function get_font_collections() { * * @since 6.5.0 * - * @param string $id Font collection id. + * @param string $slug Font collection slug. * @return array List of font collections. */ - public static function get_font_collection( $id ) { - if ( array_key_exists( $id, self::$collections ) ) { - return self::$collections[ $id ]; + public static function get_font_collection( $slug ) { + if ( array_key_exists( $slug, self::$collections ) ) { + return self::$collections[ $slug ]; } return new WP_Error( 'font_collection_not_found', 'Font collection not found.' ); } diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php index 0efd56f61d43e..c7595a56413b9 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-collections-controller.php @@ -50,7 +50,7 @@ public function register_routes() { register_rest_route( $this->namespace, - '/' . $this->rest_base . '/(?P[\/\w-]+)', + '/' . $this->rest_base . '/(?P[\/\w-]+)', array( array( 'methods' => WP_REST_Server::READABLE, @@ -70,8 +70,8 @@ public function register_routes() { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_font_collection( $request ) { - $id = $request->get_param( 'id' ); - $collection = WP_Font_Library::get_font_collection( $id ); + $slug = $request->get_param( 'slug' ); + $collection = WP_Font_Library::get_font_collection( $slug ); // If the collection doesn't exist returns a 404. if ( is_wp_error( $collection ) ) { $collection->add_data( array( 'status' => 404 ) ); diff --git a/lib/experimental/fonts/font-library/font-library.php b/lib/experimental/fonts/font-library/font-library.php index c2545171dace0..d1ad8e1447ad9 100644 --- a/lib/experimental/fonts/font-library/font-library.php +++ b/lib/experimental/fonts/font-library/font-library.php @@ -76,7 +76,7 @@ function wp_unregister_font_collection( $collection_id ) { } $default_font_collection = array( - 'id' => 'default-font-collection', + 'slug' => 'default-font-collection', 'name' => 'Google Fonts', 'description' => __( 'Add from Google Fonts. Fonts are copied to and served from your site.', 'gutenberg' ), 'src' => 'https://s.w.org/images/fonts/16.7/collections/google-fonts-with-preview.json', diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php index 21f529d71a297..380226ee8af8a 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/__construct.php @@ -17,7 +17,7 @@ public function test_should_initialize_data() { $property->setAccessible( true ); $config = array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', 'src' => 'my-collection-data.json', @@ -55,7 +55,7 @@ public function data_should_throw_exception() { 'description' => 'My collection description', 'src' => 'my-collection-data.json', ), - 'Font Collection config ID is required as a non-empty string.', + 'Font Collection config slug is required as a non-empty string.', ), 'no config' => array( @@ -80,7 +80,7 @@ public function data_should_throw_exception() { 'missing src' => array( array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', ), diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php b/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php index 03f59b6a1a6eb..5f1f082297d41 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/getConfig.php @@ -34,39 +34,39 @@ public function data_should_get_config() { return array( 'with a file' => array( 'config' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', 'src' => $mock_file, ), 'expected_data' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', ), ), 'with a url' => array( 'config' => array( - 'id' => 'my-collection-with-url', + 'slug' => 'my-collection-with-url', 'name' => 'My Collection with URL', 'description' => 'My collection description', 'src' => 'https://localhost/fonts/mock-font-collection.json', ), 'expected_data' => array( - 'id' => 'my-collection-with-url', + 'slug' => 'my-collection-with-url', 'name' => 'My Collection with URL', 'description' => 'My collection description', ), ), 'with data' => array( 'config' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', 'data' => array( 'this is mock data' => true ), ), 'expected_data' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', ), diff --git a/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php b/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php index a71928193c9aa..885b0a0b9036c 100644 --- a/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php +++ b/phpunit/tests/fonts/font-library/wpFontCollection/getConfigAndData.php @@ -69,13 +69,13 @@ public function data_should_get_config_and_data() { return array( 'with a file' => array( 'config' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', 'src' => $mock_file, ), 'expected_data' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', 'data' => array( 'this is mock data' => true ), @@ -83,13 +83,13 @@ public function data_should_get_config_and_data() { ), 'with a url' => array( 'config' => array( - 'id' => 'my-collection-with-url', + 'slug' => 'my-collection-with-url', 'name' => 'My Collection with URL', 'description' => 'My collection description', 'src' => 'https://localhost/fonts/mock-font-collection.json', ), 'expected_data' => array( - 'id' => 'my-collection-with-url', + 'slug' => 'my-collection-with-url', 'name' => 'My Collection with URL', 'description' => 'My collection description', 'data' => array( @@ -100,13 +100,13 @@ public function data_should_get_config_and_data() { ), 'with data' => array( 'config' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', 'data' => array( 'this is mock data' => true ), ), 'expected_data' => array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My collection description', 'data' => array( 'this is mock data' => true ), diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php index 00d5ca2dcb2e7..082ca89211465 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollection.php @@ -14,7 +14,7 @@ class Tests_Fonts_WpFontLibrary_GetFontCollection extends WP_Font_Library_UnitTe public function test_should_get_font_collection() { $my_font_collection_config = array( - 'id' => 'my-font-collection', + 'slug' => 'my-font-collection', 'name' => 'My Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => path_join( __DIR__, 'my-font-collection-data.json' ), @@ -24,7 +24,7 @@ public function test_should_get_font_collection() { $this->assertInstanceOf( 'WP_Font_Collection', $font_collection ); } - public function test_should_get_no_font_collection_if_the_id_is_not_registered() { + public function test_should_get_no_font_collection_if_the_slug_is_not_registered() { $font_collection = WP_Font_Library::get_font_collection( 'not-registered-font-collection' ); $this->assertWPError( $font_collection ); } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php index 40eacba8e18c5..a405584efccc2 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/getFontCollections.php @@ -18,7 +18,7 @@ public function test_should_get_an_empty_list() { public function test_should_get_mock_font_collection() { $my_font_collection_config = array( - 'id' => 'my-font-collection', + 'slug' => 'my-font-collection', 'name' => 'My Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => path_join( __DIR__, 'my-font-collection-data.json' ), diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php index 2569830f6bf2a..a7ea2870957e9 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/registerFontCollection.php @@ -14,7 +14,7 @@ class Tests_Fonts_WpFontLibrary_RegisterFontCollection extends WP_Font_Library_U public function test_should_register_font_collection() { $config = array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'name' => 'My Collection', 'description' => 'My Collection Description', 'src' => 'my-collection-data.json', @@ -23,20 +23,20 @@ public function test_should_register_font_collection() { $this->assertInstanceOf( 'WP_Font_Collection', $collection ); } - public function test_should_return_error_if_id_is_missing() { + public function test_should_return_error_if_slug_is_missing() { $config = array( 'name' => 'My Collection', 'description' => 'My Collection Description', 'src' => 'my-collection-data.json', ); $this->expectException( 'Exception' ); - $this->expectExceptionMessage( 'Font Collection config ID is required as a non-empty string.' ); + $this->expectExceptionMessage( 'Font Collection config slug is required as a non-empty string.' ); WP_Font_Library::register_font_collection( $config ); } public function test_should_return_error_if_name_is_missing() { $config = array( - 'id' => 'my-collection', + 'slug' => 'my-collection', 'description' => 'My Collection Description', 'src' => 'my-collection-data.json', ); @@ -52,15 +52,15 @@ public function test_should_return_error_if_config_is_empty() { WP_Font_Library::register_font_collection( $config ); } - public function test_should_return_error_if_id_is_repeated() { + public function test_should_return_error_if_slug_is_repeated() { $config1 = array( - 'id' => 'my-collection-1', + 'slug' => 'my-collection-1', 'name' => 'My Collection 1', 'description' => 'My Collection 1 Description', 'src' => 'my-collection-1-data.json', ); $config2 = array( - 'id' => 'my-collection-1', + 'slug' => 'my-collection-1', 'name' => 'My Collection 2', 'description' => 'My Collection 2 Description', 'src' => 'my-collection-2-data.json', @@ -72,7 +72,7 @@ public function test_should_return_error_if_id_is_repeated() { // Expects a _doing_it_wrong notice. $this->setExpectedIncorrectUsage( 'WP_Font_Library::register_font_collection' ); - // Try to register a second collection with same id. + // Try to register a second collection with same slug. $collection2 = WP_Font_Library::register_font_collection( $config2 ); $this->assertWPError( $collection2, 'A WP_Error should be returned.' ); } diff --git a/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php index e6e16956814fb..3c19a1d2089e7 100644 --- a/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpFontLibrary/unregisterFontCollection.php @@ -15,7 +15,7 @@ class Tests_Fonts_WpFontLibrary_UnregisterFontCollection extends WP_Font_Library public function test_should_unregister_font_collection() { // Registers two mock font collections. $config = array( - 'id' => 'mock-font-collection-1', + 'slug' => 'mock-font-collection-1', 'name' => 'Mock Collection to be unregistered', 'description' => 'A mock font collection to be unregistered.', 'src' => 'my-collection-data.json', @@ -23,7 +23,7 @@ public function test_should_unregister_font_collection() { WP_Font_Library::register_font_collection( $config ); $config = array( - 'id' => 'mock-font-collection-2', + 'slug' => 'mock-font-collection-2', 'name' => 'Mock Collection', 'description' => 'A mock font collection.', 'src' => 'my-mock-data.json', diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollection.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollection.php index 94e7daaa16634..c9d003389997b 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollection.php +++ b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollection.php @@ -25,7 +25,7 @@ public function set_up() { add_filter( 'pre_http_request', array( $this, 'mock_request' ), 10, 3 ); $config_with_file = array( - 'id' => 'one-collection', + 'slug' => 'one-collection', 'name' => 'One Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => $mock_file, @@ -33,7 +33,7 @@ public function set_up() { wp_register_font_collection( $config_with_file ); $config_with_url = array( - 'id' => 'collection-with-url', + 'slug' => 'collection-with-url', 'name' => 'Another Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => 'https://wordpress.org/fonts/mock-font-collection.json', @@ -42,7 +42,7 @@ public function set_up() { wp_register_font_collection( $config_with_url ); $config_with_non_existing_file = array( - 'id' => 'collection-with-non-existing-file', + 'slug' => 'collection-with-non-existing-file', 'name' => 'Another Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => '/home/non-existing-file.json', @@ -51,7 +51,7 @@ public function set_up() { wp_register_font_collection( $config_with_non_existing_file ); $config_with_non_existing_url = array( - 'id' => 'collection-with-non-existing-url', + 'slug' => 'collection-with-non-existing-url', 'name' => 'Another Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => 'https://non-existing-url-1234x.com.ar/fake-path/missing-file.json', diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollections.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollections.php index 224dab07cf0b7..0a8d24e8f392b 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollections.php +++ b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/getFontCollections.php @@ -27,7 +27,7 @@ public function test_get_font_collections() { // Add a font collection. $config = array( - 'id' => 'my-font-collection', + 'slug' => 'my-font-collection', 'name' => 'My Font Collection', 'description' => 'Demo about how to a font collection to your WordPress Font Library.', 'src' => $mock_file, @@ -39,7 +39,7 @@ public function test_get_font_collections() { $data = $response->get_data(); $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); $this->assertCount( 1, $data, 'The response data is not an array with one element.' ); - $this->assertArrayHasKey( 'id', $data[0], 'The response data does not have the key with the collection ID.' ); + $this->assertArrayHasKey( 'slug', $data[0], 'The response data does not have the key with the collection slug.' ); $this->assertArrayHasKey( 'name', $data[0], 'The response data does not have the key with the collection name.' ); } } diff --git a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/registerRoutes.php b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/registerRoutes.php index c2c019fa70a02..fb100a400fb4c 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/registerRoutes.php +++ b/phpunit/tests/fonts/font-library/wpRestFontCollectionsController/registerRoutes.php @@ -16,9 +16,9 @@ class Tests_Fonts_WPRESTFontCollectionsController_RegisterRoutes extends WP_Unit public function test_register_routes() { $routes = rest_get_server()->get_routes(); $this->assertCount( 1, $routes['/wp/v2/font-collections'], 'Rest server has not the collections path initialized.' ); - $this->assertCount( 1, $routes['/wp/v2/font-collections/(?P[\/\w-]+)'], 'Rest server has not the collection path initialized.' ); + $this->assertCount( 1, $routes['/wp/v2/font-collections/(?P[\/\w-]+)'], 'Rest server has not the collection path initialized.' ); $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections'][0]['methods'], 'Rest server has not the GET method for collections intialized.' ); - $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection intialized.' ); + $this->assertArrayHasKey( 'GET', $routes['/wp/v2/font-collections/(?P[\/\w-]+)'][0]['methods'], 'Rest server has not the GET method for collection intialized.' ); } } From 47026abfabe59f9baa89e18931d457a85c72ff65 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Thu, 11 Jan 2024 16:15:10 +0200 Subject: [PATCH 058/296] Group templates in sidebar list (#57711) * Group templates in sidebar list * address feedback * Section heading styling --------- Co-authored-by: James Koster --- .../index.js | 113 +++++++++++++----- .../style.scss | 9 ++ packages/edit-site/src/style.scss | 1 + 3 files changed, 95 insertions(+), 28 deletions(-) create mode 100644 packages/edit-site/src/components/sidebar-navigation-screen-templates/style.scss diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-templates/index.js index 527cb37ceddaf..3ff934ac100a8 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-templates/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates/index.js @@ -4,6 +4,7 @@ import { __experimentalItemGroup as ItemGroup, __experimentalItem as Item, + __experimentalVStack as VStack, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { useEntityRecords } from '@wordpress/core-data'; @@ -30,20 +31,11 @@ const TemplateItem = ( { postType, postId, ...props } ) => { export default function SidebarNavigationScreenTemplates() { const isMobileViewport = useViewportMatch( 'medium', '<' ); - const { records: templates, isResolving: isLoading } = useEntityRecords( 'postType', TEMPLATE_POST_TYPE, - { - per_page: -1, - } - ); - - const sortedTemplates = templates ? [ ...templates ] : []; - sortedTemplates.sort( ( a, b ) => - a.title.rendered.localeCompare( b.title.rendered ) + { per_page: -1 } ); - const browseAllLink = useLink( { path: '/wp_template/all' } ); const canCreate = ! isMobileViewport; return ( @@ -66,24 +58,7 @@ export default function SidebarNavigationScreenTemplates() { <> { isLoading && __( 'Loading templates…' ) } { ! isLoading && ( - - { ! templates?.length && ( - { __( 'No templates found' ) } - ) } - { sortedTemplates.map( ( template ) => ( - - { decodeEntities( - template.title?.rendered || - template.slug - ) } - - ) ) } - + ) } } @@ -97,3 +72,85 @@ export default function SidebarNavigationScreenTemplates() { /> ); } + +function TemplatesGroup( { title, templates } ) { + return ( + + { !! title && ( + + { title } + + ) } + { templates.map( ( template ) => ( + + { decodeEntities( + template.title?.rendered || template.slug + ) } + + ) ) } + + ); +} +function SidebarTemplatesList( { templates } ) { + if ( ! templates?.length ) { + return ( + + { __( 'No templates found' ) } + + ); + } + const sortedTemplates = templates ? [ ...templates ] : []; + sortedTemplates.sort( ( a, b ) => + a.title.rendered.localeCompare( b.title.rendered ) + ); + const { hierarchyTemplates, customTemplates, ...plugins } = + sortedTemplates.reduce( + ( accumulator, template ) => { + const { + original_source: originalSource, + author_text: authorText, + } = template; + if ( originalSource === 'plugin' ) { + if ( ! accumulator[ authorText ] ) { + accumulator[ authorText ] = []; + } + accumulator[ authorText ].push( template ); + } else if ( template.is_custom ) { + accumulator.customTemplates.push( template ); + } else { + accumulator.hierarchyTemplates.push( template ); + } + return accumulator; + }, + { hierarchyTemplates: [], customTemplates: [] } + ); + return ( + + { !! hierarchyTemplates.length && ( + + ) } + { !! customTemplates.length && ( + + ) } + { Object.entries( plugins ).map( + ( [ plugin, pluginTemplates ] ) => { + return ( + + ); + } + ) } + + ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-templates/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-templates/style.scss new file mode 100644 index 0000000000000..ec2b7744d4e23 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-templates/style.scss @@ -0,0 +1,9 @@ +.edit-site-sidebar-navigation-screen-templates__templates-group-title.components-item { + text-transform: uppercase; + color: $gray-200; + // 6px right padding to align with + button + padding: $grid-unit-30 6px $grid-unit-20 $grid-unit-20; + border-top: 1px solid $gray-800; + font-size: 11px; + font-weight: 500; +} diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index c7d0609b4e771..164a8523b1962 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -34,6 +34,7 @@ @import "./components/sidebar-navigation-screen-details-footer/style.scss"; @import "./components/sidebar-navigation-screen-navigation-menu/style.scss"; @import "./components/sidebar-navigation-screen-page/style.scss"; +@import "./components/sidebar-navigation-screen-templates/style.scss"; @import "components/sidebar-navigation-screen-details-panel/style.scss"; @import "./components/sidebar-navigation-screen-pattern/style.scss"; @import "./components/sidebar-navigation-screen-patterns/style.scss"; From b64eec7b536e83b72b24c776a8181889f96a9abd Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Thu, 11 Jan 2024 15:17:15 +0100 Subject: [PATCH 059/296] Fix Widgets page Undo and Redo accessibility and keyboard interaction (#57677) * Fix styling and focus style. * Fix Widgets toolbar Undo and Redo buttons keyboard interaction. --- .../src/components/header/document-tools/index.js | 5 +++-- .../edit-widgets/src/components/header/style.scss | 3 +-- .../src/components/header/undo-redo/redo.js | 11 ++++++++--- .../src/components/header/undo-redo/undo.js | 11 ++++++++--- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/edit-widgets/src/components/header/document-tools/index.js b/packages/edit-widgets/src/components/header/document-tools/index.js index a9799ac993f9a..4391ece0b89e2 100644 --- a/packages/edit-widgets/src/components/header/document-tools/index.js +++ b/packages/edit-widgets/src/components/header/document-tools/index.js @@ -83,6 +83,7 @@ function DocumentTools() { className="edit-widgets-header-toolbar" aria-label={ __( 'Document tools' ) } shouldUseKeyboardFocusShortcut={ ! blockToolbarCanBeFocused } + variant="unstyled" > { isMediumViewport && ( <> - - + + ); } + +export default forwardRef( RedoButton ); diff --git a/packages/edit-widgets/src/components/header/undo-redo/undo.js b/packages/edit-widgets/src/components/header/undo-redo/undo.js index 827ed1a415d74..271c73a452d9e 100644 --- a/packages/edit-widgets/src/components/header/undo-redo/undo.js +++ b/packages/edit-widgets/src/components/header/undo-redo/undo.js @@ -2,20 +2,23 @@ * WordPress dependencies */ import { __, isRTL } from '@wordpress/i18n'; -import { ToolbarButton } from '@wordpress/components'; +import { Button } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { undo as undoIcon, redo as redoIcon } from '@wordpress/icons'; import { displayShortcut } from '@wordpress/keycodes'; import { store as coreStore } from '@wordpress/core-data'; +import { forwardRef } from '@wordpress/element'; -export default function UndoButton() { +function UndoButton( props, ref ) { const hasUndo = useSelect( ( select ) => select( coreStore ).hasUndo(), [] ); const { undo } = useDispatch( coreStore ); return ( - ); } + +export default forwardRef( UndoButton ); From cd548afcc23253e4be4b5ca382d3f4b19b9267f4 Mon Sep 17 00:00:00 2001 From: Mario Santos <34552881+SantosGuillamot@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:17:40 +0100 Subject: [PATCH 060/296] Block Bindings API: Add block bindings PHP registration mechanisms and "Post meta" source under the experimental flag (#57249) * Change block bindings experiment name * Remove old custom fields UI * Add block bindings PHP logic * Add pattern source * Add post meta source * Adapt partially synced patterns experiment * Add domain to the translations in sources * Remove unused post_meta attrs * Change variable name * Remove a couple of comments * Add image alt to permitted attributes * Rename variables and clean up code * Fix bug wherein original pattern values were not being displayed * fix typo * Format a comment * fix comment indentation * remove the .vscode/settings.json file that sneaked in * Sync with added support for multiple attributes in patterns * Remove block supports requirement for partial syncing * Remove __experimentalBlockBindings supports from paragraph * Simplify source callback and update comments * Remove erroneous comment * Revert "Remove block supports requirement for partial syncing" This reverts commit 7fb646cae16f1703cfdee25a7f8629e6b020fec3. * Revert "Remove __experimentalBlockBindings supports from paragraph" This reverts commit 34e3f29078e9f9196befda3ded01a200509266d5. * Remove block supports dependency * Update signature and docblock of register function * Empty-Commit * Update the PHPDoc string --------- Co-authored-by: Ricardo Artemio Morales Co-authored-by: Michal Czaplinski --- lib/block-supports/pattern.php | 2 +- .../block-bindings/html-processing.php | 110 ++++++++++++ lib/experimental/block-bindings/index.php | 20 +++ .../block-bindings/sources/index.php | 32 ++++ .../block-bindings/sources/pattern.php | 21 +++ .../block-bindings/sources/post-meta.php | 25 +++ lib/experimental/blocks.php | 170 ++++++++---------- lib/experimental/connection-sources/index.php | 23 --- lib/experimental/editor-settings.php | 4 +- lib/experiments-page.php | 6 +- .../block-editor/src/hooks/custom-fields.js | 115 ------------ packages/block-editor/src/hooks/index.js | 2 - packages/block-library/src/block/edit.js | 14 +- .../block-library/src/paragraph/block.json | 1 - .../src/hooks/pattern-partial-syncing.js | 7 - .../components/partial-syncing-controls.js | 44 +++-- 16 files changed, 317 insertions(+), 279 deletions(-) create mode 100644 lib/experimental/block-bindings/html-processing.php create mode 100644 lib/experimental/block-bindings/index.php create mode 100644 lib/experimental/block-bindings/sources/index.php create mode 100644 lib/experimental/block-bindings/sources/pattern.php create mode 100644 lib/experimental/block-bindings/sources/post-meta.php delete mode 100644 lib/experimental/connection-sources/index.php delete mode 100644 packages/block-editor/src/hooks/custom-fields.js diff --git a/lib/block-supports/pattern.php b/lib/block-supports/pattern.php index a783135c793e3..2f7717091ff50 100644 --- a/lib/block-supports/pattern.php +++ b/lib/block-supports/pattern.php @@ -13,7 +13,7 @@ * @param WP_Block_Type $block_type Block Type. */ function gutenberg_register_pattern_support( $block_type ) { - $pattern_support = property_exists( $block_type, 'supports' ) ? _wp_array_get( $block_type->supports, array( '__experimentalConnections' ), false ) : false; + $pattern_support = 'core/paragraph' === $block_type->name ? true : false; if ( $pattern_support ) { if ( ! $block_type->uses_context ) { diff --git a/lib/experimental/block-bindings/html-processing.php b/lib/experimental/block-bindings/html-processing.php new file mode 100644 index 0000000000000..515749d0a8e75 --- /dev/null +++ b/lib/experimental/block-bindings/html-processing.php @@ -0,0 +1,110 @@ +get_registered( $block_name ); + if ( null === $block_type ) { + return; + } + + // Depending on the attribute source, the processing will be different. + switch ( $block_type->attributes[ $block_attr ]['source'] ) { + case 'html': + case 'rich-text': + $block_reader = new WP_HTML_Tag_Processor( $block_content ); + + // TODO: Support for CSS selectors whenever they are ready in the HTML API. + // In the meantime, support comma-separated selectors by exploding them into an array. + $selectors = explode( ',', $block_type->attributes[ $block_attr ]['selector'] ); + // Add a bookmark to the first tag to be able to iterate over the selectors. + $block_reader->next_tag(); + $block_reader->set_bookmark( 'iterate-selectors' ); + + // TODO: This shouldn't be needed when the `set_inner_html` function is ready. + // Store the parent tag and its attributes to be able to restore them later in the button. + // The button block has a wrapper while the paragraph and heading blocks don't. + if ( 'core/button' === $block_name ) { + $button_wrapper = $block_reader->get_tag(); + $button_wrapper_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); + $button_wrapper_attrs = array(); + foreach ( $button_wrapper_attribute_names as $name ) { + $button_wrapper_attrs[ $name ] = $block_reader->get_attribute( $name ); + } + } + + foreach ( $selectors as $selector ) { + // If the parent tag, or any of its children, matches the selector, replace the HTML. + if ( strcasecmp( $block_reader->get_tag( $selector ), $selector ) === 0 || $block_reader->next_tag( + array( + 'tag_name' => $selector, + ) + ) ) { + $block_reader->release_bookmark( 'iterate-selectors' ); + + // TODO: Use `set_inner_html` method whenever it's ready in the HTML API. + // Until then, it is hardcoded for the paragraph, heading, and button blocks. + // Store the tag and its attributes to be able to restore them later. + $selector_attribute_names = $block_reader->get_attribute_names_with_prefix( '' ); + $selector_attrs = array(); + foreach ( $selector_attribute_names as $name ) { + $selector_attrs[ $name ] = $block_reader->get_attribute( $name ); + } + $selector_markup = "<$selector>" . esc_html( $source_value ) . ""; + $amended_content = new WP_HTML_Tag_Processor( $selector_markup ); + $amended_content->next_tag(); + foreach ( $selector_attrs as $attribute_key => $attribute_value ) { + $amended_content->set_attribute( $attribute_key, $attribute_value ); + } + if ( 'core/paragraph' === $block_name || 'core/heading' === $block_name ) { + return $amended_content->get_updated_html(); + } + if ( 'core/button' === $block_name ) { + $button_markup = "<$button_wrapper>{$amended_content->get_updated_html()}"; + $amended_button = new WP_HTML_Tag_Processor( $button_markup ); + $amended_button->next_tag(); + foreach ( $button_wrapper_attrs as $attribute_key => $attribute_value ) { + $amended_button->set_attribute( $attribute_key, $attribute_value ); + } + return $amended_button->get_updated_html(); + } + } else { + $block_reader->seek( 'iterate-selectors' ); + } + } + $block_reader->release_bookmark( 'iterate-selectors' ); + return $block_content; + + case 'attribute': + $amended_content = new WP_HTML_Tag_Processor( $block_content ); + if ( ! $amended_content->next_tag( + array( + // TODO: build the query from CSS selector. + 'tag_name' => $block_type->attributes[ $block_attr ]['selector'], + ) + ) ) { + return $block_content; + } + $amended_content->set_attribute( $block_type->attributes[ $block_attr ]['attribute'], esc_attr( $source_value ) ); + return $amended_content->get_updated_html(); + break; + + default: + return $block_content; + break; + } + return; + } +} diff --git a/lib/experimental/block-bindings/index.php b/lib/experimental/block-bindings/index.php new file mode 100644 index 0000000000000..cca857e93702f --- /dev/null +++ b/lib/experimental/block-bindings/index.php @@ -0,0 +1,20 @@ + $label, + 'apply' => $apply, + ); + } +} diff --git a/lib/experimental/block-bindings/sources/pattern.php b/lib/experimental/block-bindings/sources/pattern.php new file mode 100644 index 0000000000000..e3456aa468d3e --- /dev/null +++ b/lib/experimental/block-bindings/sources/pattern.php @@ -0,0 +1,21 @@ +attributes, array( 'metadata', 'id' ), false ) ) { + return null; + } + $block_id = $block_instance->attributes['metadata']['id']; + return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); + }; + register_block_bindings_source( + 'pattern_attributes', + __( 'Pattern Attributes', 'gutenberg' ), + $pattern_source_callback + ); +} diff --git a/lib/experimental/block-bindings/sources/post-meta.php b/lib/experimental/block-bindings/sources/post-meta.php new file mode 100644 index 0000000000000..99b6afc03c0d4 --- /dev/null +++ b/lib/experimental/block-bindings/sources/post-meta.php @@ -0,0 +1,25 @@ +context['postId'] but it wasn't available in the image block. + $post_id = get_the_ID(); + } + + return get_post_meta( $post_id, $source_attrs['value'], true ); + }; + register_block_bindings_source( + 'post_meta', + __( 'Post Meta', 'gutenberg' ), + $post_meta_source_callback + ); +} diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index d4bb6c9b4586e..42663e127870c 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -83,118 +83,90 @@ function wp_enqueue_block_view_script( $block_name, $args ) { $gutenberg_experiments = get_option( 'gutenberg-experiments' ); if ( $gutenberg_experiments && ( - array_key_exists( 'gutenberg-connections', $gutenberg_experiments ) || + array_key_exists( 'gutenberg-block-bindings', $gutenberg_experiments ) || array_key_exists( 'gutenberg-pattern-partial-syncing', $gutenberg_experiments ) ) ) { - /** - * Renders the block meta attributes. - * - * @param string $block_content Block Content. - * @param array $block Block attributes. - * @param WP_Block $block_instance The block instance. - */ - function gutenberg_render_block_connections( $block_content, $block, $block_instance ) { - $connection_sources = require __DIR__ . '/connection-sources/index.php'; - $block_type = $block_instance->block_type; - - // Allowlist of blocks that support block connections. - // Currently, we only allow the following blocks and attributes: - // - Paragraph: content. - // - Image: url. - $blocks_attributes_allowlist = array( - 'core/paragraph' => array( 'content' ), - 'core/image' => array( 'url' ), - ); - - // Whitelist of the block types that support block connections. - // Currently, we only allow the Paragraph and Image blocks to use block connections. - if ( ! in_array( $block['blockName'], array_keys( $blocks_attributes_allowlist ), true ) ) { - return $block_content; - } - - // If for some reason, the block type is not found, skip it. - if ( null === $block_type ) { - return $block_content; - } - - // If the block does not have support for block connections, skip it. - if ( ! block_has_support( $block_type, array( '__experimentalConnections' ), false ) ) { - return $block_content; - } - - // Get all the attributes that have a connection. - $connected_attributes = $block['attrs']['connections']['attributes'] ?? false; - if ( ! $connected_attributes ) { - return $block_content; - } - - foreach ( $connected_attributes as $attribute_name => $attribute_value ) { - - // If the attribute is not in the allowlist, skip it. - if ( ! in_array( $attribute_name, $blocks_attributes_allowlist[ $block['blockName'] ], true ) ) { - continue; - } - - // Skip if the source value is not "meta_fields" or "pattern_attributes". - if ( 'meta_fields' !== $attribute_value['source'] && 'pattern_attributes' !== $attribute_value['source'] ) { - continue; - } - // If the attribute does not have a source, skip it. - if ( ! isset( $block_type->attributes[ $attribute_name ]['source'] ) ) { - continue; + require_once __DIR__ . '/block-bindings/index.php'; + // Allowed blocks that support block bindings. + // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? + global $block_bindings_allowed_blocks; + $block_bindings_allowed_blocks = array( + 'core/paragraph' => array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text' ), + ); + if ( ! function_exists( 'process_block_bindings' ) ) { + /** + * Process the block bindings attribute. + * + * @param string $block_content Block Content. + * @param array $block Block attributes. + * @param WP_Block $block_instance The block instance. + */ + function process_block_bindings( $block_content, $block, $block_instance ) { + // If the block doesn't have the bindings property, return. + if ( ! isset( $block['attrs']['metadata']['bindings'] ) ) { + return $block_content; } - if ( 'pattern_attributes' === $attribute_value['source'] ) { - if ( ! _wp_array_get( $block_instance->attributes, array( 'metadata', 'id' ), false ) ) { + // Assuming the following format for the bindings property of the "metadata" attribute: + // + // "bindings": { + // "title": { + // "source": { + // "name": "post_meta", + // "attributes": { "value": "text_custom_field" } + // } + // }, + // "url": { + // "source": { + // "name": "post_meta", + // "attributes": { "value": "text_custom_field" } + // } + // } + // } + // + global $block_bindings_allowed_blocks; + global $block_bindings_sources; + $modified_block_content = $block_content; + foreach ( $block['attrs']['metadata']['bindings'] as $binding_attribute => $binding_source ) { + // If the block is not in the list, stop processing. + if ( ! isset( $block_bindings_allowed_blocks[ $block['blockName'] ] ) ) { + return $block_content; + } + // If the attribute is not in the list, process next attribute. + if ( ! in_array( $binding_attribute, $block_bindings_allowed_blocks[ $block['blockName'] ], true ) ) { continue; } - - $custom_value = $connection_sources[ $attribute_value['source'] ]( $block_instance, $attribute_name ); - } else { - // If the attribute does not specify the name of the custom field, skip it. - if ( ! isset( $attribute_value['value'] ) ) { + // If no source is provided, or that source is not registered, process next attribute. + if ( ! isset( $binding_source['source'] ) || ! isset( $binding_source['source']['name'] ) || ! isset( $block_bindings_sources[ $binding_source['source']['name'] ] ) ) { continue; } - // Get the content from the connection source. - $custom_value = $connection_sources[ $attribute_value['source'] ]( - $block_instance, - $attribute_value['value'] - ); - } - - if ( false === $custom_value ) { - continue; - } + $source_callback = $block_bindings_sources[ $binding_source['source']['name'] ]['apply']; + // Get the value based on the source. + if ( ! isset( $binding_source['source']['attributes'] ) ) { + $source_args = array(); + } else { + $source_args = $binding_source['source']['attributes']; + } + $source_value = $source_callback( $source_args, $block_instance, $binding_attribute ); + // If the value is null, process next attribute. + if ( is_null( $source_value ) ) { + continue; + } - $tags = new WP_HTML_Tag_Processor( $block_content ); - $found = $tags->next_tag( - array( - // TODO: In the future, when blocks other than Paragraph and Image are - // supported, we should build the full query from CSS selector. - 'tag_name' => $block_type->attributes[ $attribute_name ]['selector'], - ) - ); - if ( ! $found ) { - return $block_content; + // Process the HTML based on the block and the attribute. + $modified_block_content = block_bindings_replace_html( $modified_block_content, $block['blockName'], $binding_attribute, $source_value ); } - $tag_name = $tags->get_tag(); - $markup = "<$tag_name>$custom_value"; - $updated_tags = new WP_HTML_Tag_Processor( $markup ); - $updated_tags->next_tag(); - - // Get all the attributes from the original block and add them to the new markup. - $names = $tags->get_attribute_names_with_prefix( '' ); - foreach ( $names as $name ) { - $updated_tags->set_attribute( $name, $tags->get_attribute( $name ) ); - } - - return $updated_tags->get_updated_html(); + return $modified_block_content; } - return $block_content; + // Add filter only to the blocks in the list. + foreach ( $block_bindings_allowed_blocks as $block_name => $attributes ) { + add_filter( 'render_block_' . $block_name, 'process_block_bindings', 20, 3 ); + } } - - add_filter( 'render_block', 'gutenberg_render_block_connections', 10, 3 ); } diff --git a/lib/experimental/connection-sources/index.php b/lib/experimental/connection-sources/index.php deleted file mode 100644 index 4f9e06cb13b94..0000000000000 --- a/lib/experimental/connection-sources/index.php +++ /dev/null @@ -1,23 +0,0 @@ - 'meta', - 'meta_fields' => function ( $block_instance, $meta_field ) { - // We should probably also check if the meta field exists but for now it's okay because - // if it doesn't, `get_post_meta()` will just return an empty string. - return get_post_meta( $block_instance->context['postId'], $meta_field, true ); - }, - 'pattern_attributes' => function ( $block_instance, $attribute_name ) { - $block_id = $block_instance->attributes['metadata']['id']; - return _wp_array_get( - $block_instance->context, - array( 'pattern/overrides', $block_id, $attribute_name ), - false - ); - }, -); diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index 5f61684e8b134..729376cf030dd 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -26,8 +26,8 @@ function gutenberg_enable_experiments() { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableGroupGridVariation = true', 'before' ); } - if ( $gutenberg_experiments && array_key_exists( 'gutenberg-connections', $gutenberg_experiments ) ) { - wp_add_inline_script( 'wp-block-editor', 'window.__experimentalConnections = true', 'before' ); + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-block-bindings', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalBlockBindings = true', 'before' ); } if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) { diff --git a/lib/experiments-page.php b/lib/experiments-page.php index 2d2e76273d2d5..8af1eb82c6bed 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -128,13 +128,13 @@ function gutenberg_initialize_experiments_settings() { add_settings_field( 'gutenberg-custom-fields', - __( 'Connections', 'gutenberg' ), + __( 'Block Bindings & Custom Fields', 'gutenberg' ), 'gutenberg_display_experiment_field', 'gutenberg-experiments', 'gutenberg_experiments_section', array( - 'label' => __( 'Test connecting block attribute values to a custom field value', 'gutenberg' ), - 'id' => 'gutenberg-connections', + 'label' => __( 'Test connecting block attributes to different sources like custom fields', 'gutenberg' ), + 'id' => 'gutenberg-block-bindings', ) ); diff --git a/packages/block-editor/src/hooks/custom-fields.js b/packages/block-editor/src/hooks/custom-fields.js deleted file mode 100644 index 9b677933adc13..0000000000000 --- a/packages/block-editor/src/hooks/custom-fields.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * WordPress dependencies - */ -import { addFilter } from '@wordpress/hooks'; -import { PanelBody, TextControl } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; -import { hasBlockSupport } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import { InspectorControls } from '../components'; -import { useBlockEditingMode } from '../components/block-editing-mode'; - -/** - * Filters registered block settings, extending attributes to include `connections`. - * - * @param {Object} settings Original block settings. - * - * @return {Object} Filtered block settings. - */ -function addAttribute( settings ) { - if ( hasBlockSupport( settings, '__experimentalConnections', true ) ) { - // Gracefully handle if settings.attributes.connections is undefined. - settings.attributes = { - ...settings.attributes, - connections: { - type: 'object', - }, - }; - } - - return settings; -} - -function CustomFieldsControlPure( { name, connections, setAttributes } ) { - const blockEditingMode = useBlockEditingMode(); - if ( blockEditingMode !== 'default' ) { - return null; - } - - // If the block is a paragraph or image block, we need to know which - // attribute to use for the connection. Only the `content` attribute - // of the paragraph block and the `url` attribute of the image block are supported. - let attributeName; - if ( name === 'core/paragraph' ) attributeName = 'content'; - if ( name === 'core/image' ) attributeName = 'url'; - - return ( - - - { - if ( nextValue === '' ) { - setAttributes( { - connections: undefined, - [ attributeName ]: undefined, - placeholder: undefined, - } ); - } else { - setAttributes( { - connections: { - attributes: { - // The attributeName will be either `content` or `url`. - [ attributeName ]: { - // Source will be variable, could be post_meta, user_meta, term_meta, etc. - // Could even be a custom source like a social media attribute. - source: 'meta_fields', - value: nextValue, - }, - }, - }, - [ attributeName ]: undefined, - placeholder: sprintf( - 'This content will be replaced on the frontend by the value of "%s" custom field.', - nextValue - ), - } ); - } - } } - /> - - - ); -} - -export default { - edit: CustomFieldsControlPure, - attributeKeys: [ 'connections' ], - hasSupport( name ) { - return ( - hasBlockSupport( name, '__experimentalConnections', false ) && - // Check if the current block is a paragraph or image block. - // Currently, only these two blocks are supported. - [ 'core/paragraph', 'core/image' ].includes( name ) - ); - }, -}; - -if ( - window.__experimentalConnections || - window.__experimentalPatternPartialSyncing -) { - addFilter( - 'blocks.registerBlockType', - 'core/editor/connections/attribute', - addAttribute - ); -} diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 385b9fe6b1511..f17c0a22166e4 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -25,7 +25,6 @@ import layout from './layout'; import childLayout from './layout-child'; import contentLockUI from './content-lock-ui'; import './metadata'; -import customFields from './custom-fields'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; @@ -39,7 +38,6 @@ createBlockEditFilter( position, layout, contentLockUI, - window.__experimentalConnections ? customFields : null, blockHooks, blockRenaming, ].filter( Boolean ) diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 57db2d166f9f9..6331d33c27a7b 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -27,7 +27,7 @@ import { store as blockEditorStore, BlockControls, } from '@wordpress/block-editor'; -import { getBlockSupport, parse, cloneBlock } from '@wordpress/blocks'; +import { parse, cloneBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -38,17 +38,17 @@ const { useLayoutClasses } = unlock( blockEditorPrivateApis ); function isPartiallySynced( block ) { return ( - !! getBlockSupport( block.name, '__experimentalConnections', false ) && - !! block.attributes.connections?.attributes && - Object.values( block.attributes.connections.attributes ).some( - ( connection ) => connection.source === 'pattern_attributes' + 'core/paragraph' === block.name && + !! block.attributes.metadata?.bindings && + Object.values( block.attributes.metadata.bindings ).some( + ( binding ) => binding.source.name === 'pattern_attributes' ) ); } function getPartiallySyncedAttributes( block ) { - return Object.entries( block.attributes.connections.attributes ) + return Object.entries( block.attributes.metadata.bindings ) .filter( - ( [ , connection ] ) => connection.source === 'pattern_attributes' + ( [ , binding ] ) => binding.source.name === 'pattern_attributes' ) .map( ( [ attributeKey ] ) => attributeKey ); } diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index 3fe4fbb34e102..a81d754d8ca1b 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -41,7 +41,6 @@ "text": true } }, - "__experimentalConnections": true, "spacing": { "margin": true, "padding": true, diff --git a/packages/editor/src/hooks/pattern-partial-syncing.js b/packages/editor/src/hooks/pattern-partial-syncing.js index 40bd1e16dfc00..a940890dfa693 100644 --- a/packages/editor/src/hooks/pattern-partial-syncing.js +++ b/packages/editor/src/hooks/pattern-partial-syncing.js @@ -5,7 +5,6 @@ import { addFilter } from '@wordpress/hooks'; import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useBlockEditingMode } from '@wordpress/block-editor'; -import { hasBlockSupport } from '@wordpress/blocks'; import { useSelect } from '@wordpress/data'; /** @@ -32,11 +31,6 @@ const { const withPartialSyncingControls = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { const blockEditingMode = useBlockEditingMode(); - const hasCustomFieldsSupport = hasBlockSupport( - props.name, - '__experimentalConnections', - false - ); const isEditingPattern = useSelect( ( select ) => select( editorStore ).getCurrentPostType() === @@ -45,7 +39,6 @@ const withPartialSyncingControls = createHigherOrderComponent( ); const shouldShowPartialSyncingControls = - hasCustomFieldsSupport && props.isSelected && isEditingPattern && blockEditingMode === 'default' && diff --git a/packages/patterns/src/components/partial-syncing-controls.js b/packages/patterns/src/components/partial-syncing-controls.js index d20bd1d347012..f5ac19bc05f3d 100644 --- a/packages/patterns/src/components/partial-syncing-controls.js +++ b/packages/patterns/src/components/partial-syncing-controls.js @@ -19,7 +19,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { const syncedAttributes = PARTIAL_SYNCING_SUPPORTED_BLOCKS[ name ]; const attributeSources = Object.keys( syncedAttributes ).map( ( attributeName ) => - attributes.connections?.attributes?.[ attributeName ]?.source + attributes.metadata?.bindings?.[ attributeName ]?.source?.name ); const isConnectedToOtherSources = attributeSources.every( ( source ) => source && source !== 'pattern_attributes' @@ -30,52 +30,58 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { return null; } - function updateConnections( isChecked ) { - let updatedConnections = { - ...attributes.connections, - attributes: { ...attributes.connections?.attributes }, + function updateBindings( isChecked ) { + let updatedBindings = { + ...attributes?.metadata?.bindings, }; if ( ! isChecked ) { for ( const attributeName of Object.keys( syncedAttributes ) ) { if ( - updatedConnections.attributes[ attributeName ]?.source === + updatedBindings[ attributeName ]?.source?.name === 'pattern_attributes' ) { - delete updatedConnections.attributes[ attributeName ]; + delete updatedBindings[ attributeName ]; } } - if ( ! Object.keys( updatedConnections.attributes ).length ) { - delete updatedConnections.attributes; - } - if ( ! Object.keys( updatedConnections ).length ) { - updatedConnections = undefined; + if ( ! Object.keys( updatedBindings ).length ) { + updatedBindings = undefined; } setAttributes( { - connections: updatedConnections, + metadata: { + ...attributes.metadata, + bindings: updatedBindings, + }, } ); return; } for ( const attributeName of Object.keys( syncedAttributes ) ) { - if ( ! updatedConnections.attributes[ attributeName ] ) { - updatedConnections.attributes[ attributeName ] = { - source: 'pattern_attributes', + if ( ! updatedBindings[ attributeName ] ) { + updatedBindings[ attributeName ] = { + source: { + name: 'pattern_attributes', + }, }; } } if ( typeof attributes.metadata?.id === 'string' ) { - setAttributes( { connections: updatedConnections } ); + setAttributes( { + metadata: { + ...attributes.metadata, + bindings: updatedBindings, + }, + } ); return; } const id = nanoid( 6 ); setAttributes( { - connections: updatedConnections, metadata: { ...attributes.metadata, id, + bindings: updatedBindings, }, } ); } @@ -93,7 +99,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { ( source ) => source === 'pattern_attributes' ) } onChange={ ( isChecked ) => { - updateConnections( isChecked ); + updateBindings( isChecked ); } } /> From ea90b753197c6cc39a400b31bd68e5b4d648ccd4 Mon Sep 17 00:00:00 2001 From: Takashi Kitajima Date: Fri, 12 Jan 2024 01:12:27 +0900 Subject: [PATCH 061/296] Display guidance text when there is no font (#56825) * #56734 When there is no font, the border should not appear. Display further guidance text. * update text * Fix formatting in font-families.js * Improve readability --------- Co-authored-by: Matias Benedetto Co-authored-by: Sarah Norris --- .../components/global-styles/font-families.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/edit-site/src/components/global-styles/font-families.js b/packages/edit-site/src/components/global-styles/font-families.js index 06bf595328332..7a7597878eacd 100644 --- a/packages/edit-site/src/components/global-styles/font-families.js +++ b/packages/edit-site/src/components/global-styles/font-families.js @@ -26,6 +26,8 @@ function FontFamilies() { const { modalTabOpen, toggleModal, themeFonts, customFonts } = useContext( FontLibraryContext ); + const hasFonts = 0 < customFonts.length || 0 < themeFonts.length; + return ( <> { !! modalTabOpen && ( @@ -51,14 +53,18 @@ function FontFamilies() { - - { customFonts.map( ( font ) => ( - - ) ) } - { themeFonts.map( ( font ) => ( - - ) ) } - + { hasFonts ? ( + + { customFonts.map( ( font ) => ( + + ) ) } + { themeFonts.map( ( font ) => ( + + ) ) } + + ) : ( + <>{ __( 'No fonts installed.' ) } + ) }
); From 4f97a3fcebffb0eb5ce18ae3bc0182a66f0e03fa Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Thu, 11 Jan 2024 17:48:41 +0100 Subject: [PATCH 062/296] ColorPicker: store internal HSLA state for better slider UX (#57555) * ColorPicker: store internal HSLA state for better slider UX * CHANGELOG * Comments * Memoize `colorPropHSL`, rename to `colorPropHSLA` * Add unit test * Set internal SLA only when not calling onChange to reduce visual glitches * More test --- packages/components/CHANGELOG.md | 1 + .../components/src/color-picker/hsl-input.tsx | 86 +++++--- .../src/color-picker/test/index.tsx | 206 ++++++++++++++++-- 3 files changed, 247 insertions(+), 46 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index a8dd57900cfae..5856259e47730 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -23,6 +23,7 @@ ### Enhancements +- `ColorPicker`: improve the UX around HSL sliders ([#57555](https://github.com/WordPress/gutenberg/pull/57555)). - Update `ariakit` to version `0.3.10` ([#57325](https://github.com/WordPress/gutenberg/pull/57325)). - Update `@ariakit/react` to version `0.3.12` and @ariakit/test to version `0.3.7` ([#57547](https://github.com/WordPress/gutenberg/pull/57547)). - `DropdownMenuV2`: do not collapse suffix width ([#57238](https://github.com/WordPress/gutenberg/pull/57238)). diff --git a/packages/components/src/color-picker/hsl-input.tsx b/packages/components/src/color-picker/hsl-input.tsx index 3331a97b3d4de..8d2b0c7c44489 100644 --- a/packages/components/src/color-picker/hsl-input.tsx +++ b/packages/components/src/color-picker/hsl-input.tsx @@ -3,6 +3,11 @@ */ import { colord } from 'colord'; +/** + * WordPress dependencies + */ +import { useState, useEffect, useMemo } from '@wordpress/element'; + /** * Internal dependencies */ @@ -10,7 +15,49 @@ import { InputWithSlider } from './input-with-slider'; import type { HslInputProps } from './types'; export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => { - const { h, s, l, a } = color.toHsl(); + const colorPropHSLA = useMemo( () => color.toHsl(), [ color ] ); + + const [ internalHSLA, setInternalHSLA ] = useState( { ...colorPropHSLA } ); + + const isInternalColorSameAsReceivedColor = color.isEqual( + colord( internalHSLA ) + ); + + useEffect( () => { + if ( ! isInternalColorSameAsReceivedColor ) { + // Keep internal HSLA color up to date with the received color prop + setInternalHSLA( colorPropHSLA ); + } + }, [ colorPropHSLA, isInternalColorSameAsReceivedColor ] ); + + // If the internal color is equal to the received color prop, we can use the + // HSLA values from the local state which, compared to the received color prop, + // retain more details about the actual H and S values that the user selected, + // and thus allow for better UX when interacting with the H and S sliders. + const colorValue = isInternalColorSameAsReceivedColor + ? internalHSLA + : colorPropHSLA; + + const updateHSLAValue = ( + partialNewValue: Partial< typeof colorPropHSLA > + ) => { + const nextOnChangeValue = colord( { + ...colorValue, + ...partialNewValue, + } ); + + // Fire `onChange` only if the resulting color is different from the + // current one. + // Otherwise, update the internal HSLA color to cause a re-render. + if ( ! color.isEqual( nextOnChangeValue ) ) { + onChange( nextOnChangeValue ); + } else { + setInternalHSLA( ( prevHSLA ) => ( { + ...prevHSLA, + ...partialNewValue, + } ) ); + } + }; return ( <> @@ -19,9 +66,9 @@ export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => { max={ 359 } label="Hue" abbreviation="H" - value={ h } + value={ colorValue.h } onChange={ ( nextH: number ) => { - onChange( colord( { h: nextH, s, l, a } ) ); + updateHSLAValue( { h: nextH } ); } } /> { max={ 100 } label="Saturation" abbreviation="S" - value={ s } + value={ colorValue.s } onChange={ ( nextS: number ) => { - onChange( - colord( { - h, - s: nextS, - l, - a, - } ) - ); + updateHSLAValue( { s: nextS } ); } } /> { max={ 100 } label="Lightness" abbreviation="L" - value={ l } + value={ colorValue.l } onChange={ ( nextL: number ) => { - onChange( - colord( { - h, - s, - l: nextL, - a, - } ) - ); + updateHSLAValue( { l: nextL } ); } } /> { enableAlpha && ( @@ -64,16 +97,9 @@ export const HslInput = ( { color, onChange, enableAlpha }: HslInputProps ) => { max={ 100 } label="Alpha" abbreviation="A" - value={ Math.trunc( 100 * a ) } + value={ Math.trunc( 100 * colorValue.a ) } onChange={ ( nextA: number ) => { - onChange( - colord( { - h, - s, - l, - a: nextA / 100, - } ) - ); + updateHSLAValue( { a: nextA / 100 } ); } } /> ) } diff --git a/packages/components/src/color-picker/test/index.tsx b/packages/components/src/color-picker/test/index.tsx index 8d584d626487a..98e059d5994de 100644 --- a/packages/components/src/color-picker/test/index.tsx +++ b/packages/components/src/color-picker/test/index.tsx @@ -1,13 +1,19 @@ /** * External dependencies */ -import { screen, render } from '@testing-library/react'; +import { fireEvent, screen, render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; + /** * Internal dependencies */ import { ColorPicker } from '..'; +import { click } from '@ariakit/test'; const hslaMatcher = expect.objectContaining( { h: expect.any( Number ), @@ -133,20 +139,39 @@ describe( 'ColorPicker', () => { } ); } ); - describe.each( [ - [ 'hue', 'Hue', '#aad52a' ], - [ 'saturation', 'Saturation', '#20dfdf' ], - [ 'lightness', 'Lightness', '#95eaea' ], - ] )( 'HSL inputs', ( colorInput, inputLabel, expected ) => { - it( `should fire onChange with the correct value when the ${ colorInput } value is updated`, async () => { + describe( 'HSL inputs', () => { + it( 'sliders should use accurate H and S values based on user interaction when possible', async () => { const user = userEvent.setup(); const onChange = jest.fn(); - const color = '#2ad5d5'; + + const ControlledColorPicker = ( { + onChange: onChangeProp, + ...restProps + }: React.ComponentProps< typeof ColorPicker > ) => { + const [ colorState, setColorState ] = useState( '#000000' ); + + const internalOnChange: typeof onChangeProp = ( newColor ) => { + onChangeProp?.( newColor ); + setColorState( newColor ); + }; + + return ( + <> + + + + ); + }; render( - ); @@ -156,16 +181,165 @@ describe( 'ColorPicker', () => { await user.selectOptions( formatSelector, 'hsl' ); - const inputElement = screen.getByRole( 'spinbutton', { - name: inputLabel, + const hueSliders = screen.getAllByRole( 'slider', { + name: 'Hue', } ); - expect( inputElement ).toBeVisible(); + expect( hueSliders ).toHaveLength( 2 ); - await user.clear( inputElement ); - await user.type( inputElement, '75' ); + // Reason for the `!` post-fix expression operator: if the check above + // doesn't fail, we're guaranteed that `hueSlider` is not undefined. + const hueSlider = hueSliders.at( -1 )!; + const saturationSlider = screen.getByRole( 'slider', { + name: 'Saturation', + } ); + const lightnessSlider = screen.getByRole( 'slider', { + name: 'Lightness', + } ); + const hueNumberInput = screen.getByRole( 'spinbutton', { + name: 'Hue', + } ); + const saturationNumberInput = screen.getByRole( 'spinbutton', { + name: 'Saturation', + } ); + const lightnessNumberInput = screen.getByRole( 'spinbutton', { + name: 'Lightness', + } ); + + // All initial inputs should have a value of `0` since the color is black. + expect( hueSlider ).toHaveValue( '0' ); + expect( saturationSlider ).toHaveValue( '0' ); + expect( lightnessSlider ).toHaveValue( '0' ); + expect( hueNumberInput ).toHaveValue( 0 ); + expect( saturationNumberInput ).toHaveValue( 0 ); + expect( lightnessNumberInput ).toHaveValue( 0 ); + + // Interact with the Hue slider, it should change its value (and the + // value of the associated number input), but it shouldn't cause the + // `onChange` callback to fire, since the resulting color is still black. + fireEvent.change( hueSlider, { target: { value: 80 } } ); + + expect( hueSlider ).toHaveValue( '80' ); + expect( hueNumberInput ).toHaveValue( 80 ); + expect( onChange ).not.toHaveBeenCalled(); + + // Interact with the Saturation slider, it should change its value (and the + // value of the associated number input), but it shouldn't cause the + // `onChange` callback to fire, since the resulting color is still black. + fireEvent.change( saturationSlider, { target: { value: 50 } } ); + + expect( saturationSlider ).toHaveValue( '50' ); + expect( saturationNumberInput ).toHaveValue( 50 ); + expect( onChange ).not.toHaveBeenCalled(); + + // Interact with the Lightness slider, it should change its value (and the + // value of the associated number input). It should also cause the + // `onChange` callback to fire, since changing the lightness actually + // causes the color to change. + fireEvent.change( lightnessSlider, { target: { value: 30 } } ); + await waitFor( () => + expect( lightnessSlider ).toHaveValue( '30' ) + ); + expect( lightnessNumberInput ).toHaveValue( 30 ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); + expect( onChange ).toHaveBeenLastCalledWith( '#597326' ); + + // Interact with the Lightness slider, setting to 100 (ie. white). + // It should also cause the `onChange` callback to fire, and reset the + // hue and saturation inputs to `0`. + fireEvent.change( lightnessSlider, { target: { value: 100 } } ); + + await waitFor( () => + expect( lightnessSlider ).toHaveValue( '100' ) + ); + expect( lightnessNumberInput ).toHaveValue( 100 ); + expect( hueSlider ).toHaveValue( '0' ); + expect( saturationSlider ).toHaveValue( '0' ); + expect( hueNumberInput ).toHaveValue( 0 ); + expect( saturationNumberInput ).toHaveValue( 0 ); + expect( onChange ).toHaveBeenCalledTimes( 2 ); + expect( onChange ).toHaveBeenLastCalledWith( '#ffffff' ); + + // Interact with the Hue slider, it should change its value (and the + // value of the associated number input), but it shouldn't cause the + // `onChange` callback to fire, since the resulting color is still white. + fireEvent.change( hueSlider, { target: { value: 147 } } ); + + expect( hueSlider ).toHaveValue( '147' ); + expect( hueNumberInput ).toHaveValue( 147 ); + expect( onChange ).toHaveBeenCalledTimes( 2 ); + + // Interact with the Saturation slider, it should change its value (and the + // value of the associated number input), but it shouldn't cause the + // `onChange` callback to fire, since the resulting color is still white. + fireEvent.change( saturationSlider, { target: { value: 82 } } ); + + expect( saturationSlider ).toHaveValue( '82' ); + expect( saturationNumberInput ).toHaveValue( 82 ); + expect( onChange ).toHaveBeenCalledTimes( 2 ); + + // Interact with the Lightness slider, it should change its value (and the + // value of the associated number input). It should also cause the + // `onChange` callback to fire, since changing the lightness actually + // causes the color to change. + fireEvent.change( lightnessSlider, { target: { value: 14 } } ); + + await waitFor( () => + expect( lightnessSlider ).toHaveValue( '14' ) + ); + expect( lightnessNumberInput ).toHaveValue( 14 ); expect( onChange ).toHaveBeenCalledTimes( 3 ); - expect( onChange ).toHaveBeenLastCalledWith( expected ); + expect( onChange ).toHaveBeenLastCalledWith( '#064121' ); + + // Set the color externally. All inputs should update to match the H/S/L + // value of the new color. + const setColorButton = screen.getByRole( 'button', { + name: /set color/i, + } ); + await click( setColorButton ); + + expect( hueSlider ).toHaveValue( '208' ); + expect( hueNumberInput ).toHaveValue( 208 ); + expect( saturationSlider ).toHaveValue( '44' ); + expect( saturationNumberInput ).toHaveValue( 44 ); + expect( lightnessSlider ).toHaveValue( '52' ); + expect( lightnessNumberInput ).toHaveValue( 52 ); + } ); + + describe.each( [ + [ 'hue', 'Hue', '#aad52a' ], + [ 'saturation', 'Saturation', '#20dfdf' ], + [ 'lightness', 'Lightness', '#95eaea' ], + ] )( 'HSL inputs', ( colorInput, inputLabel, expected ) => { + it( `should fire onChange with the correct value when the ${ colorInput } value is updated`, async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + const color = '#2ad5d5'; + + render( + + ); + + const formatSelector = screen.getByRole( 'combobox' ); + expect( formatSelector ).toBeVisible(); + + await user.selectOptions( formatSelector, 'hsl' ); + + const inputElement = screen.getByRole( 'spinbutton', { + name: inputLabel, + } ); + expect( inputElement ).toBeVisible(); + + await user.clear( inputElement ); + await user.type( inputElement, '75' ); + + expect( onChange ).toHaveBeenCalledTimes( 3 ); + expect( onChange ).toHaveBeenLastCalledWith( expected ); + } ); } ); } ); } ); From 50ac86a8d530c8673e9ce5f3eeadbca437c40d98 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Thu, 11 Jan 2024 12:06:31 -0500 Subject: [PATCH 063/296] Download then upload font face assets when installing from a collection (#57694) * Download then upload font face assets when installing from a collection * Add error handling to font download-then-upload * Check for downloadFromUrl property before downloading to upload * Simplify downloadFontFaceAsset and move to utils. * Add error handling to downloadFontFaceAsset * Use Promise.all to handle multiple asynchronous font face downloads. > > Co-authored-by: Sarah Norris --------- Co-authored-by: Jeff Ong Co-authored-by: Sarah Norris --- .../font-library-modal/font-collection.js | 30 +++++++++++++++- .../font-library-modal/utils/index.js | 35 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js index fc39e2e009653..f7f33032f1e3f 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/font-collection.js @@ -31,6 +31,7 @@ import { toggleFont } from './utils/toggleFont'; import { getFontsOutline } from './utils/fonts-outline'; import GoogleFontsConfirmDialog from './google-fonts-confirm-dialog'; import { getNoticeFromInstallResponse } from './utils/get-notice-from-response'; +import { downloadFontFaceAsset } from './utils'; const DEFAULT_CATEGORY = { id: 'all', @@ -154,7 +155,34 @@ function FontCollection( { id } ) { }; const handleInstall = async () => { - const response = await installFont( fontsToInstall[ 0 ] ); + const fontFamily = fontsToInstall[ 0 ]; + + try { + if ( fontFamily?.fontFace ) { + await Promise.all( + fontFamily.fontFace.map( async ( fontFace ) => { + if ( fontFace.downloadFromUrl ) { + fontFace.file = await downloadFontFaceAsset( + fontFace.downloadFromUrl + ); + delete fontFace.downloadFromUrl; + } + } ) + ); + } + } catch ( error ) { + // If any of the fonts fail to download, + // show an error notice and stop the request from being sent. + setNotice( { + type: 'error', + message: __( + 'Error installing the fonts, could not be downloaded.' + ), + } ); + return; + } + + const response = await installFont( fontFamily ); const installNotice = getNoticeFromInstallResponse( response ); setNotice( installNotice ); resetFontsToInstall(); diff --git a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js index 2874dd446efb4..0aa0f7edb4aec 100644 --- a/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js +++ b/packages/edit-site/src/components/global-styles/font-library-modal/utils/index.js @@ -9,6 +9,11 @@ import { privateApis as componentsPrivateApis } from '@wordpress/components'; import { FONT_WEIGHTS, FONT_STYLES } from './constants'; import { unlock } from '../../../../lock-unlock'; +/** + * Browser dependencies + */ +const { File } = window; + export function setUIValuesNeeded( font, extraValues = {} ) { if ( ! font.name && ( font.fontFamily || font.slug ) ) { font.name = font.fontFamily || font.slug; @@ -164,3 +169,33 @@ export function makeFormDataFromFontFamily( fontFamily ) { formData.append( 'font_family_settings', JSON.stringify( newFontFamily ) ); return formData; } + +/* + * Downloads a font face asset from a URL to the client and returns a File object. + */ +export async function downloadFontFaceAsset( url ) { + return fetch( new Request( url ) ) + .then( ( response ) => { + if ( ! response.ok ) { + throw new Error( + `Error downloading font face asset from ${ url }. Server responded with status: ${ response.status }` + ); + } + return response.blob(); + } ) + .then( ( blob ) => { + const filename = url.split( '/' ).pop(); + const file = new File( [ blob ], filename, { + type: blob.type, + } ); + return file; + } ) + .catch( ( error ) => { + // eslint-disable-next-line no-console + console.error( + `Error downloading font face asset from ${ url }:`, + error + ); + throw error; + } ); +} From 7dd920589ab5abe5ec7a53e4b5c98a17add9cc4a Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 11 Jan 2024 17:13:07 +0000 Subject: [PATCH 064/296] Fix: Typos on __unstableSetTemporarilyEditingAsBlocks documentation. (#57768) --- packages/block-editor/src/store/actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index da9beb0ba73a9..4b396045a73c2 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1679,13 +1679,13 @@ export function setBlockVisibility( updates ) { } /** - * Action that sets whether a block is being temporaritly edited as blocks. + * Action that sets whether a block is being temporarily edited as blocks. * * DO-NOT-USE in production. * This action is created for internal/experimental only usage and may be * removed anytime without any warning, causing breakage on any plugin or theme invoking it. * - * @param {?string} temporarilyEditingAsBlocks The block's clientId being temporaritly edited as blocks. + * @param {?string} temporarilyEditingAsBlocks The block's clientId being temporarily edited as blocks. */ export function __unstableSetTemporarilyEditingAsBlocks( temporarilyEditingAsBlocks From 167c439e2f899337ee510d35b05b23b97b8aa091 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca <150562+mcsf@users.noreply.github.com> Date: Thu, 11 Jan 2024 17:19:15 +0000 Subject: [PATCH 065/296] Scripts: Remove unused variable in bin/list-experimental-api-matches.sh (#57771) --- bin/list-experimental-api-matches.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/list-experimental-api-matches.sh b/bin/list-experimental-api-matches.sh index 156464c4e7375..d9399e63e5cf6 100755 --- a/bin/list-experimental-api-matches.sh +++ b/bin/list-experimental-api-matches.sh @@ -31,7 +31,7 @@ namespace() { awk -F: ' { print module($1), $2 } function module(path) { - n = split(path, parts, "/") + split(path, parts, "/") if (parts[1] == "lib") return "lib" return parts[1] "/" parts[2] }' From f154dc7f2cdec6d6253132e388cf60a64692ee9a Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 11 Jan 2024 17:31:52 +0000 Subject: [PATCH 066/296] Add: Bulk actions to dataviews with the new design. (#57255) * Add: Bulk actions to dataviews with the new design. * post rebase fixes. * Add missing secondary variant * Move bulk actions button position * Don't render bulk actions functionality when there are no bulk actions avaliable. * Fix button padding * Fix focus loss * fix flex shring * Fix focus issues * remove data items meanwhile removed from the selection * remove line accidently added * Fix vertical alignment post rebase. * Fix unrequired decodeEntities usage. * Apply feedback to the promises * lint fix * Feedback application * Labels object instead of function * Introduce removeTemplates action * fix css rebase issue * lint fixes. * remove labels * simplify logic condition * fix some classnames * multiple small changes of feedback * remove getItemTitle * Inspect Promise.allSettled result * typo * case fixing * update patterns actions * Added a selection mark * lint fixes * Style adjustments --------- Co-authored-by: James Koster --- packages/dataviews/src/bulk-actions.js | 187 ++++++++++++++++++ packages/dataviews/src/dataviews.js | 31 ++- packages/dataviews/src/item-actions.js | 6 +- packages/dataviews/src/style.scss | 56 +++++- packages/dataviews/src/view-table.js | 146 +++++++++++++- .../edit-site/src/components/actions/index.js | 21 +- .../dataviews-pattern-actions.js | 17 +- .../src/components/page-templates/index.js | 6 +- .../page-templates/template-actions.js | 114 +++++++---- packages/edit-site/src/store/actions.js | 56 +----- .../edit-site/src/store/private-actions.js | 103 ++++++++++ 11 files changed, 628 insertions(+), 115 deletions(-) create mode 100644 packages/dataviews/src/bulk-actions.js diff --git a/packages/dataviews/src/bulk-actions.js b/packages/dataviews/src/bulk-actions.js new file mode 100644 index 0000000000000..9fd9f628286e0 --- /dev/null +++ b/packages/dataviews/src/bulk-actions.js @@ -0,0 +1,187 @@ +/** + * WordPress dependencies + */ +import { + privateApis as componentsPrivateApis, + Button, + Modal, +} from '@wordpress/components'; +import { __, sprintf, _n } from '@wordpress/i18n'; +import { useMemo, useState, useCallback } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { unlock } from './lock-unlock'; + +const { + DropdownMenuV2: DropdownMenu, + DropdownMenuGroupV2: DropdownMenuGroup, + DropdownMenuItemV2: DropdownMenuItem, + DropdownMenuSeparatorV2: DropdownMenuSeparator, +} = unlock( componentsPrivateApis ); + +function ActionWithModal( { + action, + selectedItems, + setActionWithModal, + onMenuOpenChange, +} ) { + const eligibleItems = useMemo( () => { + return selectedItems.filter( ( item ) => action.isEligible( item ) ); + }, [ action, selectedItems ] ); + const { RenderModal, hideModalHeader } = action; + const onCloseModal = useCallback( () => { + setActionWithModal( undefined ); + }, [ setActionWithModal ] ); + return ( + + onMenuOpenChange( false ) } + /> + + ); +} + +function BulkActionItem( { action, selectedItems, setActionWithModal } ) { + const eligibleItems = useMemo( () => { + return selectedItems.filter( ( item ) => action.isEligible( item ) ); + }, [ action, selectedItems ] ); + + const shouldShowModal = !! action.RenderModal; + + return ( + { + if ( shouldShowModal ) { + setActionWithModal( action ); + } else { + await action.callback( eligibleItems ); + } + } } + suffix={ + eligibleItems.length > 0 ? eligibleItems.length : undefined + } + > + { action.label } + + ); +} + +function ActionsMenuGroup( { actions, selectedItems, setActionWithModal } ) { + return ( + <> + + { actions.map( ( action ) => ( + + ) ) } + + + + ); +} + +export default function BulkActions( { + data, + actions, + selection, + onSelectionChange, + getItemId, +} ) { + const bulkActions = useMemo( + () => actions.filter( ( action ) => action.supportsBulk ), + [ actions ] + ); + const areAllSelected = selection && selection.length === data.length; + const [ isMenuOpen, onMenuOpenChange ] = useState( false ); + const [ actionWithModal, setActionWithModal ] = useState(); + const selectedItems = useMemo( () => { + return data.filter( ( item ) => + selection.includes( getItemId( item ) ) + ); + }, [ selection, data, getItemId ] ); + + if ( bulkActions.length === 0 ) { + return null; + } + return ( + <> + + { selection.length + ? sprintf( + /* translators: %d: Number of items. */ + _n( + 'Edit %d item', + 'Edit %d items', + selection.length + ), + selection.length + ) + : __( 'Bulk edit' ) } + + } + > + + + { + onSelectionChange( data ); + } } + suffix={ data.length } + > + { __( 'Select all' ) } + + { + onSelectionChange( [] ); + } } + > + { __( 'Deselect' ) } + + + + { actionWithModal && ( + + ) } + + ); +} diff --git a/packages/dataviews/src/dataviews.js b/packages/dataviews/src/dataviews.js index 61837e4f8fc96..64a70d46c7d12 100644 --- a/packages/dataviews/src/dataviews.js +++ b/packages/dataviews/src/dataviews.js @@ -5,7 +5,7 @@ import { __experimentalVStack as VStack, __experimentalHStack as HStack, } from '@wordpress/components'; -import { useMemo, useState, useCallback } from '@wordpress/element'; +import { useMemo, useState, useCallback, useEffect } from '@wordpress/element'; /** * Internal dependencies @@ -14,7 +14,8 @@ import Pagination from './pagination'; import ViewActions from './view-actions'; import Filters from './filters'; import Search from './search'; -import { VIEW_LAYOUTS } from './constants'; +import { VIEW_LAYOUTS, LAYOUT_TABLE } from './constants'; +import BulkActions from './bulk-actions'; const defaultGetItemId = ( item ) => item.id; const defaultOnSelectionChange = () => {}; @@ -37,6 +38,23 @@ export default function DataViews( { } ) { const [ selection, setSelection ] = useState( [] ); + useEffect( () => { + if ( + selection.length > 0 && + selection.some( + ( id ) => ! data.some( ( item ) => item.id === id ) + ) + ) { + const newSelection = selection.filter( ( id ) => + data.some( ( item ) => item.id === id ) + ); + setSelection( newSelection ); + onSelectionChange( + data.filter( ( item ) => newSelection.includes( item.id ) ) + ); + } + }, [ selection, data, onSelectionChange ] ); + const onSetSelection = useCallback( ( items ) => { setSelection( items.map( ( item ) => item.id ) ); @@ -75,6 +93,15 @@ export default function DataViews( { onChangeView={ onChangeView } /> + { view.type === LAYOUT_TABLE && ( + + ) } setIsModalOpen( false ) } /> @@ -96,7 +96,7 @@ function ActionsDropdownMenuGroup( { actions, item } ) { action.callback( item ) } + onClick={ () => action.callback( [ item ] ) } /> ); } ) } @@ -157,7 +157,7 @@ export default function ItemActions( { item, actions, isCompact } ) { action.callback( item ) } + onClick={ () => action.callback( [ item ] ) } /> ); } ) } diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index 80630050b68ef..d934ea0df62d0 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -18,6 +18,10 @@ } } +.dataviews-filters__view-actions.components-h-stack { + align-items: center; +} + .dataviews-filters-button { position: relative; } @@ -81,6 +85,14 @@ &[data-field-id="actions"] { text-align: right; } + + &.dataviews-view-table__checkbox-column { + padding-right: 0; + } + + .components-checkbox-control__input-container { + margin: $grid-unit-05; + } } tr { border-bottom: 1px solid $gray-100; @@ -109,8 +121,32 @@ } &:hover { - td { - background-color: #f8f8f8; + background-color: #f8f8f8; + } + + .components-checkbox-control__input { + opacity: 0; + + &:checked, + &:indeterminate, + &:focus { + opacity: 1; + } + } + + &:focus-within, + &:hover { + .components-checkbox-control__input { + opacity: 1; + } + } + + &.is-selected { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.04); + color: $gray-700; + + &:hover { + background-color: rgba(var(--wp-admin-theme-color--rgb), 0.08); } } } @@ -373,7 +409,23 @@ padding: 0 $grid-unit-40; } +.dataviews-view-table-selection-checkbox label { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + .dataviews-filters__custom-menu-radio-item-prefix { display: block; width: 24px; } + +.dataviews-bulk-edit-button.components-button { + flex-shrink: 0; +} diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js index dc76572e30494..e59c4e001919c 100644 --- a/packages/dataviews/src/view-table.js +++ b/packages/dataviews/src/view-table.js @@ -1,18 +1,19 @@ /** * External dependencies */ -import classNames from 'classnames'; +import classnames from 'classnames'; /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useAsyncList } from '@wordpress/compose'; import { unseen, funnel } from '@wordpress/icons'; import { Button, Icon, privateApis as componentsPrivateApis, + CheckboxControl, } from '@wordpress/components'; import { Children, @@ -306,6 +307,80 @@ function WithSeparators( { children } ) { ) ); } +function BulkSelectionCheckbox( { selection, onSelectionChange, data } ) { + const areAllSelected = selection.length === data.length; + return ( + { + if ( areAllSelected ) { + onSelectionChange( [] ); + } else { + onSelectionChange( data ); + } + } } + label={ areAllSelected ? __( 'Deselect all' ) : __( 'Select all' ) } + /> + ); +} + +function SingleSelectionCheckbox( { + selection, + onSelectionChange, + item, + data, + getItemId, + primaryField, +} ) { + const id = getItemId( item ); + const isSelected = selection.includes( id ); + let selectionLabel; + if ( primaryField?.getValue && item ) { + // eslint-disable-next-line @wordpress/valid-sprintf + selectionLabel = sprintf( + /* translators: %s: item title. */ + isSelected ? __( 'Deselect item: %s' ) : __( 'Select item: %s' ), + primaryField.getValue( { item } ) + ); + } else { + selectionLabel = isSelected + ? __( 'Select a new item' ) + : __( 'Deselect item' ); + } + return ( + { + if ( ! isSelected ) { + onSelectionChange( + data.filter( ( _item ) => { + const itemId = getItemId?.( _item ); + return ( + itemId === id || selection.includes( itemId ) + ); + } ) + ); + } else { + onSelectionChange( + data.filter( ( _item ) => { + const itemId = getItemId?.( _item ); + return ( + itemId !== id && selection.includes( itemId ) + ); + } ) + ); + } + } } + /> + ); +} + function ViewTable( { view, onChangeView, @@ -315,7 +390,10 @@ function ViewTable( { getItemId, isLoading = false, deferredRendering, + selection, + onSelectionChange, } ) { + const hasBulkActions = actions?.some( ( action ) => action.supportsBulk ); const headerMenuRefs = useRef( new Map() ); const headerMenuToFocusRef = useRef(); const [ nextHeaderMenuToFocus, setNextHeaderMenuToFocus ] = useState(); @@ -348,14 +426,16 @@ function ViewTable( { const visibleFields = fields.filter( ( field ) => ! view.hiddenFields.includes( field.id ) && - ! [ view.layout.mediaField, view.layout.primaryField ].includes( - field.id - ) + ! [ view.layout.mediaField ].includes( field.id ) ); const usedData = deferredRendering ? asyncData : data; const hasData = !! usedData?.length; const sortValues = { asc: 'ascending', desc: 'descending' }; + const primaryField = fields.find( + ( field ) => field.id === view.layout.primaryField + ); + return (
- + + { hasBulkActions && ( + + ) } { visibleFields.map( ( field, index ) => ( { hasData && - usedData.map( ( item ) => ( - + usedData.map( ( item, index ) => ( + + { hasBulkActions && ( + + ) } { visibleFields.map( ( field ) => (
+ +
+ +
{ + RenderModal: ( { items: posts, closeModal } ) => { + // Todo - handle multiple posts + const post = posts[ 0 ]; const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); const { deleteEntityRecord } = useDispatch( coreStore ); @@ -109,7 +111,9 @@ export function usePermanentlyDeletePostAction() { isEligible( { status } ) { return status === 'trash'; }, - async callback( post ) { + async callback( posts ) { + // Todo - handle multiple posts + const post = posts[ 0 ]; try { await deleteEntityRecord( 'postType', @@ -160,7 +164,9 @@ export function useRestorePostAction() { isEligible( { status } ) { return status === 'trash'; }, - async callback( post ) { + async callback( posts ) { + // Todo - handle multiple posts + const post = posts[ 0 ]; await editEntityRecord( 'postType', post.type, post.id, { status: 'draft', } ); @@ -211,7 +217,8 @@ export const viewPostAction = { isEligible( post ) { return post.status !== 'trash'; }, - callback( post ) { + callback( posts ) { + const post = posts[ 0 ]; document.location.href = post.link; }, }; @@ -225,7 +232,8 @@ export function useEditPostAction() { isEligible( { status } ) { return status !== 'trash'; }, - callback( post ) { + callback( posts ) { + const post = posts[ 0 ]; history.push( { postId: post.id, postType: post.type, @@ -250,7 +258,8 @@ export const postRevisionsAction = { post?._links?.[ 'version-history' ]?.[ 0 ]?.count ?? 0; return lastRevisionId && revisionsCount > 1; }, - callback( post ) { + callback( posts ) { + const post = posts[ 0 ]; const href = addQueryArgs( 'revision.php', { revision: post?._links?.[ 'predecessor-version' ]?.[ 0 ]?.id, } ); diff --git a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js index bf5210beb49fb..0c44c996ed373 100644 --- a/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js +++ b/packages/edit-site/src/components/page-patterns/dataviews-pattern-actions.js @@ -45,7 +45,7 @@ export const exportJSONaction = { id: 'export-pattern', label: __( 'Export as JSON' ), isEligible: ( item ) => item.type === PATTERN_TYPES.user, - callback: ( item ) => { + callback: ( [ item ] ) => { const json = { __file: item.type, title: item.title || item.name, @@ -71,7 +71,8 @@ export const renameAction = { const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; return isCustomPattern && ! hasThemeFile; }, - RenderModal: ( { item, closeModal } ) => { + RenderModal: ( { items, closeModal } ) => { + const [ item ] = items; const [ title, setTitle ] = useState( () => item.title ); const { editEntityRecord, saveEditedEntityRecord } = useDispatch( coreStore ); @@ -160,7 +161,8 @@ export const deleteAction = { return canDeleteOrReset( item ) && ! hasThemeFile; }, hideModalHeader: true, - RenderModal: ( { item, closeModal } ) => { + RenderModal: ( { items, closeModal } ) => { + const [ item ] = items; const { __experimentalDeleteReusableBlock } = useDispatch( reusableBlocksStore ); const { createErrorNotice, createSuccessNotice } = @@ -224,7 +226,8 @@ export const resetAction = { return canDeleteOrReset( item ) && hasThemeFile; }, hideModalHeader: true, - RenderModal: ( { item, closeModal } ) => { + RenderModal: ( { items, closeModal } ) => { + const [ item ] = items; const { removeTemplate } = useDispatch( editSiteStore ); return ( @@ -254,7 +257,8 @@ export const duplicatePatternAction = { label: _x( 'Duplicate', 'action label' ), isEligible: ( item ) => item.type !== TEMPLATE_PART_POST_TYPE, modalHeader: _x( 'Duplicate pattern', 'action label' ), - RenderModal: ( { item, closeModal } ) => { + RenderModal: ( { items, closeModal } ) => { + const [ item ] = items; const { categoryId = PATTERN_DEFAULT_CATEGORY } = getQueryArgs( window.location.href ); @@ -288,7 +292,8 @@ export const duplicateTemplatePartAction = { label: _x( 'Duplicate', 'action label' ), isEligible: ( item ) => item.type === TEMPLATE_PART_POST_TYPE, modalHeader: _x( 'Duplicate template part', 'action label' ), - RenderModal: ( { item, closeModal } ) => { + RenderModal: ( { items, closeModal } ) => { + const [ item ] = items; const { createSuccessNotice } = useDispatch( noticesStore ); const { categoryId = PATTERN_DEFAULT_CATEGORY } = getQueryArgs( window.location.href diff --git a/packages/edit-site/src/components/page-templates/index.js b/packages/edit-site/src/components/page-templates/index.js index c0e0289311db6..ddc48542ee1b7 100644 --- a/packages/edit-site/src/components/page-templates/index.js +++ b/packages/edit-site/src/components/page-templates/index.js @@ -65,7 +65,9 @@ const { useHistory } = unlock( routerPrivateApis ); const EMPTY_ARRAY = []; const defaultConfigPerViewType = { - [ LAYOUT_TABLE ]: {}, + [ LAYOUT_TABLE ]: { + primaryField: 'title', + }, [ LAYOUT_GRID ]: { mediaField: 'preview', primaryField: 'title', @@ -84,7 +86,7 @@ const DEFAULT_VIEW = { // All fields are visible by default, so it's // better to keep track of the hidden ones. hiddenFields: [ 'preview' ], - layout: {}, + layout: defaultConfigPerViewType[ LAYOUT_TABLE ], filters: [], }; diff --git a/packages/edit-site/src/components/page-templates/template-actions.js b/packages/edit-site/src/components/page-templates/template-actions.js index 9f5897e31fb93..7029d464ca867 100644 --- a/packages/edit-site/src/components/page-templates/template-actions.js +++ b/packages/edit-site/src/components/page-templates/template-actions.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { backup, trash } from '@wordpress/icons'; -import { __, sprintf } from '@wordpress/i18n'; +import { __, sprintf, _n } from '@wordpress/i18n'; import { useDispatch } from '@wordpress/data'; import { useMemo, useState } from '@wordpress/element'; import { store as coreStore } from '@wordpress/core-data'; @@ -19,6 +19,7 @@ import { /** * Internal dependencies */ +import { unlock } from '../../lock-unlock'; import { store as editSiteStore } from '../../store'; import isTemplateRevertable from '../../utils/is-template-revertable'; import isTemplateRemovable from '../../utils/is-template-removable'; @@ -32,39 +33,64 @@ export function useResetTemplateAction() { return useMemo( () => ( { id: 'reset-template', - label: __( 'Reset template' ), + label: __( 'Reset' ), isPrimary: true, icon: backup, isEligible: isTemplateRevertable, - async callback( template ) { + supportsBulk: true, + async callback( templates ) { try { - await revertTemplate( template, { allowUndo: false } ); - await saveEditedEntityRecord( - 'postType', - template.type, - template.id - ); + for ( const template of templates ) { + await revertTemplate( template, { + allowUndo: false, + } ); + await saveEditedEntityRecord( + 'postType', + template.type, + template.id + ); + } createSuccessNotice( - sprintf( - /* translators: The template/part's name. */ - __( '"%s" reverted.' ), - decodeEntities( template.title.rendered ) - ), + templates.length > 1 + ? sprintf( + /* translators: The number of items. */ + __( '%s items reverted.' ), + templates.length + ) + : sprintf( + /* translators: The template/part's name. */ + __( '"%s" reverted.' ), + decodeEntities( + templates[ 0 ].title.rendered + ) + ), { type: 'snackbar', id: 'edit-site-template-reverted', } ); } catch ( error ) { - const fallbackErrorMessage = - template.type === TEMPLATE_POST_TYPE - ? __( - 'An error occurred while reverting the template.' - ) - : __( - 'An error occurred while reverting the template part.' - ); + let fallbackErrorMessage; + if ( templates[ 0 ].type === TEMPLATE_POST_TYPE ) { + fallbackErrorMessage = + templates.length === 1 + ? __( + 'An error occurred while reverting the template.' + ) + : __( + 'An error occurred while reverting the templates.' + ); + } else { + fallbackErrorMessage = + templates.length === 1 + ? __( + 'An error occurred while reverting the template part.' + ) + : __( + 'An error occurred while reverting the template parts.' + ); + } const errorMessage = error.message && error.code !== 'unknown_error' ? error.message @@ -85,21 +111,34 @@ export function useResetTemplateAction() { export const deleteTemplateAction = { id: 'delete-template', - label: __( 'Delete template' ), + label: __( 'Delete' ), isPrimary: true, icon: trash, isEligible: isTemplateRemovable, + supportsBulk: true, hideModalHeader: true, - RenderModal: ( { item: template, closeModal } ) => { - const { removeTemplate } = useDispatch( editSiteStore ); + RenderModal: ( { items: templates, closeModal, onPerform } ) => { + const { removeTemplates } = unlock( useDispatch( editSiteStore ) ); return ( - { sprintf( - // translators: %s: The template or template part's title. - __( 'Are you sure you want to delete "%s"?' ), - decodeEntities( template.title.rendered ) - ) } + { templates.length > 1 + ? sprintf( + // translators: %d: number of items to delete. + _n( + 'Delete %d item?', + 'Delete %d items?', + templates.length + ), + templates.length + ) + : sprintf( + // translators: %s: The template or template part's titles + __( 'Delete "%s"?' ), + decodeEntities( + templates?.[ 0 ]?.title?.rendered + ) + ) } @@ -126,7 +169,8 @@ export const renameTemplateAction = { label: __( 'Rename' ), isEligible: ( template ) => isTemplateRemovable( template ) && template.is_custom, - RenderModal: ( { item: template, closeModal } ) => { + RenderModal: ( { items: templates, closeModal } ) => { + const template = templates[ 0 ]; const title = decodeEntities( template.title.rendered ); const [ editedTitle, setEditedTitle ] = useState( title ); const { diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 5a8adad8e198b..e7f2671784e1d 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -5,7 +5,7 @@ import apiFetch from '@wordpress/api-fetch'; import { parse, __unstableSerializeAndClean } from '@wordpress/blocks'; import deprecated from '@wordpress/deprecated'; import { addQueryArgs } from '@wordpress/url'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; import { store as coreStore } from '@wordpress/core-data'; import { store as interfaceStore } from '@wordpress/interface'; @@ -13,7 +13,6 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as editorStore } from '@wordpress/editor'; import { speak } from '@wordpress/a11y'; import { store as preferencesStore } from '@wordpress/preferences'; -import { decodeEntities } from '@wordpress/html-entities'; /** * Internal dependencies @@ -25,6 +24,8 @@ import { TEMPLATE_PART_POST_TYPE, NAVIGATION_POST_TYPE, } from '../utils/constants'; +import { removeTemplates } from './private-actions'; + /** * Dispatches an action that toggles a feature flag. * @@ -133,54 +134,9 @@ export const addTemplate = * * @param {Object} template The template object. */ -export const removeTemplate = - ( template ) => - async ( { registry } ) => { - try { - await registry - .dispatch( coreStore ) - .deleteEntityRecord( 'postType', template.type, template.id, { - force: true, - } ); - - const lastError = registry - .select( coreStore ) - .getLastEntityDeleteError( - 'postType', - template.type, - template.id - ); - - if ( lastError ) { - throw lastError; - } - - // Depending on how the entity was retrieved it's title might be - // an object or simple string. - const templateTitle = - typeof template.title === 'string' - ? template.title - : template.title?.rendered; - - registry.dispatch( noticesStore ).createSuccessNotice( - sprintf( - /* translators: The template/part's name. */ - __( '"%s" deleted.' ), - decodeEntities( templateTitle ) - ), - { type: 'snackbar', id: 'site-editor-template-deleted-success' } - ); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while deleting the template.' ); - - registry - .dispatch( noticesStore ) - .createErrorNotice( errorMessage, { type: 'snackbar' } ); - } - }; +export const removeTemplate = ( template ) => { + return removeTemplates( [ template ] ); +}; /** * Action that sets a template part. diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js index 7354f7b9b8843..71f35dc66399e 100644 --- a/packages/edit-site/src/store/private-actions.js +++ b/packages/edit-site/src/store/private-actions.js @@ -4,6 +4,10 @@ import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as preferencesStore } from '@wordpress/preferences'; import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; +import { __, sprintf } from '@wordpress/i18n'; +import { decodeEntities } from '@wordpress/html-entities'; /** * Action that switches the canvas mode. @@ -49,3 +53,102 @@ export const setEditorCanvasContainerView = view, } ); }; + +/** + * Action that removes an array of templates. + * + * @param {Array} templates An array of template objects to remove. + */ +export const removeTemplates = + ( templates ) => + async ( { registry } ) => { + const promiseResult = await Promise.allSettled( + templates.map( ( template ) => { + return registry + .dispatch( coreStore ) + .deleteEntityRecord( + 'postType', + template.type, + template.id, + { force: true }, + { throwOnError: true } + ); + } ) + ); + + // If all the promises were fulfilled with sucess. + if ( promiseResult.every( ( { status } ) => status === 'fulfilled' ) ) { + let successMessage; + + if ( templates.length === 1 ) { + // Depending on how the entity was retrieved its title might be + // an object or simple string. + const templateTitle = + typeof templates[ 0 ].title === 'string' + ? templates[ 0 ].title + : templates[ 0 ].title?.rendered; + successMessage = sprintf( + /* translators: The template/part's name. */ + __( '"%s" deleted.' ), + decodeEntities( templateTitle ) + ); + } else { + successMessage = __( 'Templates deleted.' ); + } + + registry + .dispatch( noticesStore ) + .createSuccessNotice( successMessage, { + type: 'snackbar', + id: 'site-editor-template-deleted-success', + } ); + } else { + // If there was at lease one failure. + let errorMessage; + // If we were trying to delete a single template. + if ( promiseResult.length === 1 ) { + if ( promiseResult[ 0 ].reason?.message ) { + errorMessage = promiseResult[ 0 ].reason.message; + } else { + errorMessage = __( + 'An error occurred while deleting the template.' + ); + } + // If we were trying to delete a multiple templates + } else { + const errorMessages = new Set(); + const failedPromises = promiseResult.filter( + ( { status } ) => status === 'rejected' + ); + for ( const failedPromise of failedPromises ) { + if ( failedPromise.reason?.message ) { + errorMessages.add( failedPromise.reason.message ); + } + } + if ( errorMessages.size === 0 ) { + errorMessage = __( + 'An error occurred while deleting the templates.' + ); + } else if ( errorMessages.size === 1 ) { + errorMessage = sprintf( + /* translators: %s: an error message */ + __( + 'An error occurred while deleting the templates: %s' + ), + [ ...errorMessages ][ 0 ] + ); + } else { + errorMessage = sprintf( + /* translators: %s: a list of comma separated error messages */ + __( + 'Some errors occurred while deleting the templates: %s' + ), + [ ...errorMessages ].join( ',' ) + ); + } + } + registry + .dispatch( noticesStore ) + .createErrorNotice( errorMessage, { type: 'snackbar' } ); + } + }; From 512027e45181778e508aea99abfafc2254700962 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Fri, 12 Jan 2024 09:01:03 +1300 Subject: [PATCH 067/296] Patterns: Add black border back when editing synced pattern in the post editor (#57631) --- .../block-editor/src/components/block-canvas/style.scss | 9 ++++++++- packages/editor/src/components/editor-canvas/style.scss | 5 ----- packages/editor/src/style.scss | 1 - 3 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 packages/editor/src/components/editor-canvas/style.scss diff --git a/packages/block-editor/src/components/block-canvas/style.scss b/packages/block-editor/src/components/block-canvas/style.scss index 54ccd407d74a2..631024b7c3aec 100644 --- a/packages/block-editor/src/components/block-canvas/style.scss +++ b/packages/block-editor/src/components/block-canvas/style.scss @@ -1,6 +1,13 @@ iframe[name="editor-canvas"] { width: 100%; height: 100%; - background-color: $white; display: block; } + +iframe[name="editor-canvas"]:not(.has-history) { + background-color: $white; +} + +iframe[name="editor-canvas"].has-history { + padding: $grid-unit-60 $grid-unit-60 0; +} diff --git a/packages/editor/src/components/editor-canvas/style.scss b/packages/editor/src/components/editor-canvas/style.scss deleted file mode 100644 index d5baf48012452..0000000000000 --- a/packages/editor/src/components/editor-canvas/style.scss +++ /dev/null @@ -1,5 +0,0 @@ -.editor-canvas__iframe { - &.has-history { - padding: $grid-unit-60 $grid-unit-60 0; - } -} diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index ff5a55a3881f9..09e50d1abed79 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -29,4 +29,3 @@ @import "./components/preview-dropdown/style.scss"; @import "./components/table-of-contents/style.scss"; @import "./components/template-validation-notice/style.scss"; -@import "./components/editor-canvas/style.scss"; From 8ef45064a7ee07c2bea0ce32cc0f54eae430355f Mon Sep 17 00:00:00 2001 From: Chad Chadbourne <13856531+chad1008@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:25:25 -0500 Subject: [PATCH 068/296] Components: replace `TabPanel` with `Tabs` in the Editor Preferences Modal (#57293) * add support for private apis * implement Tabs * update styles * update code comment * classnames rather than role selectors * remove style nesting --- package-lock.json | 2 + packages/interface/lock-unlock.js | 10 +++ packages/interface/package.json | 1 + .../preferences-modal-tabs/index.js | 60 +++++++++++----- .../preferences-modal-tabs/style.scss | 69 +++++++++---------- packages/private-apis/src/implementation.js | 1 + 6 files changed, 89 insertions(+), 54 deletions(-) create mode 100644 packages/interface/lock-unlock.js diff --git a/package-lock.json b/package-lock.json index 96a14ec8eeb50..dc84ae2fba494 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55297,6 +55297,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/plugins": "file:../plugins", "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", "@wordpress/viewport": "file:../viewport", "classnames": "^2.3.1" }, @@ -70104,6 +70105,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/plugins": "file:../plugins", "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", "@wordpress/viewport": "file:../viewport", "classnames": "^2.3.1" } diff --git a/packages/interface/lock-unlock.js b/packages/interface/lock-unlock.js new file mode 100644 index 0000000000000..b6e29bb71c7c0 --- /dev/null +++ b/packages/interface/lock-unlock.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; + +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.', + '@wordpress/interface' + ); diff --git a/packages/interface/package.json b/packages/interface/package.json index df3d53990e0f5..7356a7b52d1d4 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -42,6 +42,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/plugins": "file:../plugins", "@wordpress/preferences": "file:../preferences", + "@wordpress/private-apis": "file:../private-apis", "@wordpress/viewport": "file:../viewport", "classnames": "^2.3.1" }, diff --git a/packages/interface/src/components/preferences-modal-tabs/index.js b/packages/interface/src/components/preferences-modal-tabs/index.js index bc8f7358b834d..985d963227257 100644 --- a/packages/interface/src/components/preferences-modal-tabs/index.js +++ b/packages/interface/src/components/preferences-modal-tabs/index.js @@ -13,15 +13,22 @@ import { __experimentalText as Text, __experimentalTruncate as Truncate, FlexItem, - TabPanel, Card, CardHeader, CardBody, + privateApis as componentsPrivateApis, } from '@wordpress/components'; -import { useMemo, useCallback, useState } from '@wordpress/element'; +import { useMemo, useState } from '@wordpress/element'; import { chevronLeft, chevronRight, Icon } from '@wordpress/icons'; import { isRTL, __ } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import { unlock } from '../../../lock-unlock'; + +const { Tabs } = unlock( componentsPrivateApis ); + const PREFERENCES_MENU = 'preferences-menu'; export default function PreferencesModalTabs( { sections } ) { @@ -32,7 +39,7 @@ export default function PreferencesModalTabs( { sections } ) { const [ activeMenu, setActiveMenu ] = useState( PREFERENCES_MENU ); /** * Create helper objects from `sections` for easier data handling. - * `tabs` is used for creating the `TabPanel` and `sectionsContentMap` + * `tabs` is used for creating the `Tabs` and `sectionsContentMap` * is used for easier access to active tab's content. */ const { tabs, sectionsContentMap } = useMemo( () => { @@ -53,26 +60,41 @@ export default function PreferencesModalTabs( { sections } ) { return mappedTabs; }, [ sections ] ); - const getCurrentTab = useCallback( - ( tab ) => sectionsContentMap[ tab.name ] || null, - [ sectionsContentMap ] - ); - let modalContent; // We render different components based on the viewport size. if ( isLargeViewport ) { modalContent = ( - - { getCurrentTab } - +
+ + + { tabs.map( ( tab ) => ( + + { tab.title } + + ) ) } + + { tabs.map( ( tab ) => ( + + { sectionsContentMap[ tab.name ] || null } + + ) ) } + +
); } else { modalContent = ( diff --git a/packages/interface/src/components/preferences-modal-tabs/style.scss b/packages/interface/src/components/preferences-modal-tabs/style.scss index 04b71f0a773a2..f598545d69c25 100644 --- a/packages/interface/src/components/preferences-modal-tabs/style.scss +++ b/packages/interface/src/components/preferences-modal-tabs/style.scss @@ -1,45 +1,44 @@ $vertical-tabs-width: 160px; -.interface-preferences__tabs { - .components-tab-panel__tabs { - position: absolute; - top: $header-height + $grid-unit-30; - // Aligns button text instead of button box. - left: $grid-unit-20; - width: $vertical-tabs-width; - - .components-tab-panel__tabs-item { - border-radius: $radius-block-ui; - font-weight: 400; - - &.is-active { - background: $gray-100; - box-shadow: none; - font-weight: 500; - } - - &.is-active::after { - content: none; - } - - &:focus:not(:disabled) { - box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - // Windows high contrast mode. - outline: 2px solid transparent; - } - - &:focus-visible::before { - content: none; - } - } +.interface-preferences__tabs-tablist { + position: absolute; + top: $header-height + $grid-unit-30; + // Aligns button text instead of button box. + left: $grid-unit-20; + width: $vertical-tabs-width; + +} + +.interface-preferences__tabs-tab { + border-radius: $radius-block-ui; + font-weight: 400; + + &[aria-selected="true"] { + background: $gray-100; + box-shadow: none; + font-weight: 500; + } + + &[aria-selected="true"]::after { + content: none; + } + + &[role="tab"]:focus:not(:disabled) { + box-shadow: inset 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + // Windows high contrast mode. + outline: 2px solid transparent; } - .components-tab-panel__tab-content { - padding-left: $grid-unit-30; - margin-left: $vertical-tabs-width; + &:focus-visible::before { + content: none; } } +.interface-preferences__tabs-tabpanel { + padding-left: $grid-unit-30; + margin-left: $vertical-tabs-width; +} + @media (max-width: #{ ($break-medium - 1) }) { // Keep the navigator component from overflowing the modal content area // to ensure that sticky position elements stick where intended. diff --git a/packages/private-apis/src/implementation.js b/packages/private-apis/src/implementation.js index 619478cf76386..4195991381d02 100644 --- a/packages/private-apis/src/implementation.js +++ b/packages/private-apis/src/implementation.js @@ -25,6 +25,7 @@ const CORE_MODULES_USING_PRIVATE_APIS = [ '@wordpress/edit-widgets', '@wordpress/editor', '@wordpress/format-library', + '@wordpress/interface', '@wordpress/patterns', '@wordpress/reusable-blocks', '@wordpress/router', From 2d5baf49f43d1cc3adae0a414aad991f9457c39f Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 12 Jan 2024 00:19:38 +0100 Subject: [PATCH 069/296] Scripts: Fix misplaced ReactRefreshWebpackPlugin (#57777) Restore ReactRefreshWebpackPlugin to @wordpress/scripts webpack config. #57461 accidentally moved the plugin to the modules config. It should have remained in the scripts webpack config and not been included in the modules config. --- packages/scripts/config/webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scripts/config/webpack.config.js b/packages/scripts/config/webpack.config.js index 57bd258d32539..cc76568f6fb1b 100644 --- a/packages/scripts/config/webpack.config.js +++ b/packages/scripts/config/webpack.config.js @@ -380,6 +380,8 @@ const scriptConfig = { process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), // MiniCSSExtractPlugin to extract the CSS thats gets imported into JavaScript. new MiniCSSExtractPlugin( { filename: '[name].css' } ), + // React Fast Refresh. + hasReactFastRefresh && new ReactRefreshWebpackPlugin(), // WP_NO_EXTERNALS global variable controls whether scripts' assets get // generated, and the default externals set. ! process.env.WP_NO_EXTERNALS && @@ -423,8 +425,6 @@ if ( hasExperimentalModulesFlag ) { process.env.WP_BUNDLE_ANALYZER && new BundleAnalyzerPlugin(), // MiniCSSExtractPlugin to extract the CSS thats gets imported into JavaScript. new MiniCSSExtractPlugin( { filename: '[name].css' } ), - // React Fast Refresh. - hasReactFastRefresh && new ReactRefreshWebpackPlugin(), // WP_NO_EXTERNALS global variable controls whether scripts' assets get // generated, and the default externals set. ! process.env.WP_NO_EXTERNALS && From d439f8ba42855b5d11c8c652a63405a95c3af484 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 12 Jan 2024 10:55:26 +1100 Subject: [PATCH 070/296] Only prioritise Quote transform where relevant (#57749) * Only prioritise Quote transform where relevant * Add explanatory comment. --- .../block-transformations-menu.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-switcher/block-transformations-menu.js b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js index 033201d7facad..271b7fabd5173 100644 --- a/packages/block-editor/src/components/block-switcher/block-transformations-menu.js +++ b/packages/block-editor/src/components/block-switcher/block-transformations-menu.js @@ -37,7 +37,7 @@ function useGroupedTransforms( possibleBlockTransformations ) { const priorityTextTranformsNames = Object.keys( priorityContentTranformationBlocks ); - return possibleBlockTransformations.reduce( + const groupedPossibleTransforms = possibleBlockTransformations.reduce( ( accumulator, item ) => { const { name } = item; if ( priorityTextTranformsNames.includes( name ) ) { @@ -49,6 +49,23 @@ function useGroupedTransforms( possibleBlockTransformations ) { }, { priorityTextTransformations: [], restTransformations: [] } ); + /** + * If there is only one priority text transformation and it's a Quote, + * is should move to the rest transformations. This is because Quote can + * be a container for any block type, so in multi-block selection it will + * always be suggested, even for non-text blocks. + */ + if ( + groupedPossibleTransforms.priorityTextTransformations.length === + 1 && + groupedPossibleTransforms.priorityTextTransformations[ 0 ].name === + 'core/quote' + ) { + const singleQuote = + groupedPossibleTransforms.priorityTextTransformations.pop(); + groupedPossibleTransforms.restTransformations.push( singleQuote ); + } + return groupedPossibleTransforms; }, [ possibleBlockTransformations ] ); // Order the priority text transformations. From b41c08fb3fec22743b4dcdd23a37b9e4df55b054 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Thu, 11 Jan 2024 21:04:40 -0300 Subject: [PATCH 071/296] Font Library: Add font collection JSON schema (#57736) * move font family to their on definition * adding font-collection.json schema * adding settings * updating README * update property name * resolve json schema references * throw error on definition not found * add description * disallow additional properties * fix typo in function name Co-authored-by: Jeff Ong * fix typo in function name Co-authored-by: Jeff Ong * fix wording Co-authored-by: Jeff Ong * remove unwanted comment Co-authored-by: Jeff Ong * format js --------- Co-authored-by: Jeff Ong --- bin/api-docs/gen-theme-reference.js | 40 ++++- schemas/README.md | 20 ++- schemas/json/font-collection.json | 59 +++++++ schemas/json/theme.json | 236 ++++++++++++++-------------- 4 files changed, 236 insertions(+), 119 deletions(-) create mode 100644 schemas/json/font-collection.json diff --git a/bin/api-docs/gen-theme-reference.js b/bin/api-docs/gen-theme-reference.js index f638bb708890a..0ea9e282e5463 100644 --- a/bin/api-docs/gen-theme-reference.js +++ b/bin/api-docs/gen-theme-reference.js @@ -74,6 +74,42 @@ const keys = ( maybeObject ) => { return Object.keys( maybeObject ); }; +/** + * Get definition from ref. + * + * @param {string} ref + * @return {Object} definition + * @throws {Error} If the referenced definition is not found in 'themejson.definitions'. + * + * @example + * getDefinition( '#/definitions/typographyProperties/properties/fontFamily' ) + * // returns themejson.definitions.typographyProperties.properties.fontFamily + */ +const resolveDefinitionRef = ( ref ) => { + const refParts = ref.split( '/' ); + const definition = refParts[ refParts.length - 1 ]; + if ( ! themejson.definitions[ definition ] ) { + throw new Error( `Can't resolve '${ ref }'. Definition not found` ); + } + return themejson.definitions[ definition ]; +}; + +/** + * Get properties from an array. + * + * @param {Object} items + * @return {Object} properties + */ +const getPropertiesFromArray = ( items ) => { + // if its a $ref resolve it + if ( items.$ref ) { + return resolveDefinitionRef( items.$ref ).properties; + } + + // otherwise just return the properties + return items.properties; +}; + /** * Convert settings properties to markup. * @@ -96,7 +132,9 @@ const getSettingsPropertiesMarkup = ( struct ) => { const def = 'default' in props[ key ] ? props[ key ].default : ''; const ps = props[ key ].type === 'array' - ? keys( props[ key ].items.properties ).sort().join( ', ' ) + ? keys( getPropertiesFromArray( props[ key ].items ) ) + .sort() + .join( ', ' ) : ''; markup += `| ${ key } | ${ props[ key ].type } | ${ def } | ${ ps } |\n`; } ); diff --git a/schemas/README.md b/schemas/README.md index 1b2d8992cccb9..fa695115f33ab 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -1,6 +1,6 @@ # Schemas -The collection of schemas used in WordPress, including the `theme.json` and `block.json` schemas. +The collection of schemas used in WordPress, including the `theme.json`, `block.json` and `font-collection.json` schemas. JSON schemas are used by code editors to offer tooltips, autocomplete, and validation. @@ -24,6 +24,14 @@ Or in your `theme.json`: } ``` +Or in your `font-collection.json`: + +```json +{ + "$schema": "https://schemas.wp.org/trunk/font-collection.json" +} +``` + For a specific version of the schema, replace `trunk` with `wp/X.X`: ```json @@ -56,8 +64,16 @@ To allow this you will need to: } ``` +- update your font collections's `font-collection.json` to include: + +```json +{ + "$schema": "file://{{FULL_FILE_PATH}}/schemas/json/font-collection.json" +} +``` + Be sure to replace `{{FULL_FILE_PATH}}` with the full local path to your Gutenberg repo. -With this in place you should now be able to edit either `schemas/json/theme .json` or `schemas/json/block.json` in order to see changes reflected in `theme.json` or `block.json` in your IDE. +With this in place you should now be able to edit either `schemas/json/theme .json`, `schemas/json/block.json` or `schemas/json/font-collection.json` in order to see changes reflected in `theme.json`, `block.json` or `font-collection.json` in your IDE.

Code is Poetry.

diff --git a/schemas/json/font-collection.json b/schemas/json/font-collection.json new file mode 100644 index 0000000000000..a6ca2b1412e6d --- /dev/null +++ b/schemas/json/font-collection.json @@ -0,0 +1,59 @@ +{ + "title": "JSON schema for WordPress Font Collections", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "$schema": { + "description": "JSON schema URI for font-collection.json.", + "type": "string" + }, + "version": { + "description": "Version of font-collection.json schema to use.", + "type": "integer", + "enum": [ 1 ] + }, + "font_families": { + "type": "array", + "description": "Array of font families ready to be installed", + "items": { + "type": "object", + "properties": { + "font_family_settings": { + "description": "Font family settings as in theme.json", + "allOf": [ + { "$ref": "./theme.json#/definitions/fontFamily" } + ] + }, + "categories": { + "type": "array", + "description": "Array of category slugs", + "items": { + "type": "string" + } + } + }, + "required": [ "font_family_settings" ], + "additionalProperties": false + } + }, + "categories": { + "type": "array", + "description": "Array of category objects", + "items": { + "type": "object", + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ "slug", "name" ], + "additionalProperties": false + } + } + }, + "additionalProperties": false, + "required": [ "$schema", "version", "font_families" ] +} diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 9f633fc77ec75..6ae8d15df63d2 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -614,132 +614,136 @@ "description": "Font family presets for the font family selector.\nGenerates a single custom property (`--wp--preset--font-family--{slug}`) per preset value.", "type": "array", "items": { - "type": "object", - "properties": { - "name": { - "description": "Name of the font family preset, translatable.", - "type": "string" - }, - "slug": { - "description": "Kebab-case unique identifier for the font family preset.", - "type": "string" - }, - "fontFamily": { - "description": "CSS font-family value.", + "$ref": "#/definitions/fontFamily" + } + } + }, + "additionalProperties": false + } + } + }, + "fontFamily": { + "type": "object", + "description": "Font family preset", + "properties": { + "name": { + "description": "Name of the font family preset, translatable.", + "type": "string" + }, + "slug": { + "description": "Kebab-case unique identifier for the font family preset.", + "type": "string" + }, + "fontFamily": { + "description": "CSS font-family value.", + "type": "string" + }, + "preview": { + "description": "URL to a preview image of the font family.", + "type": "string" + }, + "fontFace": { + "description": "Array of font-face declarations.", + "type": "array", + "items": { + "type": "object", + "properties": { + "fontFamily": { + "description": "CSS font-family value.", + "type": "string", + "default": "" + }, + "fontStyle": { + "description": "CSS font-style value.", + "type": "string", + "default": "normal" + }, + "fontWeight": { + "description": "List of available font weights, separated by a space.", + "default": "400", + "oneOf": [ + { "type": "string" }, - "preview": { - "description": "URL to a preview image of the font family.", + { + "type": "integer" + } + ] + }, + "fontDisplay": { + "description": "CSS font-display value.", + "type": "string", + "default": "fallback", + "enum": [ + "auto", + "block", + "fallback", + "swap", + "optional" + ] + }, + "src": { + "description": "Paths or URLs to the font files.", + "oneOf": [ + { "type": "string" }, - "fontFace": { - "description": "Array of font-face declarations.", + { "type": "array", "items": { - "type": "object", - "properties": { - "fontFamily": { - "description": "CSS font-family value.", - "type": "string", - "default": "" - }, - "fontStyle": { - "description": "CSS font-style value.", - "type": "string", - "default": "normal" - }, - "fontWeight": { - "description": "List of available font weights, separated by a space.", - "default": "400", - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "fontDisplay": { - "description": "CSS font-display value.", - "type": "string", - "default": "fallback", - "enum": [ - "auto", - "block", - "fallback", - "swap", - "optional" - ] - }, - "src": { - "description": "Paths or URLs to the font files.", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ], - "default": [] - }, - "fontStretch": { - "description": "CSS font-stretch value.", - "type": "string" - }, - "ascentOverride": { - "description": "CSS ascent-override value.", - "type": "string" - }, - "descentOverride": { - "description": "CSS descent-override value.", - "type": "string" - }, - "fontVariant": { - "description": "CSS font-variant value.", - "type": "string" - }, - "fontFeatureSettings": { - "description": "CSS font-feature-settings value.", - "type": "string" - }, - "fontVariationSettings": { - "description": "CSS font-variation-settings value.", - "type": "string" - }, - "lineGapOverride": { - "description": "CSS line-gap-override value.", - "type": "string" - }, - "sizeAdjust": { - "description": "CSS size-adjust value.", - "type": "string" - }, - "unicodeRange": { - "description": "CSS unicode-range value.", - "type": "string" - }, - "preview": { - "description": "URL to a preview image of the font face.", - "type": "string" - } - }, - "required": [ "fontFamily", "src" ], - "additionalProperties": false + "type": "string" } } - }, - "additionalProperties": false + ], + "default": [] + }, + "fontStretch": { + "description": "CSS font-stretch value.", + "type": "string" + }, + "ascentOverride": { + "description": "CSS ascent-override value.", + "type": "string" + }, + "descentOverride": { + "description": "CSS descent-override value.", + "type": "string" + }, + "fontVariant": { + "description": "CSS font-variant value.", + "type": "string" + }, + "fontFeatureSettings": { + "description": "CSS font-feature-settings value.", + "type": "string" + }, + "fontVariationSettings": { + "description": "CSS font-variation-settings value.", + "type": "string" + }, + "lineGapOverride": { + "description": "CSS line-gap-override value.", + "type": "string" + }, + "sizeAdjust": { + "description": "CSS size-adjust value.", + "type": "string" + }, + "unicodeRange": { + "description": "CSS unicode-range value.", + "type": "string" + }, + "preview": { + "description": "URL to a preview image of the font face.", + "type": "string" } - } - }, - "additionalProperties": false + }, + "required": [ "fontFamily", "src" ], + "additionalProperties": false + } } - } + }, + "additionalProperties": false }, "settingsPropertiesCustom": { "type": "object", From 3dba5c9f37cf4700252cda58f5b818215b356625 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 12 Jan 2024 12:46:59 +1100 Subject: [PATCH 072/296] Remove the unnecessary padding from preview iframe body tags (#57748) --- packages/edit-site/src/components/revisions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/revisions/index.js b/packages/edit-site/src/components/revisions/index.js index 3d69788089579..bb2cc8feabb93 100644 --- a/packages/edit-site/src/components/revisions/index.js +++ b/packages/edit-site/src/components/revisions/index.js @@ -78,7 +78,7 @@ function Revisions( { userConfig, blocks } ) { { // Forming a "block formatting context" to prevent margin collapsing. // @see https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Block_formatting_context - `.is-root-container { display: flow-root; } body { position: relative; padding: 32px; }` + `.is-root-container { display: flow-root; }` } From 9dcd4ccdee77893b29212bec42e309ea95a1152d Mon Sep 17 00:00:00 2001 From: Brooke <35543432+brookewp@users.noreply.github.com> Date: Thu, 11 Jan 2024 19:04:17 -0800 Subject: [PATCH 073/296] `BoxControl`: update design (#56665) --- packages/components/CHANGELOG.md | 1 + .../src/box-control/all-input-control.tsx | 88 +++++--- .../src/box-control/axial-input-controls.tsx | 145 ++++++------- packages/components/src/box-control/index.tsx | 99 ++++----- .../src/box-control/input-controls.tsx | 194 ++++++++++-------- .../box-control/styles/box-control-styles.ts | 82 ++------ .../styles/box-control-visualizer-styles.ts | 75 ------- .../components/src/box-control/test/index.tsx | 144 +++++++++++-- packages/components/src/box-control/types.ts | 25 +-- .../src/box-control/unit-control.tsx | 74 ------- packages/components/src/box-control/utils.ts | 51 ++++- 11 files changed, 481 insertions(+), 497 deletions(-) delete mode 100644 packages/components/src/box-control/styles/box-control-visualizer-styles.ts delete mode 100644 packages/components/src/box-control/unit-control.tsx diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 5856259e47730..646fc6eaa9dd0 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -41,6 +41,7 @@ - `Tabs`: do not render hidden content ([#57046](https://github.com/WordPress/gutenberg/pull/57046)). - `Tabs`: improve hover and text alignment styles ([#57275](https://github.com/WordPress/gutenberg/pull/57275)). - `Tabs`: make sure `Tab`s are associated to the right `TabPanel`s, regardless of the order they're rendered in ([#57033](https://github.com/WordPress/gutenberg/pull/57033)). +- `BoxControl`: Update design ([#56665](https://github.com/WordPress/gutenberg/pull/56665)). ## 25.14.0 (2023-12-13) diff --git a/packages/components/src/box-control/all-input-control.tsx b/packages/components/src/box-control/all-input-control.tsx index b66e10fdb4ce3..9c18694bbd0b6 100644 --- a/packages/components/src/box-control/all-input-control.tsx +++ b/packages/components/src/box-control/all-input-control.tsx @@ -1,15 +1,25 @@ +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ import type { UnitControlProps } from '../unit-control/types'; +import { + FlexedRangeControl, + StyledUnitControl, +} from './styles/box-control-styles'; +import { HStack } from '../h-stack'; import type { BoxControlInputControlProps } from './types'; -import UnitControl from './unit-control'; +import { parseQuantityAndUnitFromRawValue } from '../unit-control'; import { LABELS, applyValueToSides, getAllValue, isValuesMixed, isValuesDefined, + CUSTOM_VALUE_SETTINGS, } from './utils'; const noop = () => {}; @@ -17,26 +27,29 @@ const noop = () => {}; export default function AllInputControl( { onChange = noop, onFocus = noop, - onHoverOn = noop, - onHoverOff = noop, values, sides, selectedUnits, setSelectedUnits, ...props }: BoxControlInputControlProps ) { + const inputId = useInstanceId( AllInputControl, 'box-control-input-all' ); + const allValue = getAllValue( values, selectedUnits, sides ); const hasValues = isValuesDefined( values ); const isMixed = hasValues && isValuesMixed( values, selectedUnits, sides ); const allPlaceholder = isMixed ? LABELS.mixed : undefined; + const [ parsedQuantity, parsedUnit ] = + parseQuantityAndUnitFromRawValue( allValue ); + const handleOnFocus: React.FocusEventHandler< HTMLInputElement > = ( event ) => { onFocus( event, { side: 'all' } ); }; - const handleOnChange: UnitControlProps[ 'onChange' ] = ( next ) => { + const onValueChange = ( next?: string ) => { const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) ); const nextValue = isNumeric ? next : undefined; const nextValues = applyValueToSides( values, nextValue, sides ); @@ -44,6 +57,12 @@ export default function AllInputControl( { onChange( nextValues ); }; + const sliderOnChange = ( next?: number ) => { + onValueChange( + next !== undefined ? [ next, parsedUnit ].join( '' ) : undefined + ); + }; + // Set selected unit so it can be used as fallback by unlinked controls // when individual sides do not have a value containing a unit. const handleOnUnitChange: UnitControlProps[ 'onUnitChange' ] = ( unit ) => { @@ -51,36 +70,37 @@ export default function AllInputControl( { setSelectedUnits( newUnits ); }; - const handleOnHoverOn = () => { - onHoverOn( { - top: true, - bottom: true, - left: true, - right: true, - } ); - }; - - const handleOnHoverOff = () => { - onHoverOff( { - top: false, - bottom: false, - left: false, - right: false, - } ); - }; - return ( - + + + + + ); } diff --git a/packages/components/src/box-control/axial-input-controls.tsx b/packages/components/src/box-control/axial-input-controls.tsx index bc8a4bd420bbd..173605f68a872 100644 --- a/packages/components/src/box-control/axial-input-controls.tsx +++ b/packages/components/src/box-control/axial-input-controls.tsx @@ -1,10 +1,19 @@ +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; -import UnitControl from './unit-control'; -import { LABELS } from './utils'; -import { Layout } from './styles/box-control-styles'; +import Tooltip from '../tooltip'; +import { CUSTOM_VALUE_SETTINGS, LABELS } from './utils'; +import { + FlexedBoxControlIcon, + FlexedRangeControl, + InputWrapper, + StyledUnitControl, +} from './styles/box-control-styles'; import type { BoxControlInputControlProps } from './types'; const groupedSides = [ 'vertical', 'horizontal' ] as const; @@ -13,14 +22,17 @@ type GroupedSide = ( typeof groupedSides )[ number ]; export default function AxialInputControls( { onChange, onFocus, - onHoverOn, - onHoverOff, values, selectedUnits, setSelectedUnits, sides, ...props }: BoxControlInputControlProps ) { + const generatedId = useInstanceId( + AxialInputControls, + `box-control-input` + ); + const createHandleOnFocus = ( side: GroupedSide ) => ( event: React.FocusEvent< HTMLInputElement > ) => { @@ -30,43 +42,7 @@ export default function AxialInputControls( { onFocus( event, { side } ); }; - const createHandleOnHoverOn = ( side: GroupedSide ) => () => { - if ( ! onHoverOn ) { - return; - } - if ( side === 'vertical' ) { - onHoverOn( { - top: true, - bottom: true, - } ); - } - if ( side === 'horizontal' ) { - onHoverOn( { - left: true, - right: true, - } ); - } - }; - - const createHandleOnHoverOff = ( side: GroupedSide ) => () => { - if ( ! onHoverOff ) { - return; - } - if ( side === 'vertical' ) { - onHoverOff( { - top: false, - bottom: false, - } ); - } - if ( side === 'horizontal' ) { - onHoverOff( { - left: false, - right: false, - } ); - } - }; - - const createHandleOnChange = ( side: GroupedSide ) => ( next?: string ) => { + const handleOnValueChange = ( side: GroupedSide, next?: string ) => { if ( ! onChange ) { return; } @@ -109,16 +85,8 @@ export default function AxialInputControls( { ? groupedSides.filter( ( side ) => sides.includes( side ) ) : groupedSides; - const first = filteredSides[ 0 ]; - const last = filteredSides[ filteredSides.length - 1 ]; - const only = first === last && first; - return ( - + <> { filteredSides.map( ( side ) => { const [ parsedQuantity, parsedUnit ] = parseQuantityAndUnitFromRawValue( @@ -128,26 +96,65 @@ export default function AxialInputControls( { side === 'vertical' ? selectedUnits.top : selectedUnits.left; + + const inputId = [ generatedId, side ].join( '-' ); + return ( - + + + + + handleOnValueChange( side, newValue ) + } + onUnitChange={ createHandleOnUnitChange( + side + ) } + onFocus={ createHandleOnFocus( side ) } + label={ LABELS[ side ] } + hideLabelFromVision + key={ side } + /> + + + handleOnValueChange( + side, + newValue !== undefined + ? [ + newValue, + selectedUnit ?? parsedUnit, + ].join( '' ) + : undefined + ) + } + min={ 0 } + max={ + CUSTOM_VALUE_SETTINGS[ selectedUnit ?? 'px' ] + ?.max ?? 10 + } + step={ + CUSTOM_VALUE_SETTINGS[ selectedUnit ?? 'px' ] + ?.step ?? 0.1 + } + value={ parsedQuantity ?? 0 } + withInputField={ false } + /> + ); } ) } - + ); } diff --git a/packages/components/src/box-control/index.tsx b/packages/components/src/box-control/index.tsx index c7fcf066c545c..dcc890e8e3c51 100644 --- a/packages/components/src/box-control/index.tsx +++ b/packages/components/src/box-control/index.tsx @@ -9,17 +9,16 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { BaseControl } from '../base-control'; -import Button from '../button'; -import { FlexItem, FlexBlock } from '../flex'; import AllInputControl from './all-input-control'; import InputControls from './input-controls'; import AxialInputControls from './axial-input-controls'; -import BoxControlIcon from './icon'; import LinkedButton from './linked-button'; +import { Grid } from '../grid'; import { - Root, - Header, - HeaderControlWrapper, + FlexedBoxControlIcon, + InputWrapper, + ResetButton, + LinkedButtonWrapper, } from './styles/box-control-styles'; import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; import { @@ -155,57 +154,49 @@ function BoxControl( { }; return ( - -
- - - { label } - - - { allowReset && ( - - - - ) } -
- - - - - { isLinked && ( - - - - ) } - { ! isLinked && splitOnAxis && ( - - - - ) } - { ! hasOneSide && ( - - - - ) } - + + + { label } + + { isLinked && ( + + + + + ) } + { ! hasOneSide && ( + + + + ) } + + { ! isLinked && splitOnAxis && ( + + ) } { ! isLinked && ! splitOnAxis && ( ) } -
+ { allowReset && ( + + { __( 'Reset' ) } + + ) } + ); } diff --git a/packages/components/src/box-control/input-controls.tsx b/packages/components/src/box-control/input-controls.tsx index f72179f0d18c1..c8aaeae222749 100644 --- a/packages/components/src/box-control/input-controls.tsx +++ b/packages/components/src/box-control/input-controls.tsx @@ -1,82 +1,81 @@ +/** + * WordPress dependencies + */ +import { useInstanceId } from '@wordpress/compose'; /** * Internal dependencies */ -import UnitControl from './unit-control'; +import Tooltip from '../tooltip'; import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; -import { ALL_SIDES, LABELS } from './utils'; -import { LayoutContainer, Layout } from './styles/box-control-styles'; +import { ALL_SIDES, CUSTOM_VALUE_SETTINGS, LABELS } from './utils'; +import { + FlexedBoxControlIcon, + FlexedRangeControl, + InputWrapper, + StyledUnitControl, +} from './styles/box-control-styles'; import type { BoxControlInputControlProps, BoxControlValue } from './types'; -import type { UnitControlProps } from '../unit-control/types'; const noop = () => {}; export default function BoxInputControls( { onChange = noop, onFocus = noop, - onHoverOn = noop, - onHoverOff = noop, values, selectedUnits, setSelectedUnits, sides, ...props }: BoxControlInputControlProps ) { + const generatedId = useInstanceId( BoxInputControls, 'box-control-input' ); + const createHandleOnFocus = ( side: keyof BoxControlValue ) => ( event: React.FocusEvent< HTMLInputElement > ) => { onFocus( event, { side } ); }; - const createHandleOnHoverOn = ( side: keyof BoxControlValue ) => () => { - onHoverOn( { [ side ]: true } ); - }; - - const createHandleOnHoverOff = ( side: keyof BoxControlValue ) => () => { - onHoverOff( { [ side ]: false } ); - }; - const handleOnChange = ( nextValues: BoxControlValue ) => { onChange( nextValues ); }; - const createHandleOnChange: ( - side: keyof BoxControlValue - ) => UnitControlProps[ 'onChange' ] = - ( side ) => - ( next, { event } ) => { - const nextValues = { ...values }; - const isNumeric = - next !== undefined && ! isNaN( parseFloat( next ) ); - const nextValue = isNumeric ? next : undefined; + const handleOnValueChange = ( + side: keyof BoxControlValue, + next?: string, + extra?: { event: React.SyntheticEvent< Element, Event > } + ) => { + const nextValues = { ...values }; + const isNumeric = next !== undefined && ! isNaN( parseFloat( next ) ); + const nextValue = isNumeric ? next : undefined; - nextValues[ side ] = nextValue; + nextValues[ side ] = nextValue; - /** - * Supports changing pair sides. For example, holding the ALT key - * when changing the TOP will also update BOTTOM. - */ - // @ts-expect-error - TODO: event.altKey is only present when the change event was - // triggered by a keyboard event. Should this feature be implemented differently so - // it also works with drag events? - if ( event.altKey ) { - switch ( side ) { - case 'top': - nextValues.bottom = nextValue; - break; - case 'bottom': - nextValues.top = nextValue; - break; - case 'left': - nextValues.right = nextValue; - break; - case 'right': - nextValues.left = nextValue; - break; - } + /** + * Supports changing pair sides. For example, holding the ALT key + * when changing the TOP will also update BOTTOM. + */ + // @ts-expect-error - TODO: event.altKey is only present when the change event was + // triggered by a keyboard event. Should this feature be implemented differently so + // it also works with drag events? + if ( extra?.event.altKey ) { + switch ( side ) { + case 'top': + nextValues.bottom = nextValue; + break; + case 'bottom': + nextValues.top = nextValue; + break; + case 'left': + nextValues.right = nextValue; + break; + case 'right': + nextValues.left = nextValue; + break; } + } - handleOnChange( nextValues ); - }; + handleOnChange( nextValues ); + }; const createHandleOnUnitChange = ( side: keyof BoxControlValue ) => ( next?: string ) => { @@ -90,45 +89,74 @@ export default function BoxInputControls( { ? ALL_SIDES.filter( ( side ) => sides.includes( side ) ) : ALL_SIDES; - const first = filteredSides[ 0 ]; - const last = filteredSides[ filteredSides.length - 1 ]; - const only = first === last && first; - return ( - - - { filteredSides.map( ( side ) => { - const [ parsedQuantity, parsedUnit ] = - parseQuantityAndUnitFromRawValue( values[ side ] ); + <> + { filteredSides.map( ( side ) => { + const [ parsedQuantity, parsedUnit ] = + parseQuantityAndUnitFromRawValue( values[ side ] ); + + const computedUnit = values[ side ] + ? parsedUnit + : selectedUnits[ side ]; + + const inputId = [ generatedId, side ].join( '-' ); - const computedUnit = values[ side ] - ? parsedUnit - : selectedUnits[ side ]; + return ( + + + + + handleOnValueChange( + side, + nextValue, + extra + ) + } + onUnitChange={ createHandleOnUnitChange( + side + ) } + onFocus={ createHandleOnFocus( side ) } + label={ LABELS[ side ] } + hideLabelFromVision + /> + - return ( - { + handleOnValueChange( + side, + newValue !== undefined + ? [ newValue, computedUnit ].join( '' ) + : undefined + ); + } } + min={ 0 } + max={ + CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ] + ?.max ?? 10 + } + step={ + CUSTOM_VALUE_SETTINGS[ computedUnit ?? 'px' ] + ?.step ?? 0.1 + } + value={ parsedQuantity ?? 0 } + withInputField={ false } /> - ); - } ) } - - + + ); + } ) } + ); } diff --git a/packages/components/src/box-control/styles/box-control-styles.ts b/packages/components/src/box-control/styles/box-control-styles.ts index d961d4322ba5a..ce2c8aa227e58 100644 --- a/packages/components/src/box-control/styles/box-control-styles.ts +++ b/packages/components/src/box-control/styles/box-control-styles.ts @@ -1,80 +1,40 @@ /** * External dependencies */ -import { css } from '@emotion/react'; import styled from '@emotion/styled'; /** * Internal dependencies */ -import { Flex } from '../../flex'; -import BaseUnitControl from '../../unit-control'; -import { rtl } from '../../utils'; -import type { BoxUnitControlProps } from '../types'; - -export const Root = styled.div` - box-sizing: border-box; - max-width: 235px; - padding-bottom: 12px; - width: 100%; +import BoxControlIcon from '../icon'; +import Button from '../../button'; +import { HStack } from '../../h-stack'; +import RangeControl from '../../range-control'; +import UnitControl from '../../unit-control'; +import { space } from '../../utils/space'; + +export const StyledUnitControl = styled( UnitControl )` + max-width: 90px; `; -export const Header = styled( Flex )` - margin-bottom: 8px; +export const InputWrapper = styled( HStack )` + grid-column: 1 / span 3; `; -export const HeaderControlWrapper = styled( Flex )` - min-height: 30px; - gap: 0; +export const ResetButton = styled( Button )` + grid-area: 1 / 2; + justify-self: end; `; -export const UnitControlWrapper = styled.div` - box-sizing: border-box; - max-width: 80px; +export const LinkedButtonWrapper = styled.div` + grid-area: 1 / 3; + justify-self: end; `; -export const LayoutContainer = styled( Flex )` - justify-content: center; - padding-top: 8px; +export const FlexedBoxControlIcon = styled( BoxControlIcon )` + flex: 0 0 auto; `; -export const Layout = styled( Flex )` - position: relative; - height: 100%; +export const FlexedRangeControl = styled( RangeControl )` width: 100%; - justify-content: flex-start; -`; - -const unitControlBorderRadiusStyles = ( { - isFirst, - isLast, - isOnly, -}: Pick< BoxUnitControlProps, 'isFirst' | 'isLast' | 'isOnly' > ) => { - if ( isFirst ) { - return rtl( { borderTopRightRadius: 0, borderBottomRightRadius: 0 } )(); - } - if ( isLast ) { - return rtl( { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 } )(); - } - if ( isOnly ) { - return css( { borderRadius: 2 } ); - } - - return css( { - borderRadius: 0, - } ); -}; - -const unitControlMarginStyles = ( { - isFirst, - isOnly, -}: Pick< BoxUnitControlProps, 'isFirst' | 'isOnly' > ) => { - const marginLeft = isFirst || isOnly ? 0 : -1; - - return rtl( { marginLeft } )(); -}; - -export const UnitControl = styled( BaseUnitControl )` - max-width: 60px; - ${ unitControlBorderRadiusStyles }; - ${ unitControlMarginStyles }; + margin-inline-end: ${ space( 2 ) }; `; diff --git a/packages/components/src/box-control/styles/box-control-visualizer-styles.ts b/packages/components/src/box-control/styles/box-control-visualizer-styles.ts deleted file mode 100644 index bbfe66c9a71e9..0000000000000 --- a/packages/components/src/box-control/styles/box-control-visualizer-styles.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * External dependencies - */ -import { css } from '@emotion/react'; -import styled from '@emotion/styled'; - -/** - * Internal dependencies - */ -import { COLORS, rtl } from '../../utils'; - -const containerPositionStyles = ( { - isPositionAbsolute, -}: { - isPositionAbsolute: boolean; -} ) => { - if ( ! isPositionAbsolute ) return ''; - - return css` - bottom: 0; - left: 0; - pointer-events: none; - position: absolute; - right: 0; - top: 0; - z-index: 1; - `; -}; - -export const Container = styled.div` - box-sizing: border-box; - position: relative; - ${ containerPositionStyles }; -`; - -export const Side = styled.div` - box-sizing: border-box; - background: ${ COLORS.theme.accent }; - filter: brightness( 1 ); - opacity: 0; - position: absolute; - pointer-events: none; - transition: opacity 120ms linear; - z-index: 1; - - ${ ( { isActive }: { isActive: boolean } ) => - isActive && - ` - opacity: 0.3; - ` } -`; - -export const TopView = styled( Side )` - top: 0; - left: 0; - right: 0; -`; - -export const RightView = styled( Side )` - top: 0; - bottom: 0; - ${ rtl( { right: 0 } ) }; -`; - -export const BottomView = styled( Side )` - bottom: 0; - left: 0; - right: 0; -`; - -export const LeftView = styled( Side )` - top: 0; - bottom: 0; - ${ rtl( { left: 0 } ) }; -`; diff --git a/packages/components/src/box-control/test/index.tsx b/packages/components/src/box-control/test/index.tsx index 8a861eff37e1b..1ea3c84aae922 100644 --- a/packages/components/src/box-control/test/index.tsx +++ b/packages/components/src/box-control/test/index.tsx @@ -1,7 +1,7 @@ /** * External dependencies */ -import { render, screen } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; /** @@ -33,7 +33,10 @@ describe( 'BoxControl', () => { render( {} } /> ); expect( - screen.getByRole( 'textbox', { name: 'Box Control' } ) + screen.getByRole( 'group', { name: 'Box Control' } ) + ).toBeVisible(); + expect( + screen.getByRole( 'textbox', { name: 'All sides' } ) ).toBeVisible(); } ); @@ -42,15 +45,41 @@ describe( 'BoxControl', () => { render( {} } /> ); - const input = screen.getByRole( 'textbox', { - name: 'Box Control', - } ); + const input = screen.getByRole( 'textbox', { name: 'All sides' } ); await user.type( input, '100' ); await user.keyboard( '{Enter}' ); expect( input ).toHaveValue( '100' ); } ); + + it( 'should update input values when interacting with slider', () => { + render( {} } /> ); + + const slider = screen.getByRole( 'slider' ); + + fireEvent.change( slider, { target: { value: 50 } } ); + + expect( slider ).toHaveValue( '50' ); + expect( + screen.getByRole( 'textbox', { name: 'All sides' } ) + ).toHaveValue( '50' ); + } ); + + it( 'should update slider values when interacting with input', async () => { + const user = userEvent.setup(); + render( {} } /> ); + + const input = screen.getByRole( 'textbox', { + name: 'All sides', + } ); + + await user.type( input, '50' ); + await user.keyboard( '{Enter}' ); + + expect( input ).toHaveValue( '50' ); + expect( screen.getByRole( 'slider' ) ).toHaveValue( '50' ); + } ); } ); describe( 'Reset', () => { @@ -60,7 +89,7 @@ describe( 'BoxControl', () => { render( {} } /> ); const input = screen.getByRole( 'textbox', { - name: 'Box Control', + name: 'All sides', } ); await user.type( input, '100' ); @@ -79,7 +108,7 @@ describe( 'BoxControl', () => { render( ); const input = screen.getByRole( 'textbox', { - name: 'Box Control', + name: 'All sides', } ); await user.type( input, '100' ); @@ -98,7 +127,7 @@ describe( 'BoxControl', () => { render( ); const input = screen.getByRole( 'textbox', { - name: 'Box Control', + name: 'All sides', } ); await user.type( input, '100' ); @@ -118,7 +147,7 @@ describe( 'BoxControl', () => { render( spyChange( v ) } /> ); const input = screen.getByRole( 'textbox', { - name: 'Box Control', + name: 'All sides', } ); await user.type( input, '100' ); @@ -152,21 +181,49 @@ describe( 'BoxControl', () => { ); await user.type( - screen.getByRole( 'textbox', { name: 'Top' } ), + screen.getByRole( 'textbox', { name: 'Top side' } ), '100' ); expect( - screen.getByRole( 'textbox', { name: 'Top' } ) + screen.getByRole( 'textbox', { name: 'Top side' } ) ).toHaveValue( '100' ); expect( - screen.getByRole( 'textbox', { name: 'Right' } ) + screen.getByRole( 'textbox', { name: 'Right side' } ) ).not.toHaveValue(); expect( - screen.getByRole( 'textbox', { name: 'Bottom' } ) + screen.getByRole( 'textbox', { name: 'Bottom side' } ) ).not.toHaveValue(); expect( - screen.getByRole( 'textbox', { name: 'Left' } ) + screen.getByRole( 'textbox', { name: 'Left side' } ) + ).not.toHaveValue(); + } ); + + it( 'should update a single side value when using slider unlinked', async () => { + const user = userEvent.setup(); + + render( ); + + await user.click( + screen.getByRole( 'button', { name: 'Unlink sides' } ) + ); + + const slider = screen.getByRole( 'slider', { name: 'Right side' } ); + + fireEvent.change( slider, { target: { value: 50 } } ); + + expect( slider ).toHaveValue( '50' ); + expect( + screen.getByRole( 'textbox', { name: 'Top side' } ) + ).not.toHaveValue(); + expect( + screen.getByRole( 'textbox', { name: 'Right side' } ) + ).toHaveValue( '50' ); + expect( + screen.getByRole( 'textbox', { name: 'Bottom side' } ) + ).not.toHaveValue(); + expect( + screen.getByRole( 'textbox', { name: 'Left side' } ) ).not.toHaveValue(); } ); @@ -181,17 +238,68 @@ describe( 'BoxControl', () => { await user.type( screen.getByRole( 'textbox', { - name: 'Vertical', + name: 'Top and bottom sides', } ), '100' ); expect( - screen.getByRole( 'textbox', { name: 'Vertical' } ) + screen.getByRole( 'textbox', { name: 'Top and bottom sides' } ) ).toHaveValue( '100' ); expect( - screen.getByRole( 'textbox', { name: 'Horizontal' } ) + screen.getByRole( 'textbox', { name: 'Left and right sides' } ) + ).not.toHaveValue(); + } ); + + it( 'should update a whole axis using a slider when value is changed when unlinked', async () => { + const user = userEvent.setup(); + + render( ); + + await user.click( + screen.getByRole( 'button', { name: 'Unlink sides' } ) + ); + + const slider = screen.getByRole( 'slider', { + name: 'Left and right sides', + } ); + + fireEvent.change( slider, { target: { value: 50 } } ); + + expect( slider ).toHaveValue( '50' ); + expect( + screen.getByRole( 'textbox', { name: 'Top and bottom sides' } ) ).not.toHaveValue(); + expect( + screen.getByRole( 'textbox', { name: 'Left and right sides' } ) + ).toHaveValue( '50' ); + } ); + + it( 'should show "Mixed" label when sides have different values but are linked', async () => { + const user = userEvent.setup(); + + render( ); + + const unlinkButton = screen.getByRole( 'button', { + name: 'Unlink sides', + } ); + + await user.click( unlinkButton ); + + await user.type( + screen.getByRole( 'textbox', { + name: 'Right side', + } ), + '13' + ); + + expect( + screen.getByRole( 'textbox', { name: 'Right side' } ) + ).toHaveValue( '13' ); + + await user.click( unlinkButton ); + + expect( screen.getByPlaceholderText( 'Mixed' ) ).toHaveValue( '' ); } ); } ); @@ -287,7 +395,7 @@ describe( 'BoxControl', () => { render( ); const valueInput = screen.getByRole( 'textbox', { - name: 'Box Control', + name: 'All sides', } ); const unitSelect = screen.getByRole( 'combobox', { name: 'Select unit', diff --git a/packages/components/src/box-control/types.ts b/packages/components/src/box-control/types.ts index 0eba0e58fd33c..12524559564ab 100644 --- a/packages/components/src/box-control/types.ts +++ b/packages/components/src/box-control/types.ts @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import type { useHover } from '@use-gesture/react'; - /** * Internal dependencies */ @@ -16,6 +11,10 @@ export type BoxControlValue = { left?: string; }; +export type CustomValueUnits = { + [ key: string ]: { max: number; step: number }; +}; + type UnitControlPassthroughProps = Omit< UnitControlProps, 'label' | 'onChange' | 'onFocus' | 'onMouseOver' | 'onMouseOut' | 'units' @@ -92,22 +91,6 @@ export type BoxControlInputControlProps = UnitControlPassthroughProps & { values: BoxControlValue; }; -export type BoxUnitControlProps = UnitControlPassthroughProps & - Pick< UnitControlProps, 'onChange' | 'onFocus' > & { - isFirst?: boolean; - isLast?: boolean; - isOnly?: boolean; - label?: string; - onHoverOff?: ( - event: ReturnType< typeof useHover >[ 'event' ], - state: Omit< ReturnType< typeof useHover >, 'event' > - ) => void; - onHoverOn?: ( - event: ReturnType< typeof useHover >[ 'event' ], - state: Omit< ReturnType< typeof useHover >, 'event' > - ) => void; - }; - export type BoxControlIconProps = { /** * @default 24 diff --git a/packages/components/src/box-control/unit-control.tsx b/packages/components/src/box-control/unit-control.tsx deleted file mode 100644 index 24d71cf0d6cd3..0000000000000 --- a/packages/components/src/box-control/unit-control.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/** - * External dependencies - */ -import { useHover } from '@use-gesture/react'; - -/** - * Internal dependencies - */ -import BaseTooltip from '../tooltip'; -import { UnitControlWrapper, UnitControl } from './styles/box-control-styles'; -import type { BoxUnitControlProps } from './types'; - -const noop = () => {}; - -export default function BoxUnitControl( { - isFirst, - isLast, - isOnly, - onHoverOn = noop, - onHoverOff = noop, - label, - value, - ...props -}: BoxUnitControlProps ) { - const bindHoverGesture = useHover( ( { event, ...state } ) => { - if ( state.hovering ) { - onHoverOn( event, state ); - } else { - onHoverOff( event, state ); - } - } ); - - return ( - - - - - - ); -} - -function Tooltip( { - children, - text, -}: { - children: JSX.Element; - text?: string; -} ) { - if ( ! text ) return children; - - /** - * Wrapping the children in a `
` as Tooltip as it attempts - * to render the . Using a plain `
` appears to - * resolve this issue. - * - * Originally discovered and referenced here: - * https://github.com/WordPress/gutenberg/pull/24966#issuecomment-685875026 - */ - return ( - -
{ children }
-
- ); -} diff --git a/packages/components/src/box-control/utils.ts b/packages/components/src/box-control/utils.ts index 6614342d3da6d..e480c9a9f4674 100644 --- a/packages/components/src/box-control/utils.ts +++ b/packages/components/src/box-control/utils.ts @@ -7,17 +7,52 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import { parseQuantityAndUnitFromRawValue } from '../unit-control/utils'; -import type { BoxControlProps, BoxControlValue } from './types'; +import type { + BoxControlProps, + BoxControlValue, + CustomValueUnits, +} from './types'; + +export const CUSTOM_VALUE_SETTINGS: CustomValueUnits = { + px: { max: 300, step: 1 }, + '%': { max: 100, step: 1 }, + vw: { max: 100, step: 1 }, + vh: { max: 100, step: 1 }, + em: { max: 10, step: 0.1 }, + rm: { max: 10, step: 0.1 }, + svw: { max: 100, step: 1 }, + lvw: { max: 100, step: 1 }, + dvw: { max: 100, step: 1 }, + svh: { max: 100, step: 1 }, + lvh: { max: 100, step: 1 }, + dvh: { max: 100, step: 1 }, + vi: { max: 100, step: 1 }, + svi: { max: 100, step: 1 }, + lvi: { max: 100, step: 1 }, + dvi: { max: 100, step: 1 }, + vb: { max: 100, step: 1 }, + svb: { max: 100, step: 1 }, + lvb: { max: 100, step: 1 }, + dvb: { max: 100, step: 1 }, + vmin: { max: 100, step: 1 }, + svmin: { max: 100, step: 1 }, + lvmin: { max: 100, step: 1 }, + dvmin: { max: 100, step: 1 }, + vmax: { max: 100, step: 1 }, + svmax: { max: 100, step: 1 }, + lvmax: { max: 100, step: 1 }, + dvmax: { max: 100, step: 1 }, +}; export const LABELS = { - all: __( 'All' ), - top: __( 'Top' ), - bottom: __( 'Bottom' ), - left: __( 'Left' ), - right: __( 'Right' ), + all: __( 'All sides' ), + top: __( 'Top side' ), + bottom: __( 'Bottom side' ), + left: __( 'Left side' ), + right: __( 'Right side' ), mixed: __( 'Mixed' ), - vertical: __( 'Vertical' ), - horizontal: __( 'Horizontal' ), + vertical: __( 'Top and bottom sides' ), + horizontal: __( 'Left and right sides' ), }; export const DEFAULT_VALUES = { From 876bf2bd41957ecf02abf45b68e865cfe29d0215 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:09:20 +0900 Subject: [PATCH 074/296] Remove right negative margin from pinned items (#57666) --- packages/interface/src/components/pinned-items/style.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/interface/src/components/pinned-items/style.scss b/packages/interface/src/components/pinned-items/style.scss index 66062b7fa3dbb..420b94e2994b1 100644 --- a/packages/interface/src/components/pinned-items/style.scss +++ b/packages/interface/src/components/pinned-items/style.scss @@ -27,7 +27,4 @@ // Gap between pinned items. gap: $grid-unit-10; - - // Account for larger grid from parent container gap. - margin-right: -$grid-unit-05; } From d0287db174947e81fa4cb7350daf8303fa01b609 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:18:59 +0900 Subject: [PATCH 075/296] PaletteEdit: improve unit tests (#57645) * PaletteEdit: improve unit tests * Update changelog * Use toBeVisible instead of toBeInTheDocument Co-authored-by: Lena Morita * Update test name Co-authored-by: Lena Morita * Update test name Co-authored-by: Lena Morita * Update test name Co-authored-by: Lena Morita * Update test name Co-authored-by: Lena Morita * Update test name Co-authored-by: Lena Morita * Remove Unnecessary uerr click event Co-authored-by: Lena Morita * Use toBeVisible instead of toBeInTheDocument * use a fresh onChange mock for each test --------- Co-authored-by: Lena Morita --- packages/components/CHANGELOG.md | 1 + .../src/palette-edit/test/index.tsx | 329 +++++++++++++++++- 2 files changed, 320 insertions(+), 10 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 646fc6eaa9dd0..022248e3706cb 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -34,6 +34,7 @@ - `ToggleGroupControl`: Update button size in large variant to be 32px ([#57338](https://github.com/WordPress/gutenberg/pull/57338)). - `Tooltip`: improve unit tests ([#57345](https://github.com/WordPress/gutenberg/pull/57345)). - `Tooltip`: no-op when nested inside other `Tooltip` components ([#57202](https://github.com/WordPress/gutenberg/pull/57202)). +- `PaletteEdit`: improve unit tests ([#57645](https://github.com/WordPress/gutenberg/pull/57645)). ### Experimental diff --git a/packages/components/src/palette-edit/test/index.tsx b/packages/components/src/palette-edit/test/index.tsx index 1bf2802709de7..31b712225a8dc 100644 --- a/packages/components/src/palette-edit/test/index.tsx +++ b/packages/components/src/palette-edit/test/index.tsx @@ -1,7 +1,8 @@ /** * External dependencies */ -import { render, fireEvent, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * Internal dependencies @@ -9,6 +10,8 @@ import { render, fireEvent, screen } from '@testing-library/react'; import PaletteEdit, { getNameForPosition } from '..'; import type { PaletteElement } from '../types'; +const noop = () => {}; + describe( 'getNameForPosition', () => { test( 'should return 1 by default', () => { const slugPrefix = 'test-'; @@ -82,18 +85,324 @@ describe( 'getNameForPosition', () => { describe( 'PaletteEdit', () => { const defaultProps = { - colors: [ { color: '#ffffff', name: 'Base', slug: 'base' } ], - onChange: jest.fn(), paletteLabel: 'Test label', - emptyMessage: 'Test empty message', - canOnlyChangeValues: true, - canReset: true, slugPrefix: '', + onChange: noop, }; - it( 'opens color selector for color palettes', () => { - render( ); - fireEvent.click( screen.getByLabelText( 'Color: Base' ) ); - expect( screen.getByLabelText( 'Hex color' ) ).toBeInTheDocument(); + const colors = [ + { color: '#1a4548', name: 'Primary', slug: 'primary' }, + { color: '#0000ff', name: 'Secondary', slug: 'secondary' }, + ]; + const gradients = [ + { + gradient: + 'linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)', + name: 'Pale ocean', + slug: 'pale-ocean', + }, + { + gradient: + 'linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%)', + name: 'Midnight', + slug: 'midnight', + }, + ]; + + it( 'shows heading label', () => { + render( ); + + const paletteLabel = screen.getByRole( 'heading', { + level: 2, + name: 'Test label', + } ); + + expect( paletteLabel ).toBeVisible(); + } ); + + it( 'shows heading label with custom heading level', () => { + render( + + ); + + expect( + screen.getByRole( 'heading', { + level: 5, + name: 'Test label', + } ) + ).toBeVisible(); + } ); + + it( 'shows empty message', () => { + render( + + ); + + expect( screen.getByText( 'Test empty message' ) ).toBeVisible(); + } ); + + it( 'shows an option to remove all colors', async () => { + const user = userEvent.setup(); + render( ); + + await user.click( + screen.getByRole( 'button', { + name: 'Color options', + } ) + ); + + expect( + screen.getByRole( 'button', { + name: 'Remove all colors', + } ) + ).toBeVisible(); + } ); + + it( 'shows a reset option when the `canReset` prop is enabled', async () => { + const user = userEvent.setup(); + render( + + ); + + await user.click( + screen.getByRole( 'button', { + name: 'Color options', + } ) + ); + expect( + screen.getByRole( 'button', { + name: 'Reset colors', + } ) + ).toBeVisible(); + } ); + + it( 'does not show a reset colors option when `canReset` is disabled', async () => { + const user = userEvent.setup(); + render( ); + + await user.click( + screen.getByRole( 'button', { + name: 'Color options', + } ) + ); + expect( + screen.queryByRole( 'button', { + name: 'Reset colors', + } ) + ).not.toBeInTheDocument(); + } ); + + it( 'calls the `onChange` with the new color appended', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render( + + ); + + await user.click( + screen.getByRole( 'button', { + name: 'Add color', + } ) + ); + + await waitFor( () => { + expect( onChange ).toHaveBeenCalledWith( [ + ...colors, + { + color: '#000', + name: 'Color 1', + slug: 'color-1', + }, + ] ); + } ); + } ); + + it( 'calls the `onChange` with the new gradient appended', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render( + + ); + + await user.click( + screen.getByRole( 'button', { + name: 'Add gradient', + } ) + ); + + await waitFor( () => { + expect( onChange ).toHaveBeenCalledWith( [ + ...gradients, + { + gradient: + 'linear-gradient(135deg, rgba(6, 147, 227, 1) 0%, rgb(155, 81, 224) 100%)', + name: 'Color 1', + slug: 'color-1', + }, + ] ); + } ); + } ); + + it( 'can not add new colors when `canOnlyChangeValues` is enabled', () => { + render( ); + + expect( + screen.queryByRole( 'button', { + name: 'Add color', + } ) + ).not.toBeInTheDocument(); + } ); + + it( 'can remove a color', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render( + + ); + + await user.click( + screen.getByRole( 'button', { + name: 'Color options', + } ) + ); + await user.click( + screen.getByRole( 'button', { + name: 'Show details', + } ) + ); + await user.click( screen.getByText( 'Primary' ) ); + await user.click( + screen.getByRole( 'button', { + name: 'Remove color', + } ) + ); + + await waitFor( () => { + expect( onChange ).toHaveBeenCalledWith( [ colors[ 1 ] ] ); + } ); + } ); + + it( 'can update palette name', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render( + + ); + + await user.click( + screen.getByRole( 'button', { + name: 'Color options', + } ) + ); + await user.click( + screen.getByRole( 'button', { + name: 'Show details', + } ) + ); + await user.click( screen.getByText( 'Primary' ) ); + const nameInput = screen.getByRole( 'textbox', { + name: 'Color name', + } ); + await user.clear( nameInput ); + await user.type( nameInput, 'Primary Updated' ); + + await waitFor( () => { + expect( onChange ).toHaveBeenCalledWith( [ + { + ...colors[ 0 ], + name: 'Primary Updated', + slug: 'primary-updated', + }, + colors[ 1 ], + ] ); + } ); + } ); + + it( 'can update color palette value', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render( + + ); + + await user.click( screen.getByLabelText( 'Color: Primary' ) ); + const hexInput = screen.getByRole( 'textbox', { + name: 'Hex color', + } ); + await user.clear( hexInput ); + await user.type( hexInput, '000000' ); + + await waitFor( () => { + expect( onChange ).toHaveBeenCalledWith( [ + { + ...colors[ 0 ], + color: '#000000', + }, + colors[ 1 ], + ] ); + } ); + } ); + + it( 'can update gradient palette value', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render( + + ); + + await user.click( screen.getByLabelText( 'Gradient: Pale ocean' ) ); + + const typeSelectElement = screen.getByRole( 'combobox', { + name: 'Type', + } ); + await user.selectOptions( typeSelectElement, 'radial-gradient' ); + + await waitFor( () => { + expect( onChange ).toHaveBeenCalledWith( [ + { + ...gradients[ 0 ], + gradient: + 'radial-gradient(rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%)', + }, + gradients[ 1 ], + ] ); + } ); } ); } ); From eec8951360a8e4a5205f592a972cb32628cd2002 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Fri, 12 Jan 2024 11:32:38 +0400 Subject: [PATCH 076/296] Migrate 'typewriter' e2e tests to Playwright (#57673) * Migrate 'typewriter' e2e tests to Playwright * Remove old test file --- .../specs/editor/various/typewriter.spec.js | 169 +++++++++++------- 1 file changed, 106 insertions(+), 63 deletions(-) rename packages/e2e-tests/specs/editor/various/typewriter.test.js => test/e2e/specs/editor/various/typewriter.spec.js (58%) diff --git a/packages/e2e-tests/specs/editor/various/typewriter.test.js b/test/e2e/specs/editor/various/typewriter.spec.js similarity index 58% rename from packages/e2e-tests/specs/editor/various/typewriter.test.js rename to test/e2e/specs/editor/various/typewriter.spec.js index d935197b14f87..abf24cbfc298e 100644 --- a/packages/e2e-tests/specs/editor/various/typewriter.test.js +++ b/test/e2e/specs/editor/various/typewriter.spec.js @@ -1,40 +1,40 @@ /** * WordPress dependencies */ -import { createNewPost } from '@wordpress/e2e-test-utils'; +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); -describe( 'TypeWriter', () => { - beforeEach( async () => { - await createNewPost(); - } ); +/** @typedef {import('@playwright/test').Page} Page */ - const getCaretPosition = async () => - await page.evaluate( - () => - wp.dom.computeCaretRect( - document.activeElement?.contentWindow ?? window - ).y - ); +// Allow the scroll position to be 1px off. +const BUFFER = 1; - // Allow the scroll position to be 1px off. - const BUFFER = 1; +test.use( { + typewriterUtils: async ( { page }, use ) => { + await use( new TypewriterUtils( { page } ) ); + }, +} ); - const getDiff = async ( caretPosition ) => - Math.abs( ( await getCaretPosition() ) - caretPosition ); +test.describe( 'Typewriter', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); - it( 'should maintain caret position', async () => { - // Create first block. + test( 'should maintain caret position', async ( { + page, + typewriterUtils, + } ) => { + // Create test blocks. await page.keyboard.press( 'Enter' ); - - // Create second block. await page.keyboard.press( 'Enter' ); - const initialPosition = await getCaretPosition(); + const initialPosition = await typewriterUtils.getCaretPosition(); // The page shouldn't be scrolled when it's being filled. await page.keyboard.press( 'Enter' ); - expect( await getCaretPosition() ).toBeGreaterThan( initialPosition ); + expect( await typewriterUtils.getCaretPosition() ).toBeGreaterThan( + initialPosition + ); // Create blocks until the typewriter effect kicks in. while ( @@ -42,19 +42,22 @@ describe( 'TypeWriter', () => { const { activeElement } = document.activeElement?.contentDocument ?? document; return ( - wp.dom.getScrollContainer( activeElement ).scrollTop === 0 + window.wp.dom.getScrollContainer( activeElement ) + .scrollTop === 0 ); } ) ) { await page.keyboard.press( 'Enter' ); } - const newPosition = await getCaretPosition(); + const newPosition = await typewriterUtils.getCaretPosition(); // Now the scroll position should be maintained. await page.keyboard.press( 'Enter' ); - expect( await getDiff( newPosition ) ).toBeLessThanOrEqual( BUFFER ); + expect( + await typewriterUtils.getDiff( newPosition ) + ).toBeLessThanOrEqual( BUFFER ); // Type until the text wraps. while ( @@ -63,37 +66,47 @@ describe( 'TypeWriter', () => { document.activeElement?.contentDocument ?? document; return ( activeElement.clientHeight <= - parseInt( getComputedStyle( activeElement ).lineHeight, 10 ) + parseInt( + window.getComputedStyle( activeElement ).lineHeight, + 10 + ) ); } ) ) { await page.keyboard.type( 'a' ); } - expect( await getDiff( newPosition ) ).toBeLessThanOrEqual( BUFFER ); + expect( + await typewriterUtils.getDiff( newPosition ) + ).toBeLessThanOrEqual( BUFFER ); // Pressing backspace will reposition the caret to the previous line. // Scroll position should be adjusted again. await page.keyboard.press( 'Backspace' ); - expect( await getDiff( newPosition ) ).toBeLessThanOrEqual( BUFFER ); + expect( + await typewriterUtils.getDiff( newPosition ) + ).toBeLessThanOrEqual( BUFFER ); // Should reset scroll position to maintain. await page.keyboard.press( 'ArrowUp' ); - const positionAfterArrowUp = await getCaretPosition(); + const positionAfterArrowUp = await typewriterUtils.getCaretPosition(); expect( positionAfterArrowUp ).toBeLessThanOrEqual( newPosition ); // Should be scrolled to new position. await page.keyboard.press( 'Enter' ); - expect( await getDiff( positionAfterArrowUp ) ).toBeLessThanOrEqual( - BUFFER - ); + expect( + await typewriterUtils.getDiff( positionAfterArrowUp ) + ).toBeLessThanOrEqual( BUFFER ); } ); - it( 'should maintain caret position after scroll', async () => { + test( 'should maintain caret position after scroll', async ( { + page, + typewriterUtils, + } ) => { // Create first block. await page.keyboard.press( 'Enter' ); @@ -104,7 +117,7 @@ describe( 'TypeWriter', () => { const { activeElement } = document.activeElement?.contentDocument ?? document; const scrollContainer = - wp.dom.getScrollContainer( activeElement ); + window.wp.dom.getScrollContainer( activeElement ); return ( scrollContainer.scrollHeight === scrollContainer.clientHeight @@ -117,8 +130,9 @@ describe( 'TypeWriter', () => { const scrollPosition = await page.evaluate( () => { const { activeElement } = document.activeElement?.contentDocument ?? document; - return wp.dom.getScrollContainer( activeElement ).scrollTop; + return window.wp.dom.getScrollContainer( activeElement ).scrollTop; } ); + // Expect scrollbar to be at the top. expect( scrollPosition ).toBe( 0 ); @@ -127,7 +141,7 @@ describe( 'TypeWriter', () => { await page.evaluate( () => { const { activeElement } = document.activeElement?.contentDocument ?? document; - wp.dom.getScrollContainer( activeElement ).scrollTop += 2; + window.wp.dom.getScrollContainer( activeElement ).scrollTop += 2; } ); // Wait for the caret rectangle to be recalculated. await page.evaluate( @@ -136,39 +150,44 @@ describe( 'TypeWriter', () => { // After hitting Enter to create a new block, the caret screen // coordinates should be the same. - const initialPosition = await getCaretPosition(); + const initialPosition = await typewriterUtils.getCaretPosition(); await page.keyboard.press( 'Enter' ); await page.waitForFunction( () => { const { activeElement } = document.activeElement?.contentDocument ?? document; // Wait for the Typewriter to scroll down past the initial position. - return wp.dom.getScrollContainer( activeElement ).scrollTop > 2; + return ( + window.wp.dom.getScrollContainer( activeElement ).scrollTop > 2 + ); } ); - expect( await getDiff( initialPosition ) ).toBe( 0 ); + + expect( await typewriterUtils.getDiff( initialPosition ) ).toBe( 0 ); } ); - it( 'should maintain caret position after leaving last editable', async () => { - // Create first block. + test( 'should maintain caret position after leaving last editable', async ( { + page, + typewriterUtils, + } ) => { + // Create test blocks. await page.keyboard.press( 'Enter' ); - // Create second block. await page.keyboard.press( 'Enter' ); - // Create third block. await page.keyboard.press( 'Enter' ); // Move to first block. await page.keyboard.press( 'ArrowUp' ); await page.keyboard.press( 'ArrowUp' ); - const initialPosition = await getCaretPosition(); + const initialPosition = await typewriterUtils.getCaretPosition(); // Should maintain scroll position. - await page.keyboard.press( 'Enter' ); - - expect( await getDiff( initialPosition ) ).toBeLessThanOrEqual( - BUFFER - ); + expect( + await typewriterUtils.getDiff( initialPosition ) + ).toBeLessThanOrEqual( BUFFER ); } ); - it( 'should scroll caret into view from the top', async () => { + test( 'should scroll caret into view from the top', async ( { + page, + typewriterUtils, + } ) => { // Create first block. await page.keyboard.press( 'Enter' ); @@ -177,7 +196,7 @@ describe( 'TypeWriter', () => { await page.evaluate( () => { const { activeElement } = document.activeElement?.contentDocument ?? document; - return ! wp.dom.getScrollContainer( activeElement ); + return ! window.wp.dom.getScrollContainer( activeElement ); } ) ) { await page.keyboard.press( 'Enter' ); @@ -186,13 +205,14 @@ describe( 'TypeWriter', () => { let count = 0; // Create blocks until the typewriter effect kicks in, create at - // least 10 blocks to properly test the . + // least 10 blocks to properly test it. while ( ( await page.evaluate( () => { const { activeElement } = document.activeElement?.contentDocument ?? document; return ( - wp.dom.getScrollContainer( activeElement ).scrollTop === 0 + window.wp.dom.getScrollContainer( activeElement ) + .scrollTop === 0 ); } ) ) || count < 10 @@ -207,25 +227,25 @@ describe( 'TypeWriter', () => { const { activeElement } = document.activeElement?.contentDocument ?? document; activeElement.scrollIntoView( false ); - wp.dom.getScrollContainer( activeElement ).scrollTop -= + window.wp.dom.getScrollContainer( activeElement ).scrollTop -= activeElement.offsetHeight + 10; } ); - const bottomPostition = await getCaretPosition(); + const bottomPostition = await typewriterUtils.getCaretPosition(); // Should scroll the caret back into view (preserve browser behaviour). await page.keyboard.type( 'a' ); - const newBottomPosition = await getCaretPosition(); + const newBottomPosition = await typewriterUtils.getCaretPosition(); expect( newBottomPosition ).toBeLessThanOrEqual( bottomPostition ); // Should maintain new caret position. await page.keyboard.press( 'Enter' ); - expect( await getDiff( newBottomPosition ) ).toBeLessThanOrEqual( - BUFFER - ); + expect( + await typewriterUtils.getDiff( newBottomPosition ) + ).toBeLessThanOrEqual( BUFFER ); await page.keyboard.press( 'Backspace' ); @@ -239,22 +259,45 @@ describe( 'TypeWriter', () => { const { activeElement } = document.activeElement?.contentDocument ?? document; activeElement.scrollIntoView(); - wp.dom.getScrollContainer( activeElement ).scrollTop += + window.wp.dom.getScrollContainer( activeElement ).scrollTop += activeElement.offsetHeight + 10; } ); - const topPostition = await getCaretPosition(); + const topPostition = await typewriterUtils.getCaretPosition(); // Should scroll the caret back into view (preserve browser behaviour). await page.keyboard.type( 'a' ); - const newTopPosition = await getCaretPosition(); + const newTopPosition = await typewriterUtils.getCaretPosition(); expect( newTopPosition ).toBeGreaterThan( topPostition ); // Should maintain new caret position. await page.keyboard.press( 'Enter' ); - expect( await getDiff( newTopPosition ) ).toBeLessThanOrEqual( BUFFER ); + expect( + await typewriterUtils.getDiff( newTopPosition ) + ).toBeLessThanOrEqual( BUFFER ); } ); } ); + +class TypewriterUtils { + /** @type {Page} */ + #page; + + constructor( { page } ) { + this.#page = page; + } + + async getCaretPosition() { + return await this.#page.evaluate( () => { + return window.wp.dom.computeCaretRect( + document.activeElement?.contentWindow ?? window + ).y; + } ); + } + + async getDiff( caretPosition ) { + return Math.abs( ( await this.getCaretPosition() ) - caretPosition ); + } +} From d6aaa3f1e320238ac6f5ca0b02a097911461f151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:07:26 +0100 Subject: [PATCH 077/296] DataViews: show loading / no result message for the list layout (#57764) --- packages/dataviews/src/view-list.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/dataviews/src/view-list.js b/packages/dataviews/src/view-list.js index 70755c4aa35c5..df4297334d178 100644 --- a/packages/dataviews/src/view-list.js +++ b/packages/dataviews/src/view-list.js @@ -20,6 +20,7 @@ export default function ViewList( { view, fields, data, + isLoading, getItemId, onSelectionChange, onDetailsChange, @@ -49,6 +50,22 @@ export default function ViewList( { } }; + const hasData = usedData?.length; + if ( ! hasData ) { + return ( +
+ { ! hasData && ( +

{ isLoading ? __( 'Loading…' ) : __( 'No results' ) }

+ ) } +
+ ); + } + return (
    { usedData.map( ( item ) => { From 44ba13d4c26cb6e9dfea65d92530414345dad00a Mon Sep 17 00:00:00 2001 From: arthur791004 Date: Fri, 12 Jan 2024 16:16:46 +0800 Subject: [PATCH 078/296] Live Preview: Show the current theme name on the theme activation modal (#57588) * Live Preview: Show the current theme name on the theme activation modal * Handle error * Update comments * Add missing period --- .../src/middlewares/theme-preview.js | 37 +++++++++++-------- .../src/components/save-panel/index.js | 25 +++++++------ .../src/utils/use-actual-current-theme.js | 27 ++++++++++++++ 3 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 packages/edit-site/src/utils/use-actual-current-theme.js diff --git a/packages/api-fetch/src/middlewares/theme-preview.js b/packages/api-fetch/src/middlewares/theme-preview.js index 8ab21fc328476..56098c88cb351 100644 --- a/packages/api-fetch/src/middlewares/theme-preview.js +++ b/packages/api-fetch/src/middlewares/theme-preview.js @@ -1,32 +1,39 @@ /** * WordPress dependencies */ -import { addQueryArgs, hasQueryArg } from '@wordpress/url'; +import { addQueryArgs, getQueryArg, removeQueryArgs } from '@wordpress/url'; /** * This appends a `wp_theme_preview` parameter to the REST API request URL if * the admin URL contains a `theme` GET parameter. * + * If the REST API request URL has contained the `wp_theme_preview` parameter as `''`, + * then bypass this middleware. + * * @param {Record} themePath * @return {import('../types').APIFetchMiddleware} Preloading middleware. */ const createThemePreviewMiddleware = ( themePath ) => ( options, next ) => { - if ( - typeof options.url === 'string' && - ! hasQueryArg( options.url, 'wp_theme_preview' ) - ) { - options.url = addQueryArgs( options.url, { - wp_theme_preview: themePath, - } ); + if ( typeof options.url === 'string' ) { + const wpThemePreview = getQueryArg( options.url, 'wp_theme_preview' ); + if ( wpThemePreview === undefined ) { + options.url = addQueryArgs( options.url, { + wp_theme_preview: themePath, + } ); + } else if ( wpThemePreview === '' ) { + options.url = removeQueryArgs( options.url, 'wp_theme_preview' ); + } } - if ( - typeof options.path === 'string' && - ! hasQueryArg( options.path, 'wp_theme_preview' ) - ) { - options.path = addQueryArgs( options.path, { - wp_theme_preview: themePath, - } ); + if ( typeof options.path === 'string' ) { + const wpThemePreview = getQueryArg( options.path, 'wp_theme_preview' ); + if ( wpThemePreview === undefined ) { + options.path = addQueryArgs( options.path, { + wp_theme_preview: themePath, + } ); + } else if ( wpThemePreview === '' ) { + options.path = removeQueryArgs( options.path, 'wp_theme_preview' ); + } } return next( options ); diff --git a/packages/edit-site/src/components/save-panel/index.js b/packages/edit-site/src/components/save-panel/index.js index 5c1bcc1df281e..2840191b4849f 100644 --- a/packages/edit-site/src/components/save-panel/index.js +++ b/packages/edit-site/src/components/save-panel/index.js @@ -23,10 +23,8 @@ import { store as coreStore } from '@wordpress/core-data'; import { store as editSiteStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { useActivateTheme } from '../../utils/use-activate-theme'; -import { - currentlyPreviewingTheme, - isPreviewingTheme, -} from '../../utils/is-previewing-theme'; +import { useActualCurrentTheme } from '../../utils/use-actual-current-theme'; +import { isPreviewingTheme } from '../../utils/is-previewing-theme'; const { EntitiesSavedStatesExtensible } = unlock( privateApis ); @@ -39,19 +37,22 @@ const EntitiesSavedStatesForPreview = ( { onClose } ) => { activateSaveLabel = __( 'Activate' ); } - const themeName = useSelect( ( select ) => { - const theme = select( coreStore ).getTheme( - currentlyPreviewingTheme() - ); + const currentTheme = useActualCurrentTheme(); - return theme?.name?.rendered; - }, [] ); + const previewingTheme = useSelect( + ( select ) => select( coreStore ).getCurrentTheme(), + [] + ); const additionalPrompt = (

    { sprintf( - 'Saving your changes will change your active theme to %s.', - themeName + /* translators: %1$s: The name of active theme, %2$s: The name of theme to be activated. */ + __( + 'Saving your changes will change your active theme from %1$s to %2$s.' + ), + currentTheme?.name?.rendered ?? '...', + previewingTheme?.name?.rendered ?? '...' ) }

    ); diff --git a/packages/edit-site/src/utils/use-actual-current-theme.js b/packages/edit-site/src/utils/use-actual-current-theme.js new file mode 100644 index 0000000000000..6f8310c2f7de1 --- /dev/null +++ b/packages/edit-site/src/utils/use-actual-current-theme.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import apiFetch from '@wordpress/api-fetch'; +import { useState, useEffect } from '@wordpress/element'; +import { addQueryArgs } from '@wordpress/url'; + +const ACTIVE_THEMES_URL = '/wp/v2/themes?status=active'; + +export function useActualCurrentTheme() { + const [ currentTheme, setCurrentTheme ] = useState(); + + useEffect( () => { + // Set the `wp_theme_preview` to empty string to bypass the createThemePreviewMiddleware. + const path = addQueryArgs( ACTIVE_THEMES_URL, { + context: 'edit', + wp_theme_preview: '', + } ); + + apiFetch( { path } ) + .then( ( activeThemes ) => setCurrentTheme( activeThemes[ 0 ] ) ) + // Do nothing + .catch( () => {} ); + }, [] ); + + return currentTheme; +} From 75acb7e7035040283a309566070b4f707d111854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:18:29 +0100 Subject: [PATCH 079/296] DataViews: add front page to sidebar (#57759) --- .../index.js | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js index 171d59c108e9b..1ef4cbddee884 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages-dataviews/index.js @@ -7,9 +7,10 @@ import { } from '@wordpress/components'; import { layout } from '@wordpress/icons'; import { useMemo } from '@wordpress/element'; -import { useEntityRecords } from '@wordpress/core-data'; +import { useEntityRecords, store as coreStore } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -41,13 +42,36 @@ export default function SidebarNavigationScreenPagesDataViews() { per_page: -1, } ); - const templates = useMemo( - () => - templateRecords?.filter( ( { slug } ) => + + const { frontPage, postsPage } = useSelect( ( select ) => { + const { getEntityRecord } = select( coreStore ); + const siteSettings = getEntityRecord( 'root', 'site' ); + return { + frontPage: siteSettings?.page_on_front, + postsPage: siteSettings?.page_for_posts, + }; + }, [] ); + + const templates = useMemo( () => { + if ( ! templateRecords ) { + return []; + } + + const isHomePageBlog = frontPage === postsPage; + const homeTemplate = + templateRecords?.find( + ( template ) => template.slug === 'front-page' + ) || + templateRecords?.find( ( template ) => template.slug === 'home' ) || + templateRecords?.find( ( template ) => template.slug === 'index' ); + + return [ + isHomePageBlog ? homeTemplate : null, + ...templateRecords?.filter( ( { slug } ) => [ '404', 'search' ].includes( slug ) ), - [ templateRecords ] - ); + ].filter( Boolean ); + }, [ templateRecords, frontPage, postsPage ] ); return ( Date: Fri, 12 Jan 2024 09:30:59 +0100 Subject: [PATCH 080/296] Restore visual separator between mover buttons when show button label is on (#57640) * Restore visual separator between mover buttons when show button labels is on. * Style block movers separator for Top toolbar. * Reuse existing CSS selector. * Improve horizontal separator positioning. --- .../src/components/block-toolbar/style.scss | 31 +++++++++++-------- .../src/components/header/style.scss | 17 +++++++--- .../components/header-edit-mode/style.scss | 20 +++++++++--- .../src/components/header/style.scss | 2 +- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index 85020cea2aa23..fc18dba7cc2cf 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -203,6 +203,24 @@ .block-editor-block-mover .block-editor-block-mover__move-button-container { width: auto; + + @include break-small() { + position: relative; + + &::before { + content: ""; + height: $border-width; + width: 100%; + background: $gray-900; + position: absolute; + top: 50%; + left: 50%; + // With Top toolbar enabled, this separator has a smaller width. Translating the + // X axis allows to make the separator always centered regardless of its width. + transform: translate(-50%, 0); + margin-top: -$border-width * 0.5; + } + } } .block-editor-block-mover.is-horizontal { @@ -231,19 +249,6 @@ padding-right: $grid-unit-15; } - @include break-small() { - // Specificity override for https://github.com/WordPress/gutenberg/blob/try/block-toolbar-labels/packages/block-editor/src/components/block-mover/style.scss#L69 - .is-up-button.is-up-button.is-up-button { - margin-right: 0; - border-radius: 0; - order: 1; - } - - .is-down-button.is-down-button.is-down-button { - order: 2; - } - } - .block-editor-block-contextual-toolbar .block-editor-block-mover.is-horizontal .block-editor-block-mover-button.block-editor-block-mover-button { width: auto; } diff --git a/packages/edit-post/src/components/header/style.scss b/packages/edit-post/src/components/header/style.scss index a3cf623a2d1aa..84e8328393d7a 100644 --- a/packages/edit-post/src/components/header/style.scss +++ b/packages/edit-post/src/components/header/style.scss @@ -69,7 +69,7 @@ margin-left: $grid-unit; } - // Modified group borders + // Modified group borders. .components-toolbar-group, .components-toolbar { border-right: none; @@ -194,18 +194,27 @@ } .show-icon-labels { - .edit-post-header__toolbar .block-editor-block-mover { + // Modified group borders. border-left: none; &::before { content: ""; width: $border-width; - margin-top: $grid-unit + $grid-unit-05; - margin-bottom: $grid-unit + $grid-unit-05; + margin-top: $grid-unit-15; + margin-bottom: $grid-unit-15; background-color: $gray-300; margin-left: $grid-unit; } + + // Modified block movers horizontal separator. + .block-editor-block-mover__move-button-container { + &::before { + width: calc(100% - #{$grid-unit-30}); + background: $gray-300; + left: calc(50% + 1px); + } + } } } diff --git a/packages/edit-site/src/components/header-edit-mode/style.scss b/packages/edit-site/src/components/header-edit-mode/style.scss index 44b762a667bb7..710c20b4e15f1 100644 --- a/packages/edit-site/src/components/header-edit-mode/style.scss +++ b/packages/edit-site/src/components/header-edit-mode/style.scss @@ -143,16 +143,26 @@ $header-toolbar-min-width: 335px; } .block-editor-block-mover { + // Modified group borders. border-left: none; &::before { content: ""; width: $border-width; - margin-top: $grid-unit + $grid-unit-05; - margin-bottom: $grid-unit + $grid-unit-05; + margin-top: $grid-unit-15; + margin-bottom: $grid-unit-15; background-color: $gray-300; margin-left: $grid-unit; } + + // Modified block movers horizontal separator. + .block-editor-block-mover__move-button-container { + &::before { + width: calc(100% - #{$grid-unit-30}); + background: $gray-300; + left: calc(50% + 1px); + } + } } } @@ -164,7 +174,7 @@ $header-toolbar-min-width: 335px; border-bottom: 0; } - // Modified group borders + // Modified group borders. .components-toolbar-group, .components-toolbar { border-right: none; @@ -172,8 +182,8 @@ $header-toolbar-min-width: 335px; &::after { content: ""; width: $border-width; - margin-top: $grid-unit + $grid-unit-05; - margin-bottom: $grid-unit + $grid-unit-05; + margin-top: $grid-unit-15; + margin-bottom: $grid-unit-15; background-color: $gray-300; margin-left: $grid-unit; } diff --git a/packages/edit-widgets/src/components/header/style.scss b/packages/edit-widgets/src/components/header/style.scss index c5bdc2561d5d5..2a3e3509cb412 100644 --- a/packages/edit-widgets/src/components/header/style.scss +++ b/packages/edit-widgets/src/components/header/style.scss @@ -17,7 +17,7 @@ border-bottom: 0; } - // Modified group borders + // Modified group borders. .components-toolbar-group, .components-toolbar { border-right: none; From f89e323c4332833696556f157d16bf8062e581cb Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 12 Jan 2024 11:31:15 +0100 Subject: [PATCH 081/296] Editor: Unify the Editor Mode preference. (#57642) --- packages/edit-post/src/index.js | 2 +- packages/edit-post/src/store/actions.js | 4 +--- packages/edit-post/src/store/selectors.js | 19 ++++++++++++++++--- packages/edit-site/src/index.js | 2 +- packages/edit-site/src/store/actions.js | 2 +- packages/edit-site/src/store/selectors.js | 2 +- .../convert-editor-settings.js | 1 + .../preferences-package-data/test/index.js | 2 +- 8 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index a3831047457be..b9409f22f6ab3 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -53,7 +53,6 @@ export function initializeEditor( const root = createRoot( target ); dispatch( preferencesStore ).setDefaults( 'core/edit-post', { - editorMode: 'visual', fullscreenMode: true, hiddenBlockTypes: [], isPublishSidebarEnabled: true, @@ -65,6 +64,7 @@ export function initializeEditor( dispatch( preferencesStore ).setDefaults( 'core', { allowRightClickOverrides: true, + editorMode: 'visual', fixedToolbar: false, inactivePanels: [], openPanels: [ 'post-status' ], diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index a4ca78f95e59c..642640335d053 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -194,9 +194,7 @@ export const toggleFeature = export const switchEditorMode = ( mode ) => ( { dispatch, registry } ) => { - registry - .dispatch( preferencesStore ) - .set( 'core/edit-post', 'editorMode', mode ); + registry.dispatch( preferencesStore ).set( 'core', 'editorMode', mode ); // Unselect blocks when we switch to the code editor. if ( mode !== 'visual' ) { diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 0d879493f995b..26f888999e9fb 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -30,8 +30,7 @@ const EMPTY_OBJECT = {}; */ export const getEditorMode = createRegistrySelector( ( select ) => () => - select( preferencesStore ).get( 'core/edit-post', 'editorMode' ) ?? - 'visual' + select( preferencesStore ).get( 'core', 'editorMode' ) ?? 'visual' ); /** @@ -167,7 +166,6 @@ export const getPreferences = createRegistrySelector( ( select ) => () => { // editor preferences. const preferences = [ 'hiddenBlockTypes', - 'editorMode', 'preferredStyleVariations', ].reduce( ( accumulatedPrefs, preferenceKey ) => { const value = select( preferencesStore ).get( @@ -180,6 +178,20 @@ export const getPreferences = createRegistrySelector( ( select ) => () => { [ preferenceKey ]: value, }; }, {} ); + const corePreferences = [ 'editorMode' ].reduce( + ( accumulatedPrefs, preferenceKey ) => { + const value = select( preferencesStore ).get( + 'core', + preferenceKey + ); + + return { + ...accumulatedPrefs, + [ preferenceKey ]: value, + }; + }, + {} + ); // Panels were a preference, but the data structure changed when the state // was migrated to the preferences store. They need to be converted from @@ -194,6 +206,7 @@ export const getPreferences = createRegistrySelector( ( select ) => () => { return { ...preferences, + ...corePreferences, panels, }; } ); diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 29b7df32e6d69..fccf8cb02bc54 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -52,7 +52,6 @@ export function initializeEditor( id, settings ) { // We dispatch actions and update the store synchronously before rendering // so that we won't trigger unnecessary re-renders with useEffect. dispatch( preferencesStore ).setDefaults( 'core/edit-site', { - editorMode: 'visual', welcomeGuide: true, welcomeGuideStyles: true, welcomeGuidePage: true, @@ -62,6 +61,7 @@ export function initializeEditor( id, settings ) { dispatch( preferencesStore ).setDefaults( 'core', { allowRightClickOverrides: true, distractionFree: false, + editorMode: 'visual', fixedToolbar: false, focusMode: false, inactivePanels: [], diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index e7f2671784e1d..f5f4ae7ba3fae 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -505,7 +505,7 @@ export const switchEditorMode = ( { dispatch, registry } ) => { registry .dispatch( 'core/preferences' ) - .set( 'core/edit-site', 'editorMode', mode ); + .set( 'core', 'editorMode', mode ); // Unselect blocks when we switch to a non visual mode. if ( mode !== 'visual' ) { diff --git a/packages/edit-site/src/store/selectors.js b/packages/edit-site/src/store/selectors.js index 4d7adaaa848fe..becc066a45d24 100644 --- a/packages/edit-site/src/store/selectors.js +++ b/packages/edit-site/src/store/selectors.js @@ -268,7 +268,7 @@ export const getCurrentTemplateTemplateParts = createRegistrySelector( * @return {string} Editing mode. */ export const getEditorMode = createRegistrySelector( ( select ) => () => { - return select( preferencesStore ).get( 'core/edit-site', 'editorMode' ); + return select( preferencesStore ).get( 'core', 'editorMode' ); } ); /** diff --git a/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js b/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js index 84542937563ac..5def03bb348fa 100644 --- a/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js +++ b/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js @@ -7,6 +7,7 @@ export default function convertEditorSettings( data ) { const settingsToMoveToCore = [ 'allowRightClickOverrides', 'distractionFree', + 'editorMode', 'fixedToolbar', 'focusMode', 'inactivePanels', diff --git a/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js b/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js index ffa39e630f509..5ea8cd45e0676 100644 --- a/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js +++ b/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js @@ -43,6 +43,7 @@ describe( 'convertPreferencesPackageData', () => { .toMatchInlineSnapshot( ` { "core": { + "editorMode": "visual", "fixedToolbar": true, "inactivePanels": [], "openPanels": [ @@ -54,7 +55,6 @@ describe( 'convertPreferencesPackageData', () => { "welcomeGuide": false, }, "core/edit-post": { - "editorMode": "visual", "fullscreenMode": false, "hiddenBlockTypes": [ "core/audio", From 7ca256c2793a5d225381d29faea44832c58dbd53 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 12 Jan 2024 22:00:15 +1100 Subject: [PATCH 082/296] Site editor: add global styles changes to save flow (#57470) * Comparing current user global styles with saved record. * Added a "Changes made to" h3 heading. --- .../entities-saved-states/entity-type-list.js | 52 +++++++++++++++++-- .../entities-saved-states/style.scss | 4 ++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/entities-saved-states/entity-type-list.js b/packages/editor/src/components/entities-saved-states/entity-type-list.js index dffac536a1d22..d422c2ae9bfdb 100644 --- a/packages/editor/src/components/entities-saved-states/entity-type-list.js +++ b/packages/editor/src/components/entities-saved-states/entity-type-list.js @@ -5,11 +5,18 @@ import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { PanelBody, PanelRow } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; +import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor'; +import { useContext } from '@wordpress/element'; /** * Internal dependencies */ import EntityRecordItem from './entity-record-item'; +import { unlock } from '../../lock-unlock'; + +const { getGlobalStylesChanges, GlobalStylesContext } = unlock( + blockEditorPrivateApis +); function getEntityDescription( entity, count ) { switch ( entity ) { @@ -27,6 +34,44 @@ function getEntityDescription( entity, count ) { } } +function GlobalStylesDescription( { record } ) { + const { user: currentEditorGlobalStyles } = + useContext( GlobalStylesContext ); + const savedRecord = useSelect( + ( select ) => + select( coreStore ).getEntityRecord( + record.kind, + record.name, + record.key + ), + [ record.kind, record.name, record.key ] + ); + + const globalStylesChanges = getGlobalStylesChanges( + currentEditorGlobalStyles, + savedRecord, + { + maxResults: 10, + } + ); + return globalStylesChanges.length ? ( + <> +

    + { __( 'Changes made to:' ) } +

    + { globalStylesChanges.join( ', ' ) } + + ) : null; +} + +function EntityDescription( { record, count } ) { + if ( 'globalStyles' === record?.name ) { + return ; + } + const description = getEntityDescription( record?.name, count ); + return description ? { description } : null; +} + export default function EntityTypeList( { list, unselectedEntities, @@ -42,19 +87,16 @@ export default function EntityTypeList( { ), [ firstRecord.kind, firstRecord.name ] ); - const { name } = firstRecord; let entityLabel = entityConfig.label; - if ( name === 'wp_template_part' ) { + if ( firstRecord?.name === 'wp_template_part' ) { entityLabel = 1 === count ? __( 'Template Part' ) : __( 'Template Parts' ); } - // Set description based on type of entity. - const description = getEntityDescription( name, count ); return ( - { description && { description } } + { list.map( ( record ) => { return ( Date: Fri, 12 Jan 2024 13:58:21 +0200 Subject: [PATCH 083/296] Editor: Use hooks instead of HoCs for `EditorNotices` (#57772) * Editor: Use hooks instead of HoCs for EditorNotices * Minor optimizations as per feedback --- .../src/components/editor-notices/index.js | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/editor/src/components/editor-notices/index.js b/packages/editor/src/components/editor-notices/index.js index ec4bd04d68920..a25d3fa40a204 100644 --- a/packages/editor/src/components/editor-notices/index.js +++ b/packages/editor/src/components/editor-notices/index.js @@ -2,8 +2,7 @@ * WordPress dependencies */ import { NoticeList } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; +import { useDispatch, useSelect } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; /** @@ -11,7 +10,14 @@ import { store as noticesStore } from '@wordpress/notices'; */ import TemplateValidationNotice from '../template-validation-notice'; -export function EditorNotices( { notices, onRemove } ) { +export function EditorNotices() { + const { notices } = useSelect( + ( select ) => ( { + notices: select( noticesStore ).getNotices(), + } ), + [] + ); + const { removeNotice } = useDispatch( noticesStore ); const dismissibleNotices = notices.filter( ( { isDismissible, type } ) => isDismissible && type === 'default' ); @@ -28,7 +34,7 @@ export function EditorNotices( { notices, onRemove } ) { @@ -36,11 +42,4 @@ export function EditorNotices( { notices, onRemove } ) { ); } -export default compose( [ - withSelect( ( select ) => ( { - notices: select( noticesStore ).getNotices(), - } ) ), - withDispatch( ( dispatch ) => ( { - onRemove: dispatch( noticesStore ).removeNotice, - } ) ), -] )( EditorNotices ); +export default EditorNotices; From 1b6948a98e4b0600783397cad48a45a098c6a917 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Fri, 12 Jan 2024 14:15:56 +0100 Subject: [PATCH 084/296] Unify the preferences modal UI between post and site editor (#57639) Co-authored-by: ntsekouras --- lib/client-assets.php | 21 +- package-lock.json | 4 + packages/base-styles/_z-index.scss | 4 +- .../{options => }/enable-custom-fields.js | 13 +- .../{options => }/enable-panel.js | 11 +- .../{options => }/enable-publish-sidebar.js | 11 +- .../src/components/preferences-modal/index.js | 311 +++--------------- .../preferences-modal/meta-boxes-section.js | 8 +- .../options/enable-feature.js | 31 -- .../preferences-modal/options/index.js | 5 - .../enable-custom-fields.js.snap | 8 +- .../__snapshots__/meta-boxes-section.js.snap | 36 +- .../test/enable-custom-fields.js | 0 .../plugin-document-setting-panel/index.js | 8 +- packages/edit-post/src/editor.js | 140 ++++---- packages/edit-post/src/editor.native.js | 37 +-- packages/edit-post/src/index.js | 2 +- packages/edit-post/src/index.native.js | 2 +- packages/edit-post/src/store/actions.js | 33 +- packages/edit-post/src/store/selectors.js | 34 +- packages/edit-post/src/store/test/actions.js | 18 +- packages/edit-post/src/style.scss | 1 - .../src/components/preferences-modal/index.js | 219 +----------- .../src/components/block-manager/category.js | 62 ++-- .../src/components/block-manager/checklist.js | 4 +- .../src/components/block-manager/index.js | 32 +- .../src/components/block-manager/style.scss | 28 +- .../preferences-modal/enable-panel.js} | 13 +- .../enable-plugin-document-setting-panel.js | 2 +- .../src/components/preferences-modal/index.js | 269 +++++++++++++++ .../preferences-modal/test/index.js | 28 ++ .../provider/use-block-editor-settings.js | 29 +- packages/editor/src/private-apis.js | 8 +- packages/editor/src/store/private-actions.js | 49 +++ packages/editor/src/style.scss | 1 + packages/interface/src/components/index.js | 4 - .../preferences-modal-section/index.js | 19 -- packages/interface/src/style.scss | 4 - .../convert-editor-settings.js | 1 + .../preferences-package-data/test/index.js | 8 +- packages/preferences/package.json | 2 + .../preference-base-option}/README.md | 10 +- .../preference-base-option}/index.js | 2 +- .../preference-base-option}/style.scss | 2 +- .../preference-toggle-control/index.js} | 17 +- .../preferences-modal-section/README.md | 1 + .../preferences-modal-section/index.js | 15 + .../preferences-modal-section/style.scss | 10 +- .../preferences-modal-tabs/README.md | 0 .../preferences-modal-tabs/index.js | 12 +- .../preferences-modal-tabs/style.scss | 8 +- .../components/preferences-modal/README.md | 2 +- .../src/components/preferences-modal/index.js | 2 +- .../components/preferences-modal/style.scss | 2 +- packages/preferences/src/index.js | 1 + packages/preferences/src/lock-unlock.js | 9 + packages/preferences/src/private-apis.js | 18 + packages/preferences/src/store/index.js | 2 +- packages/preferences/src/style.scss | 4 + packages/private-apis/src/implementation.js | 1 + 60 files changed, 773 insertions(+), 865 deletions(-) rename packages/edit-post/src/components/preferences-modal/{options => }/enable-custom-fields.js (87%) rename packages/edit-post/src/components/preferences-modal/{options => }/enable-panel.js (72%) rename packages/edit-post/src/components/preferences-modal/{options => }/enable-publish-sidebar.js (75%) delete mode 100644 packages/edit-post/src/components/preferences-modal/options/enable-feature.js delete mode 100644 packages/edit-post/src/components/preferences-modal/options/index.js rename packages/edit-post/src/components/preferences-modal/{options => }/test/__snapshots__/enable-custom-fields.js.snap (98%) rename packages/edit-post/src/components/preferences-modal/{options => }/test/enable-custom-fields.js (100%) rename packages/{edit-post => editor}/src/components/block-manager/category.js (57%) rename packages/{edit-post => editor}/src/components/block-manager/checklist.js (85%) rename packages/{edit-post => editor}/src/components/block-manager/index.js (83%) rename packages/{edit-post => editor}/src/components/block-manager/style.scss (66%) rename packages/{edit-site/src/components/preferences-modal/enable-panel-option.js => editor/src/components/preferences-modal/enable-panel.js} (66%) rename packages/{edit-post/src/components/preferences-modal/options => editor/src/components/preferences-modal}/enable-plugin-document-setting-panel.js (90%) create mode 100644 packages/editor/src/components/preferences-modal/index.js create mode 100644 packages/editor/src/components/preferences-modal/test/index.js delete mode 100644 packages/interface/src/components/preferences-modal-section/index.js rename packages/{interface/src/components/preferences-modal-base-option => preferences/src/components/preference-base-option}/README.md (69%) rename packages/{interface/src/components/preferences-modal-base-option => preferences/src/components/preference-base-option}/index.js (86%) rename packages/{interface/src/components/preferences-modal-base-option => preferences/src/components/preference-base-option}/style.scss (75%) rename packages/{edit-site/src/components/preferences-modal/enable-feature.js => preferences/src/components/preference-toggle-control/index.js} (66%) rename packages/{interface => preferences}/src/components/preferences-modal-section/README.md (94%) create mode 100644 packages/preferences/src/components/preferences-modal-section/index.js rename packages/{interface => preferences}/src/components/preferences-modal-section/style.scss (50%) rename packages/{interface => preferences}/src/components/preferences-modal-tabs/README.md (100%) rename packages/{interface => preferences}/src/components/preferences-modal-tabs/index.js (93%) rename packages/{interface => preferences}/src/components/preferences-modal-tabs/style.scss (85%) rename packages/{interface => preferences}/src/components/preferences-modal/README.md (96%) rename packages/{interface => preferences}/src/components/preferences-modal/index.js (87%) rename packages/{interface => preferences}/src/components/preferences-modal/style.scss (94%) create mode 100644 packages/preferences/src/lock-unlock.js create mode 100644 packages/preferences/src/private-apis.js create mode 100644 packages/preferences/src/style.scss diff --git a/lib/client-assets.php b/lib/client-assets.php index c5b03b6833d03..717267a5e2a50 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -266,7 +266,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-block-editor', gutenberg_url( 'build/block-editor/style.css' ), - array( 'wp-components' ), + array( 'wp-components', 'wp-preferences' ), $version ); $styles->add_data( 'wp-block-editor', 'rtl', 'replace' ); @@ -275,7 +275,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-editor', gutenberg_url( 'build/editor/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-patterns', 'wp-reusable-blocks' ), + array( 'wp-components', 'wp-block-editor', 'wp-patterns', 'wp-reusable-blocks', 'wp-preferences' ), $version ); $styles->add_data( 'wp-editor', 'rtl', 'replace' ); @@ -284,7 +284,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-edit-post', gutenberg_url( 'build/edit-post/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-commands' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-block-library', 'wp-commands', 'wp-preferences' ), $version ); $styles->add_data( 'wp-edit-post', 'rtl', 'replace' ); @@ -409,7 +409,7 @@ function gutenberg_register_packages_styles( $styles ) { $styles, 'wp-edit-site', gutenberg_url( 'build/edit-site/style.css' ), - array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-commands' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-commands', 'wp-preferences' ), $version ); $styles->add_data( 'wp-edit-site', 'rtl', 'replace' ); @@ -418,7 +418,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-editor', 'wp-edit-blocks', 'wp-patterns', 'wp-reusable-blocks', 'wp-widgets' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-patterns', 'wp-reusable-blocks', 'wp-widgets', 'wp-preferences' ), $version ); $styles->add_data( 'wp-edit-widgets', 'rtl', 'replace' ); @@ -436,7 +436,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-editor', 'wp-edit-blocks', 'wp-widgets' ), + array( 'wp-components', 'wp-block-editor', 'wp-editor', 'wp-edit-blocks', 'wp-widgets', 'wp-preferences' ), $version ); $styles->add_data( 'wp-customize-widgets', 'rtl', 'replace' ); @@ -466,6 +466,15 @@ function gutenberg_register_packages_styles( $styles ) { array( 'wp-components' ) ); $styles->add_data( 'wp-widgets', 'rtl', 'replace' ); + + gutenberg_override_style( + $styles, + 'wp-preferences', + gutenberg_url( 'build/preferences/style.css' ), + array( 'wp-components' ), + $version + ); + $styles->add_data( 'wp-preferences', 'rtl', 'replace' ); } add_action( 'wp_default_styles', 'gutenberg_register_packages_styles' ); diff --git a/package-lock.json b/package-lock.json index dc84ae2fba494..626b31a3ee9f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55595,10 +55595,12 @@ "@babel/runtime": "^7.16.0", "@wordpress/a11y": "file:../a11y", "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "classnames": "^2.3.1" }, "engines": { @@ -70260,10 +70262,12 @@ "@babel/runtime": "^7.16.0", "@wordpress/a11y": "file:../a11y", "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "classnames": "^2.3.1" } }, diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 36d01c843c1c7..e6d8d12769e70 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -161,9 +161,9 @@ $z-layers: ( ".components-resizable-box__corner-handle": 2, // Make sure block manager sticky category titles appear above the options - ".edit-post-block-manager__category-title": 1, + ".editor-block-manager__category-title": 1, // And block manager sticky disabled block count is higher still - ".edit-post-block-manager__disabled-blocks-count": 2, + ".editor-block-manager__disabled-blocks-count": 2, // Needs to appear below other color circular picker related UI elements. ".components-circular-option-picker__option-wrapper::before": -1, diff --git a/packages/edit-post/src/components/preferences-modal/options/enable-custom-fields.js b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js similarity index 87% rename from packages/edit-post/src/components/preferences-modal/options/enable-custom-fields.js rename to packages/edit-post/src/components/preferences-modal/enable-custom-fields.js index 15521b80b361b..e3ba4a1568420 100644 --- a/packages/edit-post/src/components/preferences-modal/options/enable-custom-fields.js +++ b/packages/edit-post/src/components/preferences-modal/enable-custom-fields.js @@ -6,9 +6,16 @@ import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { withSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { ___unstablePreferencesModalBaseOption as BaseOption } from '@wordpress/interface'; +import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; import { getPathAndQueryString } from '@wordpress/url'; +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { PreferenceBaseOption } = unlock( preferencesPrivateApis ); + function submitCustomFieldsForm() { const customFieldsForm = document.getElementById( 'toggle-custom-fields-form' @@ -53,7 +60,7 @@ export function EnableCustomFieldsOption( { label, areCustomFieldsEnabled } ) { const [ isChecked, setIsChecked ] = useState( areCustomFieldsEnabled ); return ( - ) } - + ); } diff --git a/packages/edit-post/src/components/preferences-modal/options/enable-panel.js b/packages/edit-post/src/components/preferences-modal/enable-panel.js similarity index 72% rename from packages/edit-post/src/components/preferences-modal/options/enable-panel.js rename to packages/edit-post/src/components/preferences-modal/enable-panel.js index 6c9ea22b7f17d..d698cdc360cae 100644 --- a/packages/edit-post/src/components/preferences-modal/options/enable-panel.js +++ b/packages/edit-post/src/components/preferences-modal/enable-panel.js @@ -3,8 +3,15 @@ */ import { compose, ifCondition } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; -import { ___unstablePreferencesModalBaseOption as BaseOption } from '@wordpress/interface'; import { store as editorStore } from '@wordpress/editor'; +import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { PreferenceBaseOption } = unlock( preferencesPrivateApis ); export default compose( withSelect( ( select, { panelName } ) => { @@ -20,4 +27,4 @@ export default compose( onChange: () => dispatch( editorStore ).toggleEditorPanelEnabled( panelName ), } ) ) -)( BaseOption ); +)( PreferenceBaseOption ); diff --git a/packages/edit-post/src/components/preferences-modal/options/enable-publish-sidebar.js b/packages/edit-post/src/components/preferences-modal/enable-publish-sidebar.js similarity index 75% rename from packages/edit-post/src/components/preferences-modal/options/enable-publish-sidebar.js rename to packages/edit-post/src/components/preferences-modal/enable-publish-sidebar.js index fa309bedf2461..84f4e72a94609 100644 --- a/packages/edit-post/src/components/preferences-modal/options/enable-publish-sidebar.js +++ b/packages/edit-post/src/components/preferences-modal/enable-publish-sidebar.js @@ -5,7 +5,14 @@ import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import { ifViewportMatches } from '@wordpress/viewport'; import { store as editorStore } from '@wordpress/editor'; -import { ___unstablePreferencesModalBaseOption as BaseOption } from '@wordpress/interface'; +import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; + +const { PreferenceBaseOption } = unlock( preferencesPrivateApis ); export default compose( withSelect( ( select ) => ( { @@ -22,4 +29,4 @@ export default compose( // In < medium viewports we override this option and always show the publish sidebar. // See the edit-post's header component for the specific logic. ifViewportMatches( 'medium' ) -)( BaseOption ); +)( PreferenceBaseOption ); diff --git a/packages/edit-post/src/components/preferences-modal/index.js b/packages/edit-post/src/components/preferences-modal/index.js index a422e4d0a207a..326677aeffc5a 100644 --- a/packages/edit-post/src/components/preferences-modal/index.js +++ b/packages/edit-post/src/components/preferences-modal/index.js @@ -5,294 +5,71 @@ import { __ } from '@wordpress/i18n'; import { useViewportMatch } from '@wordpress/compose'; import { useSelect, useDispatch } from '@wordpress/data'; -import { useMemo } from '@wordpress/element'; -import { - PostTaxonomies, - PostExcerptCheck, - PageAttributesCheck, - PostFeaturedImageCheck, - PostTypeSupportCheck, - store as editorStore, -} from '@wordpress/editor'; -import { - PreferencesModal, - PreferencesModalTabs, - PreferencesModalSection, - store as interfaceStore, -} from '@wordpress/interface'; -import { store as preferencesStore } from '@wordpress/preferences'; +import { store as interfaceStore } from '@wordpress/interface'; +import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies */ - -import { - EnablePluginDocumentSettingPanelOption, - EnablePublishSidebarOption, - EnablePanelOption, - EnableFeature, -} from './options'; +import { unlock } from '../../lock-unlock'; import MetaBoxesSection from './meta-boxes-section'; -import { store as editPostStore } from '../../store'; -import BlockManager from '../block-manager'; +import EnablePublishSidebarOption from './enable-publish-sidebar'; + +const { PreferencesModalSection, PreferenceToggleControl } = unlock( + preferencesPrivateApis +); +const { PreferencesModal } = unlock( editorPrivateApis ); export const PREFERENCES_MODAL_NAME = 'edit-post/preferences'; export default function EditPostPreferencesModal() { const isLargeViewport = useViewportMatch( 'medium' ); const { closeModal } = useDispatch( interfaceStore ); - const [ isModalActive, showBlockBreadcrumbsOption ] = useSelect( - ( select ) => { - const { getEditorSettings } = select( editorStore ); - const { getEditorMode } = select( editPostStore ); - const { get } = select( preferencesStore ); - const modalActive = select( interfaceStore ).isModalActive( - PREFERENCES_MODAL_NAME - ); - const mode = getEditorMode(); - const isRichEditingEnabled = getEditorSettings().richEditingEnabled; - const isDistractionFreeEnabled = get( 'core', 'distractionFree' ); - return [ - modalActive, - ! isDistractionFreeEnabled && - isLargeViewport && - isRichEditingEnabled && - mode === 'visual', - isDistractionFreeEnabled, - ]; - }, - [ isLargeViewport ] - ); - - const { closeGeneralSidebar } = useDispatch( editPostStore ); - const { setIsListViewOpened, setIsInserterOpened } = - useDispatch( editorStore ); - const { set: setPreference } = useDispatch( preferencesStore ); - - const toggleDistractionFree = () => { - setPreference( 'core', 'fixedToolbar', true ); - setIsInserterOpened( false ); - setIsListViewOpened( false ); - closeGeneralSidebar(); - }; + const { isModalActive } = useSelect( ( select ) => { + const modalActive = select( interfaceStore ).isModalActive( + PREFERENCES_MODAL_NAME + ); + return { + isModalActive: modalActive, + }; + }, [] ); - const turnOffDistractionFree = () => { - setPreference( 'core', 'distractionFree', false ); - }; - - const sections = useMemo( - () => [ - { - name: 'general', - tabLabel: __( 'General' ), - content: ( - <> - { isLargeViewport && ( - - - - ) } - - - { showBlockBreadcrumbsOption && ( - - ) } - - - - - ( - - ) } - /> - - - - - - - - - - - - - - - - ), - }, - { - name: 'appearance', - tabLabel: __( 'Appearance' ), - content: ( - - + { isLargeViewport && ( + + - - - - ), - }, - { - name: 'accessibility', - tabLabel: __( 'Accessibility' ), - content: ( - <> - - - - - - - - ), - }, - { - name: 'blocks', - tabLabel: __( 'Blocks' ), - content: ( - <> - - - - - - - - ), - }, - ], - [ isLargeViewport, showBlockBreadcrumbsOption ] - ); + ) } + + + ), + appearance: ( + + ), + }; if ( ! isModalActive ) { return null; } return ( - - - + ); } diff --git a/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js b/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js index c160137cfa451..2974e81547dbd 100644 --- a/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js +++ b/packages/edit-post/src/components/preferences-modal/meta-boxes-section.js @@ -4,13 +4,17 @@ import { __ } from '@wordpress/i18n'; import { withSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { PreferencesModalSection } from '@wordpress/interface'; +import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; /** * Internal dependencies */ -import { EnableCustomFieldsOption, EnablePanelOption } from './options'; +import EnableCustomFieldsOption from './enable-custom-fields'; +import EnablePanelOption from './enable-panel'; import { store as editPostStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +const { PreferencesModalSection } = unlock( preferencesPrivateApis ); export function MetaBoxesSection( { areCustomFieldsRegistered, diff --git a/packages/edit-post/src/components/preferences-modal/options/enable-feature.js b/packages/edit-post/src/components/preferences-modal/options/enable-feature.js deleted file mode 100644 index b3f3a4bbe6923..0000000000000 --- a/packages/edit-post/src/components/preferences-modal/options/enable-feature.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { ___unstablePreferencesModalBaseOption as BaseOption } from '@wordpress/interface'; -import { store as preferencesStore } from '@wordpress/preferences'; - -export default function EnableFeature( props ) { - const { - scope = 'core/edit-post', - featureName, - onToggle = () => {}, - ...remainingProps - } = props; - const isChecked = useSelect( - ( select ) => !! select( preferencesStore ).get( scope, featureName ), - [ scope, featureName ] - ); - const { toggle } = useDispatch( preferencesStore ); - const onChange = () => { - onToggle(); - toggle( scope, featureName ); - }; - return ( - - ); -} diff --git a/packages/edit-post/src/components/preferences-modal/options/index.js b/packages/edit-post/src/components/preferences-modal/options/index.js deleted file mode 100644 index b5cb1c6c4fe1b..0000000000000 --- a/packages/edit-post/src/components/preferences-modal/options/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { default as EnableCustomFieldsOption } from './enable-custom-fields'; -export { default as EnablePanelOption } from './enable-panel'; -export { default as EnablePluginDocumentSettingPanelOption } from './enable-plugin-document-setting-panel'; -export { default as EnablePublishSidebarOption } from './enable-publish-sidebar'; -export { default as EnableFeature } from './enable-feature'; diff --git a/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/enable-custom-fields.js.snap similarity index 98% rename from packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap rename to packages/edit-post/src/components/preferences-modal/test/__snapshots__/enable-custom-fields.js.snap index 781cff283f164..1ecf664fa091f 100644 --- a/packages/edit-post/src/components/preferences-modal/options/test/__snapshots__/enable-custom-fields.js.snap +++ b/packages/edit-post/src/components/preferences-modal/test/__snapshots__/enable-custom-fields.js.snap @@ -54,7 +54,7 @@ exports[`EnableCustomFieldsOption renders a checked checkbox and a confirmation

    Advanced panels

    Advanced panels

    Advanced panels

    { - const { - isFeatureActive, - getEditedPostTemplate, - getHiddenBlockTypes, - } = select( editPostStore ); - const { getEntityRecord, getPostType, getEntityRecords, canUser } = - select( coreStore ); - const { getEditorSettings } = select( editorStore ); - const { getBlockTypes } = select( blocksStore ); - const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( - currentPost.postType - ); - // Ideally the initializeEditor function should be called using the ID of the REST endpoint. - // to avoid the special case. - let postObject; - if ( isTemplate ) { - const posts = getEntityRecords( - 'postType', - currentPost.postType, - { - wp_id: currentPost.postId, - } - ); - postObject = posts?.[ 0 ]; - } else { - postObject = getEntityRecord( - 'postType', - currentPost.postType, - currentPost.postId - ); - } - const supportsTemplateMode = - getEditorSettings().supportsTemplateMode; - const isViewable = - getPostType( currentPost.postType )?.viewable ?? false; - const canEditTemplate = canUser( 'create', 'templates' ); - return { - hasInlineToolbar: isFeatureActive( 'inlineToolbar' ), - preferredStyleVariations: select( preferencesStore ).get( - 'core/edit-post', - 'preferredStyleVariations' - ), - hiddenBlockTypes: getHiddenBlockTypes(), - blockTypes: getBlockTypes(), - template: - supportsTemplateMode && isViewable && canEditTemplate - ? getEditedPostTemplate() - : null, - post: postObject, - }; - }, - [ currentPost.postType, currentPost.postId ] - ); + const { hasInlineToolbar, post, preferredStyleVariations, template } = + useSelect( + ( select ) => { + const { isFeatureActive, getEditedPostTemplate } = + select( editPostStore ); + const { + getEntityRecord, + getPostType, + getEntityRecords, + canUser, + } = select( coreStore ); + const { getEditorSettings } = select( editorStore ); + const isTemplate = [ + 'wp_template', + 'wp_template_part', + ].includes( currentPost.postType ); + // Ideally the initializeEditor function should be called using the ID of the REST endpoint. + // to avoid the special case. + let postObject; + if ( isTemplate ) { + const posts = getEntityRecords( + 'postType', + currentPost.postType, + { + wp_id: currentPost.postId, + } + ); + postObject = posts?.[ 0 ]; + } else { + postObject = getEntityRecord( + 'postType', + currentPost.postType, + currentPost.postId + ); + } + const supportsTemplateMode = + getEditorSettings().supportsTemplateMode; + const isViewable = + getPostType( currentPost.postType )?.viewable ?? false; + const canEditTemplate = canUser( 'create', 'templates' ); + return { + hasInlineToolbar: isFeatureActive( 'inlineToolbar' ), + preferredStyleVariations: select( preferencesStore ).get( + 'core/edit-post', + 'preferredStyleVariations' + ), + template: + supportsTemplateMode && isViewable && canEditTemplate + ? getEditedPostTemplate() + : null, + post: postObject, + }; + }, + [ currentPost.postType, currentPost.postId ] + ); const { updatePreferredStyleVariations } = useDispatch( editPostStore ); @@ -113,33 +105,11 @@ function Editor( { onChange: updatePreferredStyleVariations, }, hasInlineToolbar, - - // Keep a reference of the `allowedBlockTypes` from the server to handle use cases - // where we need to differentiate if a block is disabled by the user or some plugin. - defaultAllowedBlockTypes: settings.allowedBlockTypes, }; - - // Omit hidden block types if exists and non-empty. - if ( hiddenBlockTypes.length > 0 ) { - // Defer to passed setting for `allowedBlockTypes` if provided as - // anything other than `true` (where `true` is equivalent to allow - // all block types). - const defaultAllowedBlockTypes = - true === settings.allowedBlockTypes - ? blockTypes.map( ( { name } ) => name ) - : settings.allowedBlockTypes || []; - - result.allowedBlockTypes = defaultAllowedBlockTypes.filter( - ( type ) => ! hiddenBlockTypes.includes( type ) - ); - } - return result; }, [ settings, hasInlineToolbar, - hiddenBlockTypes, - blockTypes, preferredStyleVariations, updatePreferredStyleVariations, getPostLinkProps, diff --git a/packages/edit-post/src/editor.native.js b/packages/edit-post/src/editor.native.js index 74d4299a2a1fd..1e49673540e37 100644 --- a/packages/edit-post/src/editor.native.js +++ b/packages/edit-post/src/editor.native.js @@ -10,7 +10,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; */ import { Component } from '@wordpress/element'; import { EditorProvider } from '@wordpress/editor'; -import { parse, serialize, store as blocksStore } from '@wordpress/blocks'; +import { parse, serialize } from '@wordpress/blocks'; import { withDispatch, withSelect } from '@wordpress/data'; import { compose } from '@wordpress/compose'; import { @@ -47,32 +47,12 @@ class Editor extends Component { this.setTitleRef = this.setTitleRef.bind( this ); } - getEditorSettings( settings, hiddenBlockTypes, blockTypes ) { + getEditorSettings( settings ) { settings = { ...settings, isRTL: I18nManager.isRTL, }; - // Omit hidden block types if exists and non-empty. - if ( hiddenBlockTypes.length > 0 ) { - if ( settings.allowedBlockTypes === undefined ) { - // If no specific flags for allowedBlockTypes are set, assume `true` - // meaning allow all block types. - settings.allowedBlockTypes = true; - } - // Defer to passed setting for `allowedBlockTypes` if provided as - // anything other than `true` (where `true` is equivalent to allow - // all block types). - const defaultAllowedBlockTypes = - true === settings.allowedBlockTypes - ? blockTypes.map( ( { name } ) => name ) - : settings.allowedBlockTypes || []; - - settings.allowedBlockTypes = defaultAllowedBlockTypes.filter( - ( type ) => ! hiddenBlockTypes.includes( type ) - ); - } - return settings; } @@ -127,8 +107,6 @@ class Editor extends Component { const { settings, initialEdits, - hiddenBlockTypes, - blockTypes, post, postId, postType, @@ -137,11 +115,7 @@ class Editor extends Component { ...props } = this.props; - const editorSettings = this.getEditorSettings( - settings, - hiddenBlockTypes, - blockTypes - ); + const editorSettings = this.getEditorSettings( settings ); const normalizedPost = post || { id: postId, @@ -180,13 +154,10 @@ class Editor extends Component { export default compose( [ withSelect( ( select ) => { - const { getEditorMode, getHiddenBlockTypes } = select( editPostStore ); - const { getBlockTypes } = select( blocksStore ); + const { getEditorMode } = select( editPostStore ); return { mode: getEditorMode(), - hiddenBlockTypes: getHiddenBlockTypes(), - blockTypes: getBlockTypes(), }; } ), withDispatch( ( dispatch ) => { diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index b9409f22f6ab3..78c79227c9d89 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -54,7 +54,6 @@ export function initializeEditor( dispatch( preferencesStore ).setDefaults( 'core/edit-post', { fullscreenMode: true, - hiddenBlockTypes: [], isPublishSidebarEnabled: true, preferredStyleVariations: {}, themeStyles: true, @@ -66,6 +65,7 @@ export function initializeEditor( allowRightClickOverrides: true, editorMode: 'visual', fixedToolbar: false, + hiddenBlockTypes: [], inactivePanels: [], openPanels: [ 'post-status' ], showBlockBreadcrumbs: true, diff --git a/packages/edit-post/src/index.native.js b/packages/edit-post/src/index.native.js index 974f617d609f2..c1b5126dba3b1 100644 --- a/packages/edit-post/src/index.native.js +++ b/packages/edit-post/src/index.native.js @@ -24,7 +24,6 @@ export function initializeEditor( id, postType, postId ) { dispatch( preferencesStore ).setDefaults( 'core/edit-post', { editorMode: 'visual', fullscreenMode: true, - hiddenBlockTypes: [], inactivePanels: [], isPublishSidebarEnabled: true, openPanels: [ 'post-status' ], @@ -32,6 +31,7 @@ export function initializeEditor( id, postType, postId ) { welcomeGuide: true, } ); dispatch( preferencesStore ).setDefaults( 'core', { + hiddenBlockTypes: [], inactivePanels: [], openPanels: [ 'post-status' ], } ); diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 642640335d053..b6d8e437ccabf 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -17,6 +17,7 @@ import { addFilter } from '@wordpress/hooks'; */ import { getMetaBoxContainer } from '../utils/meta-boxes'; import { store as editPostStore } from '.'; +import { unlock } from '../lock-unlock'; /** * Returns an action object used in signalling that the user opened an editor sidebar. @@ -287,21 +288,7 @@ export const updatePreferredStyleVariations = export const showBlockTypes = ( blockNames ) => ( { registry } ) => { - const existingBlockNames = - registry - .select( preferencesStore ) - .get( 'core/edit-post', 'hiddenBlockTypes' ) ?? []; - - const newBlockNames = existingBlockNames.filter( - ( type ) => - ! ( - Array.isArray( blockNames ) ? blockNames : [ blockNames ] - ).includes( type ) - ); - - registry - .dispatch( preferencesStore ) - .set( 'core/edit-post', 'hiddenBlockTypes', newBlockNames ); + unlock( registry.dispatch( editorStore ) ).showBlockTypes( blockNames ); }; /** @@ -312,21 +299,7 @@ export const showBlockTypes = export const hideBlockTypes = ( blockNames ) => ( { registry } ) => { - const existingBlockNames = - registry - .select( preferencesStore ) - .get( 'core/edit-post', 'hiddenBlockTypes' ) ?? []; - - const mergedBlockNames = new Set( [ - ...existingBlockNames, - ...( Array.isArray( blockNames ) ? blockNames : [ blockNames ] ), - ] ); - - registry - .dispatch( preferencesStore ) - .set( 'core/edit-post', 'hiddenBlockTypes', [ - ...mergedBlockNames, - ] ); + unlock( registry.dispatch( editorStore ) ).hideBlockTypes( blockNames ); }; /** diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 26f888999e9fb..b6a54dfd0de2c 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -164,21 +164,21 @@ export const getPreferences = createRegistrySelector( ( select ) => () => { // These preferences now exist in the preferences store. // Fetch them so that they can be merged into the post // editor preferences. - const preferences = [ - 'hiddenBlockTypes', - 'preferredStyleVariations', - ].reduce( ( accumulatedPrefs, preferenceKey ) => { - const value = select( preferencesStore ).get( - 'core/edit-post', - preferenceKey - ); + const preferences = [ 'preferredStyleVariations' ].reduce( + ( accumulatedPrefs, preferenceKey ) => { + const value = select( preferencesStore ).get( + 'core/edit-post', + preferenceKey + ); - return { - ...accumulatedPrefs, - [ preferenceKey ]: value, - }; - }, {} ); - const corePreferences = [ 'editorMode' ].reduce( + return { + ...accumulatedPrefs, + [ preferenceKey ]: value, + }; + }, + {} + ); + const corePreferences = [ 'editorMode', 'hiddenBlockTypes' ].reduce( ( accumulatedPrefs, preferenceKey ) => { const value = select( preferencesStore ).get( 'core', @@ -238,10 +238,8 @@ export function getPreference( state, preferenceKey, defaultValue ) { */ export const getHiddenBlockTypes = createRegistrySelector( ( select ) => () => { return ( - select( preferencesStore ).get( - 'core/edit-post', - 'hiddenBlockTypes' - ) ?? EMPTY_ARRAY + select( preferencesStore ).get( 'core', 'hiddenBlockTypes' ) ?? + EMPTY_ARRAY ); } ); diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index f702d412d55da..f1db11d26bafc 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -154,16 +154,13 @@ describe( 'actions', () => { expect( registry - .select( editPostStore ) - .getPreference( 'hiddenBlockTypes' ) + .select( preferencesStore ) + .get( 'core', 'hiddenBlockTypes' ) ).toEqual( expected ); expect( registry.select( editPostStore ).getHiddenBlockTypes() ).toEqual( expected ); - - // Expect a deprecation message for `getPreference`. - expect( console ).toHaveWarned(); } ); } ); @@ -177,8 +174,8 @@ describe( 'actions', () => { expect( registry - .select( editPostStore ) - .getPreference( 'hiddenBlockTypes' ) + .select( preferencesStore ) + .get( 'core', 'hiddenBlockTypes' ) ).toEqual( expectedA ); expect( @@ -193,8 +190,8 @@ describe( 'actions', () => { expect( registry - .select( editPostStore ) - .getPreference( 'hiddenBlockTypes' ) + .select( preferencesStore ) + .get( 'core', 'hiddenBlockTypes' ) ).toEqual( expectedB ); expect( @@ -220,6 +217,9 @@ describe( 'actions', () => { 'core/paragraph': 'fancy', 'core/quote': 'posh', } ); + + // Expect a deprecation message for `getPreference`. + expect( console ).toHaveWarned(); } ); it( 'removes a preferred style variation for a block when a style name is omitted', () => { diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index adfd7218c4c12..adb5e08da5e84 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -3,7 +3,6 @@ @import "./components/header/fullscreen-mode-close/style.scss"; @import "./components/keyboard-shortcut-help-modal/style.scss"; @import "./components/layout/style.scss"; -@import "./components/block-manager/style.scss"; @import "./components/meta-boxes/meta-boxes-area/style.scss"; @import "./components/sidebar/style.scss"; @import "./components/sidebar/post-format/style.scss"; diff --git a/packages/edit-site/src/components/preferences-modal/index.js b/packages/edit-site/src/components/preferences-modal/index.js index 8634e3e18b23e..4d719fedad234 100644 --- a/packages/edit-site/src/components/preferences-modal/index.js +++ b/packages/edit-site/src/components/preferences-modal/index.js @@ -1,30 +1,16 @@ /** * WordPress dependencies */ -import { - PreferencesModal, - PreferencesModalTabs, - PreferencesModalSection, - store as interfaceStore, -} from '@wordpress/interface'; -import { __ } from '@wordpress/i18n'; -import { useSelect, useDispatch, useRegistry } from '@wordpress/data'; -import { store as preferencesStore } from '@wordpress/preferences'; -import { - PostTaxonomies, - PostExcerptCheck, - PageAttributesCheck, - PostFeaturedImageCheck, - PostTypeSupportCheck, - store as editorStore, -} from '@wordpress/editor'; +import { store as interfaceStore } from '@wordpress/interface'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies */ -import EnableFeature from './enable-feature'; -import EnablePanelOption from './enable-panel-option'; -import { store as editSiteStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +const { PreferencesModal } = unlock( editorPrivateApis ); export const PREFERENCES_MODAL_NAME = 'edit-site/preferences'; @@ -32,201 +18,12 @@ export default function EditSitePreferencesModal() { const isModalActive = useSelect( ( select ) => select( interfaceStore ).isModalActive( PREFERENCES_MODAL_NAME ) ); - const { closeModal, openModal } = useDispatch( interfaceStore ); - const toggleModal = () => - isModalActive ? closeModal() : openModal( PREFERENCES_MODAL_NAME ); - const registry = useRegistry(); - const { closeGeneralSidebar } = useDispatch( editSiteStore ); - const { setIsListViewOpened, setIsInserterOpened } = - useDispatch( editorStore ); - - const { set: setPreference } = useDispatch( preferencesStore ); - const toggleDistractionFree = () => { - registry.batch( () => { - setPreference( 'core', 'fixedToolbar', true ); - setIsInserterOpened( false ); - setIsListViewOpened( false ); - closeGeneralSidebar(); - } ); - }; - - const turnOffDistractionFree = () => { - setPreference( 'core', 'distractionFree', false ); - }; + const { closeModal } = useDispatch( interfaceStore ); - const sections = [ - { - name: 'general', - tabLabel: __( 'General' ), - content: ( - <> - - - - - - - ( - - ) } - /> - - - - - - - - - - - - - - - ), - }, - { - name: 'appearance', - tabLabel: __( 'Appearance' ), - content: ( - - - - - - ), - }, - { - name: 'accessibility', - tabLabel: __( 'Accessibility' ), - content: ( - <> - - - - - - - - ), - }, - { - name: 'blocks', - tabLabel: __( 'Blocks' ), - content: ( - <> - - - - - ), - }, - ]; if ( ! isModalActive ) { return null; } return ( - - - + ); } diff --git a/packages/edit-post/src/components/block-manager/category.js b/packages/editor/src/components/block-manager/category.js similarity index 57% rename from packages/edit-post/src/components/block-manager/category.js rename to packages/editor/src/components/block-manager/category.js index 6a64563366d63..e7125fa151f72 100644 --- a/packages/edit-post/src/components/block-manager/category.js +++ b/packages/editor/src/components/block-manager/category.js @@ -5,44 +5,46 @@ import { useMemo, useCallback } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { useInstanceId } from '@wordpress/compose'; import { CheckboxControl } from '@wordpress/components'; -import { store as editorStore } from '@wordpress/editor'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Internal dependencies */ import BlockTypesChecklist from './checklist'; -import { store as editPostStore } from '../../store'; +import { store as editorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; function BlockManagerCategory( { title, blockTypes } ) { const instanceId = useInstanceId( BlockManagerCategory ); - const { defaultAllowedBlockTypes, hiddenBlockTypes } = useSelect( - ( select ) => { - const { getEditorSettings } = select( editorStore ); - const { getHiddenBlockTypes } = select( editPostStore ); - return { - defaultAllowedBlockTypes: - getEditorSettings().defaultAllowedBlockTypes, - hiddenBlockTypes: getHiddenBlockTypes(), - }; - }, - [] - ); + const { allowedBlockTypes, hiddenBlockTypes } = useSelect( ( select ) => { + const { getEditorSettings } = select( editorStore ); + const { get } = select( preferencesStore ); + return { + allowedBlockTypes: getEditorSettings().allowedBlockTypes, + hiddenBlockTypes: get( 'core', 'hiddenBlockTypes' ), + }; + }, [] ); const filteredBlockTypes = useMemo( () => { - if ( defaultAllowedBlockTypes === true ) { + if ( allowedBlockTypes === true ) { return blockTypes; } return blockTypes.filter( ( { name } ) => { - return defaultAllowedBlockTypes?.includes( name ); + return allowedBlockTypes?.includes( name ); } ); - }, [ defaultAllowedBlockTypes, blockTypes ] ); - const { showBlockTypes, hideBlockTypes } = useDispatch( editPostStore ); - const toggleVisible = useCallback( ( blockName, nextIsChecked ) => { - if ( nextIsChecked ) { - showBlockTypes( blockName ); - } else { - hideBlockTypes( blockName ); - } - }, [] ); + }, [ allowedBlockTypes, blockTypes ] ); + const { showBlockTypes, hideBlockTypes } = unlock( + useDispatch( editorStore ) + ); + const toggleVisible = useCallback( + ( blockName, nextIsChecked ) => { + if ( nextIsChecked ) { + showBlockTypes( blockName ); + } else { + hideBlockTypes( blockName ); + } + }, + [ showBlockTypes, hideBlockTypes ] + ); const toggleAllVisible = useCallback( ( nextIsChecked ) => { const blockNames = blockTypes.map( ( { name } ) => name ); @@ -52,7 +54,7 @@ function BlockManagerCategory( { title, blockTypes } ) { hideBlockTypes( blockNames ); } }, - [ blockTypes ] + [ blockTypes, showBlockTypes, hideBlockTypes ] ); if ( ! filteredBlockTypes.length ) { @@ -61,9 +63,9 @@ function BlockManagerCategory( { title, blockTypes } ) { const checkedBlockNames = filteredBlockTypes .map( ( { name } ) => name ) - .filter( ( type ) => ! hiddenBlockTypes.includes( type ) ); + .filter( ( type ) => ! ( hiddenBlockTypes ?? [] ).includes( type ) ); - const titleId = 'edit-post-block-manager__category-title-' + instanceId; + const titleId = 'editor-block-manager__category-title-' + instanceId; const isAllChecked = checkedBlockNames.length === filteredBlockTypes.length; const isIndeterminate = ! isAllChecked && checkedBlockNames.length > 0; @@ -72,13 +74,13 @@ function BlockManagerCategory( { title, blockTypes } ) {
    { title } } /> diff --git a/packages/edit-post/src/components/block-manager/checklist.js b/packages/editor/src/components/block-manager/checklist.js similarity index 85% rename from packages/edit-post/src/components/block-manager/checklist.js rename to packages/editor/src/components/block-manager/checklist.js index aa21fefb1c818..01bd06abdeba8 100644 --- a/packages/edit-post/src/components/block-manager/checklist.js +++ b/packages/editor/src/components/block-manager/checklist.js @@ -6,11 +6,11 @@ import { CheckboxControl } from '@wordpress/components'; function BlockTypesChecklist( { blockTypes, value, onItemChange } ) { return ( -
      +
        { blockTypes.map( ( blockType ) => (
      • +
        { !! numberOfHiddenBlocks && ( -
        +
        { sprintf( /* translators: %d: number of blocks. */ _n( @@ -78,16 +80,16 @@ function BlockManager( { placeholder={ __( 'Search for a block' ) } value={ search } onChange={ ( nextSearch ) => setSearch( nextSearch ) } - className="edit-post-block-manager__search" + className="editor-block-manager__search" />
        { blockTypes.length === 0 && ( -

        +

        { __( 'No blocks found.' ) }

        ) } @@ -120,7 +122,7 @@ export default compose( [ hasBlockSupport, isMatchingSearchTerm, } = select( blocksStore ); - const { getHiddenBlockTypes } = select( editPostStore ); + const { get } = select( preferencesStore ); // Some hidden blocks become unregistered // by removing for instance the plugin that registered them, yet @@ -128,13 +130,13 @@ export default compose( [ // We consider "hidden", blocks which were hidden and // are still registered. const blockTypes = getBlockTypes(); - const hiddenBlockTypes = getHiddenBlockTypes().filter( - ( hiddenBlock ) => { - return blockTypes.some( - ( registeredBlock ) => registeredBlock.name === hiddenBlock - ); - } - ); + const hiddenBlockTypes = ( + get( 'core', 'hiddenBlockTypes' ) ?? [] + ).filter( ( hiddenBlock ) => { + return blockTypes.some( + ( registeredBlock ) => registeredBlock.name === hiddenBlock + ); + } ); const numberOfHiddenBlocks = Array.isArray( hiddenBlockTypes ) && hiddenBlockTypes.length; @@ -147,7 +149,7 @@ export default compose( [ }; } ), withDispatch( ( dispatch ) => { - const { showBlockTypes } = dispatch( editPostStore ); + const { showBlockTypes } = unlock( dispatch( editorStore ) ); return { enableAllBlockTypes: ( blockTypes ) => { const blockNames = blockTypes.map( ( { name } ) => name ); diff --git a/packages/edit-post/src/components/block-manager/style.scss b/packages/editor/src/components/block-manager/style.scss similarity index 66% rename from packages/edit-post/src/components/block-manager/style.scss rename to packages/editor/src/components/block-manager/style.scss index e1b92c7c4da43..62e5c9d60cb17 100644 --- a/packages/edit-post/src/components/block-manager/style.scss +++ b/packages/editor/src/components/block-manager/style.scss @@ -1,14 +1,14 @@ -.edit-post-block-manager__no-results { +.editor-block-manager__no-results { font-style: italic; padding: $grid-unit-30 0; text-align: center; } -.edit-post-block-manager__search { +.editor-block-manager__search { margin: $grid-unit-20 0; } -.edit-post-block-manager__disabled-blocks-count { +.editor-block-manager__disabled-blocks-count { border: 1px solid $gray-300; border-width: 1px 0; // Cover up horizontal areas off the sides of the box rectangle @@ -19,10 +19,10 @@ position: sticky; // When sticking, tuck the top border beneath the modal header border top: ($grid-unit-05 + 1) * -1; - z-index: z-index(".edit-post-block-manager__disabled-blocks-count"); + z-index: z-index(".editor-block-manager__disabled-blocks-count"); // Stick the category titles to the bottom - ~ .edit-post-block-manager__results .edit-post-block-manager__category-title { + ~ .editor-block-manager__results .editor-block-manager__category-title { top: $grid-unit-40 - 1; } .is-link { @@ -30,32 +30,32 @@ } } -.edit-post-block-manager__category { +.editor-block-manager__category { margin: 0 0 $grid-unit-30 0; } -.edit-post-block-manager__category-title { +.editor-block-manager__category-title { position: sticky; top: - $grid-unit-05; // Offsets the top padding on the modal content container padding: $grid-unit-20 0; background-color: $white; - z-index: z-index(".edit-post-block-manager__category-title"); + z-index: z-index(".editor-block-manager__category-title"); .components-checkbox-control__label { font-weight: 600; } } -.edit-post-block-manager__checklist { +.editor-block-manager__checklist { margin-top: 0; } -.edit-post-block-manager__category-title, -.edit-post-block-manager__checklist-item { +.editor-block-manager__category-title, +.editor-block-manager__checklist-item { border-bottom: 1px solid $gray-300; } -.edit-post-block-manager__checklist-item { +.editor-block-manager__checklist-item { display: flex; justify-content: space-between; align-items: center; @@ -72,11 +72,11 @@ } } -.edit-post-block-manager__results { +.editor-block-manager__results { border-top: $border-width solid $gray-300; } // Remove the top border from results when adjacent to the disabled block count -.edit-post-block-manager__disabled-blocks-count + .edit-post-block-manager__results { +.editor-block-manager__disabled-blocks-count + .editor-block-manager__results { border-top-width: 0; } diff --git a/packages/edit-site/src/components/preferences-modal/enable-panel-option.js b/packages/editor/src/components/preferences-modal/enable-panel.js similarity index 66% rename from packages/edit-site/src/components/preferences-modal/enable-panel-option.js rename to packages/editor/src/components/preferences-modal/enable-panel.js index 6c9ea22b7f17d..66c9140943441 100644 --- a/packages/edit-site/src/components/preferences-modal/enable-panel-option.js +++ b/packages/editor/src/components/preferences-modal/enable-panel.js @@ -3,8 +3,15 @@ */ import { compose, ifCondition } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; -import { ___unstablePreferencesModalBaseOption as BaseOption } from '@wordpress/interface'; -import { store as editorStore } from '@wordpress/editor'; +import { privateApis as preferencesPrivateApis } from '@wordpress/preferences'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import { store as editorStore } from '../../store'; + +const { PreferenceBaseOption } = unlock( preferencesPrivateApis ); export default compose( withSelect( ( select, { panelName } ) => { @@ -20,4 +27,4 @@ export default compose( onChange: () => dispatch( editorStore ).toggleEditorPanelEnabled( panelName ), } ) ) -)( BaseOption ); +)( PreferenceBaseOption ); diff --git a/packages/edit-post/src/components/preferences-modal/options/enable-plugin-document-setting-panel.js b/packages/editor/src/components/preferences-modal/enable-plugin-document-setting-panel.js similarity index 90% rename from packages/edit-post/src/components/preferences-modal/options/enable-plugin-document-setting-panel.js rename to packages/editor/src/components/preferences-modal/enable-plugin-document-setting-panel.js index b18b0436579bc..4dd2e55eba007 100644 --- a/packages/edit-post/src/components/preferences-modal/options/enable-plugin-document-setting-panel.js +++ b/packages/editor/src/components/preferences-modal/enable-plugin-document-setting-panel.js @@ -6,7 +6,7 @@ import { createSlotFill } from '@wordpress/components'; /** * Internal dependencies */ -import { EnablePanelOption } from './index'; +import EnablePanelOption from './enable-panel'; const { Fill, Slot } = createSlotFill( 'EnablePluginDocumentSettingPanelOption' diff --git a/packages/editor/src/components/preferences-modal/index.js b/packages/editor/src/components/preferences-modal/index.js new file mode 100644 index 0000000000000..044c0eb426e22 --- /dev/null +++ b/packages/editor/src/components/preferences-modal/index.js @@ -0,0 +1,269 @@ +/** + * WordPress dependencies + */ + +import { __ } from '@wordpress/i18n'; +import { useViewportMatch } from '@wordpress/compose'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { + store as preferencesStore, + privateApis as preferencesPrivateApis, +} from '@wordpress/preferences'; + +/** + * Internal dependencies + */ +import EnablePanelOption from './enable-panel'; +import EnablePluginDocumentSettingPanelOption from './enable-plugin-document-setting-panel'; +import BlockManager from '../block-manager'; +import PostTaxonomies from '../post-taxonomies'; +import PostFeaturedImageCheck from '../post-featured-image/check'; +import PostExcerptCheck from '../post-excerpt/check'; +import PageAttributesCheck from '../page-attributes/check'; +import PostTypeSupportCheck from '../post-type-support-check'; +import { store as editorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; + +const { + PreferencesModal, + PreferencesModalTabs, + PreferencesModalSection, + PreferenceToggleControl, +} = unlock( preferencesPrivateApis ); + +export default function EditorPreferencesModal( { + extraSections = {}, + isActive, + onClose, +} ) { + const isLargeViewport = useViewportMatch( 'medium' ); + const { showBlockBreadcrumbsOption } = useSelect( + ( select ) => { + const { getEditorSettings } = select( editorStore ); + const { get } = select( preferencesStore ); + const isRichEditingEnabled = getEditorSettings().richEditingEnabled; + const isDistractionFreeEnabled = get( 'core', 'distractionFree' ); + return { + showBlockBreadcrumbsOption: + ! isDistractionFreeEnabled && + isLargeViewport && + isRichEditingEnabled, + }; + }, + [ isLargeViewport ] + ); + + const { setIsListViewOpened, setIsInserterOpened } = + useDispatch( editorStore ); + const { set: setPreference } = useDispatch( preferencesStore ); + + const toggleDistractionFree = () => { + setPreference( 'core', 'fixedToolbar', true ); + setIsInserterOpened( false ); + setIsListViewOpened( false ); + // Todo: Check sidebar when closing/opening distraction free. + }; + + const turnOffDistractionFree = () => { + setPreference( 'core', 'distractionFree', false ); + }; + + const sections = useMemo( + () => [ + { + name: 'general', + tabLabel: __( 'General' ), + content: ( + <> + + + { showBlockBreadcrumbsOption && ( + + ) } + + + + + ( + + ) } + /> + + + + + + + + + + + + + + { extraSections?.general } + + ), + }, + { + name: 'appearance', + tabLabel: __( 'Appearance' ), + content: ( + + + + + { extraSections?.appearance } + + ), + }, + { + name: 'accessibility', + tabLabel: __( 'Accessibility' ), + content: ( + <> + + + + + + + + ), + }, + { + name: 'blocks', + tabLabel: __( 'Blocks' ), + content: ( + <> + + + + + + + + ), + }, + ], + [ isLargeViewport, showBlockBreadcrumbsOption, extraSections ] + ); + + if ( ! isActive ) { + return null; + } + + return ( + + + + ); +} diff --git a/packages/editor/src/components/preferences-modal/test/index.js b/packages/editor/src/components/preferences-modal/test/index.js new file mode 100644 index 0000000000000..01ac1a88fbe7d --- /dev/null +++ b/packages/editor/src/components/preferences-modal/test/index.js @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { render, screen } from '@testing-library/react'; + +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import EditPostPreferencesModal from '../'; + +// This allows us to tweak the returned value on each test. +jest.mock( '@wordpress/data/src/components/use-select', () => jest.fn() ); +jest.mock( '@wordpress/compose/src/hooks/use-viewport-match', () => jest.fn() ); + +describe( 'EditPostPreferencesModal', () => { + it( 'should not render when the modal is not active', () => { + useSelect.mockImplementation( () => [ false, false, false ] ); + render( ); + expect( + screen.queryByRole( 'dialog', { name: 'Preferences' } ) + ).not.toBeInTheDocument(); + } ); +} ); 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 eddc295766f8c..5a9b3a82b1bdb 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -11,6 +11,7 @@ import { import { __ } from '@wordpress/i18n'; import { store as preferencesStore } from '@wordpress/preferences'; import { useViewportMatch } from '@wordpress/compose'; +import { store as blocksStore } from '@wordpress/blocks'; /** * Internal dependencies @@ -29,7 +30,6 @@ const BLOCK_EDITOR_SETTINGS = [ '__experimentalPreferredStyleVariations', '__unstableGalleryWithImageBlocks', 'alignWide', - 'allowedBlockTypes', 'blockInspectorTabs', 'allowedMimeTypes', 'bodyPlaceholder', @@ -88,12 +88,14 @@ function useBlockEditorSettings( settings, postType, postId ) { const isLargeViewport = useViewportMatch( 'medium' ); const { allowRightClickOverrides, + blockTypes, focusMode, hasFixedToolbar, isDistractionFree, keepCaretInsideBlock, reusableBlocks, hasUploadPermissions, + hiddenBlockTypes, canUseUnfilteredHTML, userCanCreatePages, pageOnFront, @@ -114,7 +116,7 @@ function useBlockEditorSettings( settings, postType, postId ) { getBlockPatternCategories, } = select( coreStore ); const { get } = select( preferencesStore ); - + const { getBlockTypes } = select( blocksStore ); const siteSettings = canUser( 'read', 'settings' ) ? getEntityRecord( 'root', 'site' ) : undefined; @@ -124,6 +126,7 @@ function useBlockEditorSettings( settings, postType, postId ) { 'core', 'allowRightClickOverrides' ), + blockTypes: getBlockTypes(), canUseUnfilteredHTML: getRawEntityRecord( 'postType', postType, @@ -132,6 +135,7 @@ function useBlockEditorSettings( settings, postType, postId ) { focusMode: get( 'core', 'focusMode' ), hasFixedToolbar: get( 'core', 'fixedToolbar' ) || ! isLargeViewport, + hiddenBlockTypes: get( 'core', 'hiddenBlockTypes' ), isDistractionFree: get( 'core', 'distractionFree' ), keepCaretInsideBlock: get( 'core', 'keepCaretInsideBlock' ), reusableBlocks: isWeb @@ -215,6 +219,25 @@ function useBlockEditorSettings( settings, postType, postId ) { [ saveEntityRecord, userCanCreatePages ] ); + const allowedBlockTypes = useMemo( () => { + // Omit hidden block types if exists and non-empty. + if ( hiddenBlockTypes && hiddenBlockTypes.length > 0 ) { + // Defer to passed setting for `allowedBlockTypes` if provided as + // anything other than `true` (where `true` is equivalent to allow + // all block types). + const defaultAllowedBlockTypes = + true === settings.allowedBlockTypes + ? blockTypes.map( ( { name } ) => name ) + : settings.allowedBlockTypes || []; + + return defaultAllowedBlockTypes.filter( + ( type ) => ! hiddenBlockTypes.includes( type ) + ); + } + + return settings.allowedBlockTypes; + }, [ settings.allowedBlockTypes, hiddenBlockTypes, blockTypes ] ); + const forceDisableFocusMode = settings.focusMode === false; return useMemo( @@ -224,6 +247,7 @@ function useBlockEditorSettings( settings, postType, postId ) { BLOCK_EDITOR_SETTINGS.includes( key ) ) ), + allowedBlockTypes, allowRightClickOverrides, focusMode: focusMode && ! forceDisableFocusMode, hasFixedToolbar, @@ -261,6 +285,7 @@ function useBlockEditorSettings( settings, postType, postId ) { __experimentalSetIsInserterOpened: setIsInserterOpened, } ), [ + allowedBlockTypes, allowRightClickOverrides, focusMode, forceDisableFocusMode, diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index 5f8fc7ccf7318..16c27b1b57c19 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -4,28 +4,32 @@ import EditorCanvas from './components/editor-canvas'; import { ExperimentalEditorProvider } from './components/provider'; import { lock } from './lock-unlock'; +import EnablePluginDocumentSettingPanelOption from './components/preferences-modal/enable-plugin-document-setting-panel'; import { EntitiesSavedStatesExtensible } from './components/entities-saved-states'; import useBlockEditorSettings from './components/provider/use-block-editor-settings'; import DocumentTools from './components/document-tools'; import InserterSidebar from './components/inserter-sidebar'; import ListViewSidebar from './components/list-view-sidebar'; +import PluginPostExcerpt from './components/post-excerpt/plugin'; import PostPanelRow from './components/post-panel-row'; import PostViewLink from './components/post-view-link'; import PreviewDropdown from './components/preview-dropdown'; -import PluginPostExcerpt from './components/post-excerpt/plugin'; +import PreferencesModal from './components/preferences-modal'; export const privateApis = {}; lock( privateApis, { DocumentTools, EditorCanvas, ExperimentalEditorProvider, + EnablePluginDocumentSettingPanelOption, EntitiesSavedStatesExtensible, InserterSidebar, ListViewSidebar, + PluginPostExcerpt, PostPanelRow, PostViewLink, PreviewDropdown, - PluginPostExcerpt, + PreferencesModal, // This is a temporary private API while we're updating the site editor to use EditorProvider. useBlockEditorSettings, diff --git a/packages/editor/src/store/private-actions.js b/packages/editor/src/store/private-actions.js index 7ddeab5f35734..0d7c0a2186421 100644 --- a/packages/editor/src/store/private-actions.js +++ b/packages/editor/src/store/private-actions.js @@ -4,6 +4,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; +import { store as preferencesStore } from '@wordpress/preferences'; /** * Returns an action object used to set which template is currently being used/edited. @@ -59,3 +60,51 @@ export const createTemplate = } ); }; + +/** + * Update the provided block types to be visible. + * + * @param {string[]} blockNames Names of block types to show. + */ +export const showBlockTypes = + ( blockNames ) => + ( { registry } ) => { + const existingBlockNames = + registry + .select( preferencesStore ) + .get( 'core', 'hiddenBlockTypes' ) ?? []; + + const newBlockNames = existingBlockNames.filter( + ( type ) => + ! ( + Array.isArray( blockNames ) ? blockNames : [ blockNames ] + ).includes( type ) + ); + + registry + .dispatch( preferencesStore ) + .set( 'core', 'hiddenBlockTypes', newBlockNames ); + }; + +/** + * Update the provided block types to be hidden. + * + * @param {string[]} blockNames Names of block types to hide. + */ +export const hideBlockTypes = + ( blockNames ) => + ( { registry } ) => { + const existingBlockNames = + registry + .select( preferencesStore ) + .get( 'core', 'hiddenBlockTypes' ) ?? []; + + const mergedBlockNames = new Set( [ + ...existingBlockNames, + ...( Array.isArray( blockNames ) ? blockNames : [ blockNames ] ), + ] ); + + registry + .dispatch( preferencesStore ) + .set( 'core', 'hiddenBlockTypes', [ ...mergedBlockNames ] ); + }; diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index 09e50d1abed79..450c61fd0bb7e 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -1,4 +1,5 @@ @import "./components/autocompleters/style.scss"; +@import "./components/block-manager/style.scss"; @import "./components/document-bar/style.scss"; @import "./components/document-outline/style.scss"; @import "./components/document-tools/style.scss"; diff --git a/packages/interface/src/components/index.js b/packages/interface/src/components/index.js index c9c2d09b3b3ab..6f986e138838e 100644 --- a/packages/interface/src/components/index.js +++ b/packages/interface/src/components/index.js @@ -6,8 +6,4 @@ export { default as PinnedItems } from './pinned-items'; export { default as MoreMenuDropdown } from './more-menu-dropdown'; export { default as MoreMenuFeatureToggle } from './more-menu-feature-toggle'; export { default as ActionItem } from './action-item'; -export { default as PreferencesModal } from './preferences-modal'; -export { default as PreferencesModalTabs } from './preferences-modal-tabs'; -export { default as PreferencesModalSection } from './preferences-modal-section'; -export { default as ___unstablePreferencesModalBaseOption } from './preferences-modal-base-option'; export { default as NavigableRegion } from './navigable-region'; diff --git a/packages/interface/src/components/preferences-modal-section/index.js b/packages/interface/src/components/preferences-modal-section/index.js deleted file mode 100644 index 8ea2ca2652d6d..0000000000000 --- a/packages/interface/src/components/preferences-modal-section/index.js +++ /dev/null @@ -1,19 +0,0 @@ -const Section = ( { description, title, children } ) => ( -
        - -

        - { title } -

        - { description && ( -

        - { description } -

        - ) } -
        -
        - { children } -
        -
        -); - -export default Section; diff --git a/packages/interface/src/style.scss b/packages/interface/src/style.scss index be111640a0a79..e6950de411156 100644 --- a/packages/interface/src/style.scss +++ b/packages/interface/src/style.scss @@ -4,7 +4,3 @@ @import "./components/interface-skeleton/style.scss"; @import "./components/more-menu-dropdown/style.scss"; @import "./components/pinned-items/style.scss"; -@import "./components/preferences-modal/style.scss"; -@import "./components/preferences-modal-tabs/style.scss"; -@import "./components/preferences-modal-section/style.scss"; -@import "./components/preferences-modal-base-option/style.scss"; diff --git a/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js b/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js index 5def03bb348fa..01986803579b6 100644 --- a/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js +++ b/packages/preferences-persistence/src/migrations/preferences-package-data/convert-editor-settings.js @@ -10,6 +10,7 @@ export default function convertEditorSettings( data ) { 'editorMode', 'fixedToolbar', 'focusMode', + 'hiddenBlockTypes', 'inactivePanels', 'keepCaretInsideBlock', 'mostUsedBlocks', diff --git a/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js b/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js index 5ea8cd45e0676..fefde528402ca 100644 --- a/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js +++ b/packages/preferences-persistence/src/migrations/preferences-package-data/test/index.js @@ -45,6 +45,10 @@ describe( 'convertPreferencesPackageData', () => { "core": { "editorMode": "visual", "fixedToolbar": true, + "hiddenBlockTypes": [ + "core/audio", + "core/cover", + ], "inactivePanels": [], "openPanels": [ "post-status", @@ -56,10 +60,6 @@ describe( 'convertPreferencesPackageData', () => { }, "core/edit-post": { "fullscreenMode": false, - "hiddenBlockTypes": [ - "core/audio", - "core/cover", - ], "pinnedItems": { "my-sidebar-plugin/title-sidebar": false, }, diff --git a/packages/preferences/package.json b/packages/preferences/package.json index dc44878577aaf..e1164a7a1306c 100644 --- a/packages/preferences/package.json +++ b/packages/preferences/package.json @@ -31,10 +31,12 @@ "@babel/runtime": "^7.16.0", "@wordpress/a11y": "file:../a11y", "@wordpress/components": "file:../components", + "@wordpress/compose": "file:../compose", "@wordpress/data": "file:../data", "@wordpress/element": "file:../element", "@wordpress/i18n": "file:../i18n", "@wordpress/icons": "file:../icons", + "@wordpress/private-apis": "file:../private-apis", "classnames": "^2.3.1" }, "peerDependencies": { diff --git a/packages/interface/src/components/preferences-modal-base-option/README.md b/packages/preferences/src/components/preference-base-option/README.md similarity index 69% rename from packages/interface/src/components/preferences-modal-base-option/README.md rename to packages/preferences/src/components/preference-base-option/README.md index 03c89960b6850..8ff438ba322dc 100644 --- a/packages/interface/src/components/preferences-modal-base-option/README.md +++ b/packages/preferences/src/components/preference-base-option/README.md @@ -1,17 +1,15 @@ -#__unstablePreferencesModalBaseOption +# PreferenceBaseOption -`__unstablePreferencesModalBaseOption` renders a toggle meant to be used with `PreferencesModal`. +`PreferenceBaseOption` renders a toggle meant to be used within `PreferencesModal`. This component implements a `ToggleControl` component from the `@wordpress/components` package. -**It is an unstable component so is subject to breaking changes at any moment. Use at own risk.** - ## Example ```jsx function MyEditorPreferencesOption() { return ( - <__unstablePreferencesModalBaseOption + ) } - + ) } ``` diff --git a/packages/interface/src/components/preferences-modal-base-option/index.js b/packages/preferences/src/components/preference-base-option/index.js similarity index 86% rename from packages/interface/src/components/preferences-modal-base-option/index.js rename to packages/preferences/src/components/preference-base-option/index.js index 92346630d98aa..f2d1b4936c97f 100644 --- a/packages/interface/src/components/preferences-modal-base-option/index.js +++ b/packages/preferences/src/components/preference-base-option/index.js @@ -5,7 +5,7 @@ import { ToggleControl } from '@wordpress/components'; function BaseOption( { help, label, isChecked, onChange, children } ) { return ( -
        +
        {}, ...remainingProps @@ -21,11 +25,14 @@ export default function EnableFeature( props ) { onToggle(); toggle( scope, featureName ); }; + return ( - ); } + +export default PreferenceToggleControl; diff --git a/packages/interface/src/components/preferences-modal-section/README.md b/packages/preferences/src/components/preferences-modal-section/README.md similarity index 94% rename from packages/interface/src/components/preferences-modal-section/README.md rename to packages/preferences/src/components/preferences-modal-section/README.md index 6e78ca05c0aa8..4b032b5db0497 100644 --- a/packages/interface/src/components/preferences-modal-section/README.md +++ b/packages/preferences/src/components/preferences-modal-section/README.md @@ -1,3 +1,4 @@ +# PreferencesModalSection `PreferencesModalSection` renders a section (as a fieldset) meant to be used with `PreferencesModal`. diff --git a/packages/preferences/src/components/preferences-modal-section/index.js b/packages/preferences/src/components/preferences-modal-section/index.js new file mode 100644 index 0000000000000..1a3f6f64ef4e7 --- /dev/null +++ b/packages/preferences/src/components/preferences-modal-section/index.js @@ -0,0 +1,15 @@ +const Section = ( { description, title, children } ) => ( +
        + +

        { title }

        + { description && ( +

        + { description } +

        + ) } +
        +
        { children }
        +
        +); + +export default Section; diff --git a/packages/interface/src/components/preferences-modal-section/style.scss b/packages/preferences/src/components/preferences-modal-section/style.scss similarity index 50% rename from packages/interface/src/components/preferences-modal-section/style.scss rename to packages/preferences/src/components/preferences-modal-section/style.scss index a1259af3430d5..2f6480dd71dab 100644 --- a/packages/interface/src/components/preferences-modal-section/style.scss +++ b/packages/preferences/src/components/preferences-modal-section/style.scss @@ -1,4 +1,4 @@ -.interface-preferences-modal__section { +.preferences-modal__section { margin: 0 0 2.5rem 0; &:last-child { @@ -6,23 +6,23 @@ } } -.interface-preferences-modal__section-legend { +.preferences-modal__section-legend { margin-bottom: $grid-unit-10; } -.interface-preferences-modal__section-title { +.preferences-modal__section-title { font-size: 0.9rem; font-weight: 600; margin-top: 0; } -.interface-preferences-modal__section-description { +.preferences-modal__section-description { margin: -$grid-unit-10 0 $grid-unit-10 0; font-size: $helptext-font-size; font-style: normal; color: $gray-700; } -.interface-preferences-modal__section:has(.interface-preferences-modal__section-content:empty) { +.preferences-modal__section:has(.preferences-modal__section-content:empty) { display: none; } diff --git a/packages/interface/src/components/preferences-modal-tabs/README.md b/packages/preferences/src/components/preferences-modal-tabs/README.md similarity index 100% rename from packages/interface/src/components/preferences-modal-tabs/README.md rename to packages/preferences/src/components/preferences-modal-tabs/README.md diff --git a/packages/interface/src/components/preferences-modal-tabs/index.js b/packages/preferences/src/components/preferences-modal-tabs/index.js similarity index 93% rename from packages/interface/src/components/preferences-modal-tabs/index.js rename to packages/preferences/src/components/preferences-modal-tabs/index.js index 985d963227257..4797b50985f03 100644 --- a/packages/interface/src/components/preferences-modal-tabs/index.js +++ b/packages/preferences/src/components/preferences-modal-tabs/index.js @@ -25,7 +25,7 @@ import { isRTL, __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { unlock } from '../../../lock-unlock'; +import { unlock } from '../../lock-unlock'; const { Tabs } = unlock( componentsPrivateApis ); @@ -64,7 +64,7 @@ export default function PreferencesModalTabs( { sections } ) { // We render different components based on the viewport size. if ( isLargeViewport ) { modalContent = ( -
        +
        - + { tabs.map( ( tab ) => ( { tab.title } @@ -87,7 +87,7 @@ export default function PreferencesModalTabs( { sections } ) { { sectionsContentMap[ tab.name ] || null } @@ -100,7 +100,7 @@ export default function PreferencesModalTabs( { sections } ) { modalContent = ( diff --git a/packages/interface/src/components/preferences-modal-tabs/style.scss b/packages/preferences/src/components/preferences-modal-tabs/style.scss similarity index 85% rename from packages/interface/src/components/preferences-modal-tabs/style.scss rename to packages/preferences/src/components/preferences-modal-tabs/style.scss index f598545d69c25..d3afd4174cd0c 100644 --- a/packages/interface/src/components/preferences-modal-tabs/style.scss +++ b/packages/preferences/src/components/preferences-modal-tabs/style.scss @@ -1,6 +1,6 @@ $vertical-tabs-width: 160px; -.interface-preferences__tabs-tablist { +.preferences__tabs-tablist { position: absolute; top: $header-height + $grid-unit-30; // Aligns button text instead of button box. @@ -9,7 +9,7 @@ $vertical-tabs-width: 160px; } -.interface-preferences__tabs-tab { +.preferences__tabs-tab { border-radius: $radius-block-ui; font-weight: 400; @@ -34,7 +34,7 @@ $vertical-tabs-width: 160px; } } -.interface-preferences__tabs-tabpanel { +.preferences__tabs-tabpanel { padding-left: $grid-unit-30; margin-left: $vertical-tabs-width; } @@ -42,7 +42,7 @@ $vertical-tabs-width: 160px; @media (max-width: #{ ($break-medium - 1) }) { // Keep the navigator component from overflowing the modal content area // to ensure that sticky position elements stick where intended. - .interface-preferences__provider { + .preferences__provider { height: 100%; } } diff --git a/packages/interface/src/components/preferences-modal/README.md b/packages/preferences/src/components/preferences-modal/README.md similarity index 96% rename from packages/interface/src/components/preferences-modal/README.md rename to packages/preferences/src/components/preferences-modal/README.md index 4327a59a7905a..4814fc12e5898 100644 --- a/packages/interface/src/components/preferences-modal/README.md +++ b/packages/preferences/src/components/preferences-modal/README.md @@ -4,7 +4,7 @@ This component implements a `Modal` component from the `@wordpress/components` package. -Sections passed to this component should use `PreferencesModalSection` component from the `@wordpress/interface` package. +Sections passed to this component should use `PreferencesModalSection` component from the `@wordpress/preferences` package. ## Example diff --git a/packages/interface/src/components/preferences-modal/index.js b/packages/preferences/src/components/preferences-modal/index.js similarity index 87% rename from packages/interface/src/components/preferences-modal/index.js rename to packages/preferences/src/components/preferences-modal/index.js index 99bb9d3c25dcf..8dc4f1020c036 100644 --- a/packages/interface/src/components/preferences-modal/index.js +++ b/packages/preferences/src/components/preferences-modal/index.js @@ -7,7 +7,7 @@ import { __ } from '@wordpress/i18n'; export default function PreferencesModal( { closeModal, children } ) { return ( diff --git a/packages/interface/src/components/preferences-modal/style.scss b/packages/preferences/src/components/preferences-modal/style.scss similarity index 94% rename from packages/interface/src/components/preferences-modal/style.scss rename to packages/preferences/src/components/preferences-modal/style.scss index 4e70f9f817e64..ca185ac4258e0 100644 --- a/packages/interface/src/components/preferences-modal/style.scss +++ b/packages/preferences/src/components/preferences-modal/style.scss @@ -1,4 +1,4 @@ -.interface-preferences-modal { +.preferences-modal { // To keep modal dimensions consistent as subsections are navigated, width // and height are used instead of max-(width/height). @include break-small() { diff --git a/packages/preferences/src/index.js b/packages/preferences/src/index.js index 72531a0824c17..856b93b21105d 100644 --- a/packages/preferences/src/index.js +++ b/packages/preferences/src/index.js @@ -1,2 +1,3 @@ export * from './components'; export { store } from './store'; +export * from './private-apis'; diff --git a/packages/preferences/src/lock-unlock.js b/packages/preferences/src/lock-unlock.js new file mode 100644 index 0000000000000..981f244881ed0 --- /dev/null +++ b/packages/preferences/src/lock-unlock.js @@ -0,0 +1,9 @@ +/** + * WordPress dependencies + */ +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + 'I know using unstable features means my theme or plugin will inevitably break in the next version of WordPress.', + '@wordpress/preferences' + ); diff --git a/packages/preferences/src/private-apis.js b/packages/preferences/src/private-apis.js new file mode 100644 index 0000000000000..110d134f86b82 --- /dev/null +++ b/packages/preferences/src/private-apis.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import PreferenceBaseOption from './components/preference-base-option'; +import PreferenceToggleControl from './components/preference-toggle-control'; +import PreferencesModal from './components/preferences-modal'; +import PreferencesModalSection from './components/preferences-modal-section'; +import PreferencesModalTabs from './components/preferences-modal-tabs'; +import { lock } from './lock-unlock'; + +export const privateApis = {}; +lock( privateApis, { + PreferenceBaseOption, + PreferenceToggleControl, + PreferencesModal, + PreferencesModalSection, + PreferencesModalTabs, +} ); diff --git a/packages/preferences/src/store/index.js b/packages/preferences/src/store/index.js index 0c2421966a0d7..7e57dac5e712c 100644 --- a/packages/preferences/src/store/index.js +++ b/packages/preferences/src/store/index.js @@ -12,7 +12,7 @@ import * as selectors from './selectors'; import { STORE_NAME } from './constants'; /** - * Store definition for the interface namespace. + * Store definition for the preferences namespace. * * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/data/README.md#createReduxStore * diff --git a/packages/preferences/src/style.scss b/packages/preferences/src/style.scss new file mode 100644 index 0000000000000..c6769c44b00ba --- /dev/null +++ b/packages/preferences/src/style.scss @@ -0,0 +1,4 @@ +@import "./components/preference-base-option/style.scss"; +@import "./components/preferences-modal/style.scss"; +@import "./components/preferences-modal-tabs/style.scss"; +@import "./components/preferences-modal-section/style.scss"; diff --git a/packages/private-apis/src/implementation.js b/packages/private-apis/src/implementation.js index 4195991381d02..a31fd91ce094d 100644 --- a/packages/private-apis/src/implementation.js +++ b/packages/private-apis/src/implementation.js @@ -27,6 +27,7 @@ const CORE_MODULES_USING_PRIVATE_APIS = [ '@wordpress/format-library', '@wordpress/interface', '@wordpress/patterns', + '@wordpress/preferences', '@wordpress/reusable-blocks', '@wordpress/router', '@wordpress/dataviews', From 295ebf53c868ae8b71f0baef08041ee1fc5e7b63 Mon Sep 17 00:00:00 2001 From: Fabian Todt Date: Fri, 12 Jan 2024 14:23:05 +0100 Subject: [PATCH 085/296] Better navigation link variations for post types / taxonomies (#56100) * better naming + code comments * add unit tests for private post types / taxonomies * handle unregistering of post types / taxonomies * Fix typo in code comment Co-authored-by: Andrei Draganescu --------- Co-authored-by: Andrei Draganescu --- .../src/navigation-link/index.php | 68 +++++++++-- .../block-navigation-link-variations-test.php | 112 +++++++++++++++++- 2 files changed, 165 insertions(+), 15 deletions(-) diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index 1165ce94b5921..f7c26afd1f88d 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -1,6 +1,6 @@ get_registered( 'core/navigation-link' ); @@ -341,6 +341,31 @@ function register_block_core_navigation_link_variation( $variation ) { $navigation_block_type->variations[] = $variation; } +/** + * Unregister a variation for a post type / taxonomy for the navigation link block. + * + * @param string $name Name of the post type / taxonomy (which was used as variation name). + * @return void + */ +function block_core_navigation_link_unregister_variation( $name ) { + // Directly get the variations from the registered block type + // because there's no server side (un)registration for variations (see #47170). + $navigation_block_type = WP_Block_Type_Registry::get_instance()->get_registered( 'core/navigation-link' ); + // If the block is not registered (yet), there's no need to remove a variation. + if ( ! $navigation_block_type || empty( $navigation_block_type->variations ) ) { + return; + } + // Search for the variation and remove it from the array. + foreach ( $navigation_block_type->variations as $i => $variation ) { + if ( $variation['name'] === $name ) { + unset( $navigation_block_type->variations[ $i ] ); + break; + } + } + // Reindex array after removing one variation. + $navigation_block_type->variations = array_values( $navigation_block_type->variations ); +} + /** * Register the navigation link block. * @@ -393,21 +418,24 @@ function register_block_core_navigation_link() { add_action( 'init', 'register_block_core_navigation_link' ); // Register actions for all post types and taxonomies, to add variations when they are registered. // All post types/taxonomies registered before register_block_core_navigation_link, will be handled by that function. -add_action( 'registered_post_type', 'register_block_core_navigation_link_post_type_variation', 10, 2 ); -add_action( 'registered_taxonomy', 'register_block_core_navigation_link_taxonomy_variation', 10, 3 ); +add_action( 'registered_post_type', 'block_core_navigation_link_register_post_type_variation', 10, 2 ); +add_action( 'registered_taxonomy', 'block_core_navigation_link_register_taxonomy_variation', 10, 3 ); +// Handle unregistering of post types and taxonomies and remove the variations. +add_action( 'unregistered_post_type', 'block_core_navigation_link_unregister_post_type_variation' ); +add_action( 'unregistered_taxonomy', 'block_core_navigation_link_unregister_taxonomy_variation' ); /** * Register custom post type variations for navigation link on post type registration * Handles all post types registered after the block is registered in register_navigation_link_post_type_variations * - * @param string $post_type The post type name passed from registered_post_type filter. + * @param string $post_type The post type name passed from registered_post_type action hook. * @param WP_Post_Type $post_type_object The post type object passed from registered_post_type. * @return void */ -function register_block_core_navigation_link_post_type_variation( $post_type, $post_type_object ) { +function block_core_navigation_link_register_post_type_variation( $post_type, $post_type_object ) { if ( $post_type_object->show_in_nav_menus ) { $variation = build_variation_for_navigation_link( $post_type_object, 'post-type' ); - register_block_core_navigation_link_variation( $variation ); + block_core_navigation_link_register_variation( $variation ); } } @@ -420,9 +448,29 @@ function register_block_core_navigation_link_post_type_variation( $post_type, $p * @param array $args Array of taxonomy registration arguments. * @return void */ -function register_block_core_navigation_link_taxonomy_variation( $taxonomy, $object_type, $args ) { +function block_core_navigation_link_register_taxonomy_variation( $taxonomy, $object_type, $args ) { if ( isset( $args['show_in_nav_menus'] ) && $args['show_in_nav_menus'] ) { $variation = build_variation_for_navigation_link( (object) $args, 'post-type' ); - register_block_core_navigation_link_variation( $variation ); + block_core_navigation_link_register_variation( $variation ); } } + +/** + * Unregisters a custom post type variation for navigation link on post type unregistration. + * + * @param string $post_type The post type name passed from unregistered_post_type action hook. + * @return void + */ +function block_core_navigation_link_unregister_post_type_variation( $post_type ) { + block_core_navigation_link_unregister_variation( $post_type ); +} + +/** + * Unregisters a custom taxonomy variation for navigation link on taxonomy unregistration. + * + * @param string $taxonomy The taxonomy name passed from unregistered_taxonomy action hook. + * @return void + */ +function block_core_navigation_link_unregister_taxonomy_variation( $taxonomy ) { + block_core_navigation_link_unregister_variation( $taxonomy ); +} diff --git a/phpunit/blocks/block-navigation-link-variations-test.php b/phpunit/blocks/block-navigation-link-variations-test.php index c5082e0f4c878..8eef5c393148b 100644 --- a/phpunit/blocks/block-navigation-link-variations-test.php +++ b/phpunit/blocks/block-navigation-link-variations-test.php @@ -1,6 +1,6 @@ true, ) ); + register_post_type( + 'private_custom_book', + array( + 'labels' => array( + 'item_link' => 'Custom Book', + ), + 'public' => false, + 'show_in_rest' => true, + 'show_in_nav_menus' => false, + ) + ); register_taxonomy( 'book_type', 'custom_book', @@ -35,40 +46,131 @@ public function set_up() { 'show_in_nav_menus' => true, ) ); + register_taxonomy( + 'private_book_type', + 'private_custom_book', + array( + 'labels' => array( + 'item_link' => 'Book Type', + ), + 'show_in_nav_menus' => false, + ) + ); } public function tear_down() { unregister_post_type( 'custom_book' ); + unregister_post_type( 'private_custom_book' ); unregister_taxonomy( 'book_type' ); + unregister_taxonomy( 'private_book_type' ); + unregister_post_type( 'temp_custom_book' ); + unregister_taxonomy( 'temp_book_type' ); parent::tear_down(); } /** - * @covers ::register_block_core_navigation_link_post_type_variation + * @covers ::block_core_navigation_link_register_post_type_variation */ public function test_navigation_link_variations_custom_post_type() { $registry = WP_Block_Type_Registry::get_instance(); $nav_link_block = $registry->get_registered( 'core/navigation-link' ); $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); $variation = $this->get_variation_by_name( 'custom_book', $nav_link_block->variations ); - $this->assertIsArray( $variation, 'Block variation is not an array' ); + $this->assertIsArray( $variation, 'Block variation does not exist' ); $this->assertArrayHasKey( 'title', $variation, 'Block variation has no title' ); $this->assertEquals( 'Custom Book', $variation['title'], 'Variation title is different than the post type label' ); } /** - * @covers ::register_block_core_navigation_link_taxonomy_variation + * @covers ::block_core_navigation_link_register_post_type_variation + */ + public function test_navigation_link_variations_private_custom_post_type() { + $registry = WP_Block_Type_Registry::get_instance(); + $nav_link_block = $registry->get_registered( 'core/navigation-link' ); + $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); + $variation = $this->get_variation_by_name( 'private_custom_book', $nav_link_block->variations ); + $this->assertEmpty( $variation, 'Block variation for private post type exists.' ); + } + + /** + * @covers ::block_core_navigation_link_register_taxonomy_variation */ public function test_navigation_link_variations_custom_taxonomy() { $registry = WP_Block_Type_Registry::get_instance(); $nav_link_block = $registry->get_registered( 'core/navigation-link' ); $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); $variation = $this->get_variation_by_name( 'book_type', $nav_link_block->variations ); - $this->assertIsArray( $variation, 'Block variation is not an array' ); + $this->assertIsArray( $variation, 'Block variation does not exist' ); $this->assertArrayHasKey( 'title', $variation, 'Block variation has no title' ); $this->assertEquals( 'Book Type', $variation['title'], 'Variation title is different than the post type label' ); } + /** + * @covers ::block_core_navigation_link_register_taxonomy_variation + */ + public function test_navigation_link_variations_private_custom_taxonomy() { + $registry = WP_Block_Type_Registry::get_instance(); + $nav_link_block = $registry->get_registered( 'core/navigation-link' ); + $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); + $variation = $this->get_variation_by_name( 'private_book_type', $nav_link_block->variations ); + $this->assertEmpty( $variation, 'Block variation for private taxonomy exists.' ); + } + + /** + * @covers ::block_core_navigation_link_unregister_post_type_variation + */ + public function test_navigation_link_variations_unregister_post_type() { + register_post_type( + 'temp_custom_book', + array( + 'labels' => array( + 'item_link' => 'Custom Book', + ), + 'public' => true, + 'show_in_rest' => true, + 'show_in_nav_menus' => true, + ) + ); + + $registry = WP_Block_Type_Registry::get_instance(); + $nav_link_block = $registry->get_registered( 'core/navigation-link' ); + $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); + $variation = $this->get_variation_by_name( 'temp_custom_book', $nav_link_block->variations ); + $this->assertIsArray( $variation, 'Block variation does not exist' ); + + unregister_post_type( 'temp_custom_book' ); + + $variation = $this->get_variation_by_name( 'temp_custom_book', $nav_link_block->variations ); + $this->assertEmpty( $variation, 'Block variation still exists' ); + } + + /** + * @covers ::block_core_navigation_link_unregister_taxonomy_variation + */ + public function test_navigation_link_variations_unregister_taxonomy() { + register_taxonomy( + 'temp_book_type', + 'custom_book', + array( + 'labels' => array( + 'item_link' => 'Book Type', + ), + 'show_in_nav_menus' => true, + ) + ); + + $registry = WP_Block_Type_Registry::get_instance(); + $nav_link_block = $registry->get_registered( 'core/navigation-link' ); + $this->assertNotEmpty( $nav_link_block->variations, 'Block has no variations' ); + $variation = $this->get_variation_by_name( 'temp_book_type', $nav_link_block->variations ); + $this->assertIsArray( $variation, 'Block variation does not exist' ); + + unregister_taxonomy( 'temp_book_type' ); + + $variation = $this->get_variation_by_name( 'temp_book_type', $nav_link_block->variations ); + $this->assertEmpty( $variation, 'Block variation still exists' ); + } + /** * Get a variation by its name from an array of variations. * From 7e74cc84c2f641f1c4d4e71171a8d02728fd10fb Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Fri, 12 Jan 2024 23:39:04 +1000 Subject: [PATCH 086/296] fix: Add space to text string to workaround RN text display bug (#57783) See also: https://github.com/facebook/react-native/issues/21729 --- .../editor/src/components/offline-status/index.native.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/editor/src/components/offline-status/index.native.js b/packages/editor/src/components/offline-status/index.native.js index b136fdae1d5b2..2e514abb6e091 100644 --- a/packages/editor/src/components/offline-status/index.native.js +++ b/packages/editor/src/components/offline-status/index.native.js @@ -90,10 +90,8 @@ const OfflineStatus = () => { ) } style={ containerStyle } > - - - { __( 'Working Offline' ) } - + + { __( 'Working Offline' ) } ) : null; }; From f461b177d87a865ecbc9f6a064f5f06f5416b354 Mon Sep 17 00:00:00 2001 From: Carolina Nymark Date: Fri, 12 Jan 2024 14:45:17 +0100 Subject: [PATCH 087/296] Add more taxonomy options to the post navigation link (#48912) * Add 3 options to the post navigation link * Update core__post-navigation-link.json * Support custom post types and custom taxonomies * fix CS issues * Update index.php * Update index.php * Update edit.js * try to fix spacing in core-blocks.md * Remove the taxonomy filter toggle and update the PHP conditions. * Update core__post-navigation-link.json * Update help text about excluding terms * Update help text again. * Remove the exclude terms feature, move filter to advanced panel * Try to fix CS issues * Update docs * Try to fix CS issues * Use context to get the postType --- docs/reference-guides/core-blocks.md | 2 +- .../src/post-navigation-link/block.json | 8 +++ .../src/post-navigation-link/edit.js | 64 ++++++++++++++++++- .../src/post-navigation-link/index.php | 20 +++++- .../blocks/core__post-navigation-link.json | 3 +- 5 files changed, 91 insertions(+), 6 deletions(-) diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index c05cdd3eb009b..84396caefbf47 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -639,7 +639,7 @@ Displays the next or previous post link that is adjacent to the current post. ([ - **Name:** core/post-navigation-link - **Category:** theme - **Supports:** color (background, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ -- **Attributes:** arrow, label, linkLabel, showTitle, textAlign, type +- **Attributes:** arrow, inSameTerm, label, linkLabel, showTitle, taxonomy, textAlign, type ## Post Template diff --git a/packages/block-library/src/post-navigation-link/block.json b/packages/block-library/src/post-navigation-link/block.json index e1b6d4fa90a40..61d42f0e55f20 100644 --- a/packages/block-library/src/post-navigation-link/block.json +++ b/packages/block-library/src/post-navigation-link/block.json @@ -28,8 +28,16 @@ "arrow": { "type": "string", "default": "none" + }, + "inSameTerm": { + "type": "boolean" + }, + "taxonomy": { + "type": "string", + "default": "" } }, + "usesContext": [ "postType" ], "supports": { "reusable": false, "html": false, diff --git a/packages/block-library/src/post-navigation-link/edit.js b/packages/block-library/src/post-navigation-link/edit.js index f899788428bcd..464531f9d4546 100644 --- a/packages/block-library/src/post-navigation-link/edit.js +++ b/packages/block-library/src/post-navigation-link/edit.js @@ -10,6 +10,7 @@ import { __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption, ToggleControl, + SelectControl, PanelBody, } from '@wordpress/components'; import { @@ -20,9 +21,20 @@ import { useBlockProps, } from '@wordpress/block-editor'; import { __, _x } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; export default function PostNavigationLinkEdit( { - attributes: { type, label, showTitle, textAlign, linkLabel, arrow }, + context: { postType }, + attributes: { + type, + label, + showTitle, + textAlign, + linkLabel, + arrow, + taxonomy, + }, setAttributes, } ) { const isNext = type === 'next'; @@ -47,6 +59,40 @@ export default function PostNavigationLinkEdit( { [ `has-text-align-${ textAlign }` ]: textAlign, } ), } ); + + const taxonomies = useSelect( + ( select ) => { + const { getTaxonomies } = select( coreStore ); + const filteredTaxonomies = getTaxonomies( { + type: postType, + per_page: -1, + context: 'view', + } ); + return filteredTaxonomies; + }, + [ postType ] + ); + const getTaxonomyOptions = () => { + const selectOption = { + label: __( 'Unfiltered' ), + value: '', + }; + const taxonomyOptions = ( taxonomies ?? [] ) + .filter( + ( tax ) => + tax.slug !== 'nav_menu' && + tax.slug !== 'wp_pattern_category' + ) + .map( ( item ) => { + return { + value: item.slug, + label: item.name, + }; + } ); + + return [ selectOption, ...taxonomyOptions ]; + }; + return ( <> @@ -114,6 +160,22 @@ export default function PostNavigationLinkEdit( { + + + setAttributes( { + taxonomy: value, + inSameTerm: value === '' ? false : true, + } ) + } + help={ __( + 'Only link to posts that have the same taxonomy terms as the current post. For example the same tags or categories.' + ) } + /> + %2$s
        ', $wrapper_attributes, diff --git a/test/integration/fixtures/blocks/core__post-navigation-link.json b/test/integration/fixtures/blocks/core__post-navigation-link.json index 9689f956f1150..b226c04855e35 100644 --- a/test/integration/fixtures/blocks/core__post-navigation-link.json +++ b/test/integration/fixtures/blocks/core__post-navigation-link.json @@ -6,7 +6,8 @@ "type": "next", "showTitle": false, "linkLabel": false, - "arrow": "none" + "arrow": "none", + "taxonomy": "" }, "innerBlocks": [] } From 0b4edfa5f31f99e7eecaa28e95ffc157b2b64046 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Fri, 12 Jan 2024 14:46:12 +0100 Subject: [PATCH 088/296] Fix parent selector button focus style and metrics (#57728) * Fix parent selector button focus style and positioning. * Fix left and right padding. * Fix block switcher font size. --- .../src/components/block-switcher/style.scss | 4 ---- .../src/components/block-toolbar/style.scss | 9 ++++----- .../src/components/block-tools/style.scss | 15 ++++++++------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/block-switcher/style.scss b/packages/block-editor/src/components/block-switcher/style.scss index b8503a0811e42..d30c6587a520f 100644 --- a/packages/block-editor/src/components/block-switcher/style.scss +++ b/packages/block-editor/src/components/block-switcher/style.scss @@ -35,10 +35,6 @@ } } -.show-icon-labels .block-editor-block-toolbar .block-editor-block-switcher .components-button.has-icon::after { - font-size: 14px; -} - .components-button.block-editor-block-switcher__no-switcher-icon { display: flex; diff --git a/packages/block-editor/src/components/block-toolbar/style.scss b/packages/block-editor/src/components/block-toolbar/style.scss index fc18dba7cc2cf..cff75008d355f 100644 --- a/packages/block-editor/src/components/block-toolbar/style.scss +++ b/packages/block-editor/src/components/block-toolbar/style.scss @@ -98,6 +98,10 @@ .block-editor-block-parent-selector { position: relative; + // Must use the same negative margins of the .components-toolbar-group + // https://github.com/WordPress/gutenberg/blob/73a4716f429b5dce0190638049f5bd30f0b242f6/packages/block-editor/src/components/block-toolbar/style.scss#L32-L33 + margin-top: -$border-width; + margin-bottom: -$border-width; // Parent selector dot divider &::after { @@ -108,11 +112,6 @@ bottom: $grid-unit-20; } } - - .block-editor-block-parent-selector__button { - position: relative; - top: -1px; - } } diff --git a/packages/block-editor/src/components/block-tools/style.scss b/packages/block-editor/src/components/block-tools/style.scss index 3371d795e6c03..c5920bdf2f7d3 100644 --- a/packages/block-editor/src/components/block-tools/style.scss +++ b/packages/block-editor/src/components/block-tools/style.scss @@ -235,6 +235,11 @@ padding-right: 6px; padding-left: 6px; background-color: $white; + + .show-icon-labels & { + padding-right: $grid-unit-15; + padding-left: $grid-unit-15; + } } } @@ -242,15 +247,11 @@ .show-icon-labels & { .block-editor-block-parent-selector { - position: static; - margin-top: -$border-width; + position: relative; + left: auto; margin-left: -$border-width; - margin-bottom: -$border-width; - - .block-editor-block-parent-selector__button { - position: static; - } } + .block-editor-block-mover__move-button-container, .block-editor-block-toolbar__block-controls .block-editor-block-mover { border-left: 1px solid $gray-900; From e7128cd0eb382a1566c3791721f210e83d9d5435 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Fri, 12 Jan 2024 23:06:27 +0900 Subject: [PATCH 089/296] LinkControl: Remove unnecessary right padding of input fields (#57784) --- .../src/components/link-control/search-input.js | 10 +--------- .../src/components/link-control/style.scss | 11 ++++------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/link-control/search-input.js b/packages/block-editor/src/components/link-control/search-input.js index d6023b9220d63..70cd896ccabfa 100644 --- a/packages/block-editor/src/components/link-control/search-input.js +++ b/packages/block-editor/src/components/link-control/search-input.js @@ -1,7 +1,3 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; /** * WordPress dependencies */ @@ -119,10 +115,6 @@ const LinkControlSearchInput = forwardRef( } }; - const inputClasses = classnames( className, { - // 'has-no-label': ! hideLabelFromVision, - } ); - return (
        Date: Fri, 12 Jan 2024 14:35:01 +0000 Subject: [PATCH 090/296] Data view list layout: Fix thumbnail dimensions (#57774) --- packages/dataviews/src/style.scss | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/dataviews/src/style.scss b/packages/dataviews/src/style.scss index d934ea0df62d0..680fcf6a480d4 100644 --- a/packages/dataviews/src/style.scss +++ b/packages/dataviews/src/style.scss @@ -335,11 +335,18 @@ } .dataviews-view-list__media-wrapper { - min-width: $grid-unit-40; + width: $grid-unit-40; height: $grid-unit-40; border-radius: $grid-unit-05; overflow: hidden; position: relative; + flex-shrink: 0; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } &::after { content: ""; From 858bc3b7c216b1cab8d00405cd5e0ece452d2bae Mon Sep 17 00:00:00 2001 From: Jon Surrell Date: Fri, 12 Jan 2024 16:47:09 +0100 Subject: [PATCH 091/296] DependencyExtractionWebpackPlugin: Throw when using scripts from modules (#57795) WordPress Script dependencies are not currently available as dependencies of WordPress Modules. Using e.g. lodash or @wordpress/api-fetch in a module build would result in bundling that dependency. For a package like lodash that's undesirable but should work. However, many @wordpress/* packages are not intended to be bundle or duplicated and will not work as expected. It's likely an error to use WordPress Scripts inside modules at this time. --------- Co-authored-by: Luis Herranz --- .../lib/index.js | 50 ++++++---- .../lib/util.js | 16 ++- .../test/__snapshots__/build.js.snap | 99 ++++++++++++++++--- .../test/build.js | 10 ++ .../fixtures/combine-assets/webpack.config.js | 4 +- .../test/fixtures/error/index.js | 10 ++ .../test/fixtures/error/webpack.config.js | 15 +++ .../webpack.config.js | 4 +- .../has-extension-suffix/webpack.config.js | 4 +- .../webpack.config.js | 4 +- .../option-output-filename/webpack.config.js | 4 +- .../output-format-json/webpack.config.js | 7 +- .../runtime-chunk-single/webpack.config.js | 4 +- .../fixtures/style-imports/webpack.config.js | 4 +- .../wordpress-interactivity/webpack.config.js | 8 +- .../wordpress-require/webpack.config.js | 4 +- .../test/fixtures/wordpress/webpack.config.js | 4 +- 17 files changed, 205 insertions(+), 46 deletions(-) create mode 100644 packages/dependency-extraction-webpack-plugin/test/fixtures/error/index.js create mode 100644 packages/dependency-extraction-webpack-plugin/test/fixtures/error/webpack.config.js diff --git a/packages/dependency-extraction-webpack-plugin/lib/index.js b/packages/dependency-extraction-webpack-plugin/lib/index.js index e8d9fc9bfee1d..344d4de9a0572 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/index.js +++ b/packages/dependency-extraction-webpack-plugin/lib/index.js @@ -62,28 +62,38 @@ class DependencyExtractionWebpackPlugin { externalizeWpDeps( { request }, callback ) { let externalRequest; - // Handle via options.requestToExternal(Module) first. - if ( this.useModules ) { - if ( typeof this.options.requestToExternalModule === 'function' ) { - externalRequest = - this.options.requestToExternalModule( request ); + try { + // Handle via options.requestToExternal(Module) first. + if ( this.useModules ) { + if ( + typeof this.options.requestToExternalModule === 'function' + ) { + externalRequest = + this.options.requestToExternalModule( request ); + + // requestToExternalModule allows a boolean shorthand + if ( externalRequest === false ) { + externalRequest = undefined; + } + if ( externalRequest === true ) { + externalRequest = request; + } + } + } else if ( typeof this.options.requestToExternal === 'function' ) { + externalRequest = this.options.requestToExternal( request ); } - } else if ( typeof this.options.requestToExternal === 'function' ) { - externalRequest = this.options.requestToExternal( request ); - } - - // Cascade to default if unhandled and enabled. - if ( - typeof externalRequest === 'undefined' && - this.options.useDefaults - ) { - externalRequest = this.useModules - ? defaultRequestToExternalModule( request ) - : defaultRequestToExternal( request ); - } - if ( this.useModules && externalRequest === true ) { - externalRequest = request; + // Cascade to default if unhandled and enabled. + if ( + typeof externalRequest === 'undefined' && + this.options.useDefaults + ) { + externalRequest = this.useModules + ? defaultRequestToExternalModule( request ) + : defaultRequestToExternal( request ); + } + } catch ( err ) { + return callback( err ); } if ( externalRequest ) { diff --git a/packages/dependency-extraction-webpack-plugin/lib/util.js b/packages/dependency-extraction-webpack-plugin/lib/util.js index 50b7eaa8db98f..39b2f4b4bca58 100644 --- a/packages/dependency-extraction-webpack-plugin/lib/util.js +++ b/packages/dependency-extraction-webpack-plugin/lib/util.js @@ -61,9 +61,13 @@ function defaultRequestToExternal( request ) { * * Currently only @wordpress/interactivity * + * Do not use the boolean shorthand here, it's only handled for the `requestToExternalModule` option. + * * @param {string} request Module request (the module name in `import from`) to be transformed - * @return {string|undefined} The resulting external definition. Return `undefined` - * to ignore the request. Return `string` to map the request to an external. This may simply be returning the request, e.g. `@wordpress/interactivity` maps to the external `@wordpress/interactivity`. + * @return {string|Error|undefined} The resulting external definition. + * - Return `undefined` to ignore the request (do not externalize). + * - Return `string` to map the request to an external. + * - Return `Error` to emit an error. */ function defaultRequestToExternalModule( request ) { if ( request === '@wordpress/interactivity' ) { @@ -73,6 +77,14 @@ function defaultRequestToExternalModule( request ) { // which forces @wordpress/interactivity imports to be hoisted to static imports. return `module ${ request }`; } + + const isWordPressScript = Boolean( defaultRequestToExternal( request ) ); + + if ( isWordPressScript ) { + throw new Error( + `Attempted to use WordPress script in a module: ${ request }, which is not supported yet.` + ); + } } /** diff --git a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap index 84ed7660802ba..a595a948ba108 100644 --- a/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap +++ b/packages/dependency-extraction-webpack-plugin/test/__snapshots__/build.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DependencyExtractionWebpackPlugin modules Webpack \`combine-assets\` should produce expected output: Asset file 'assets.php' should match snapshot 1`] = ` -" array('dependencies' => array('@wordpress/blob'), 'version' => 'b2c5cea79a32b3d91bf8', 'type' => 'module'), 'fileB.mjs' => array('dependencies' => array('@wordpress/token-list'), 'version' => '5c4197fd48811f25807f', 'type' => 'module')); +" array('dependencies' => array('@wordpress/blob', 'lodash'), 'version' => '4ab8cc4b6b7619053443', 'type' => 'module'), 'fileB.mjs' => array('dependencies' => array('@wordpress/token-list'), 'version' => '5c4197fd48811f25807f', 'type' => 'module')); " `; @@ -17,6 +17,11 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`combine-assets\` sh "request": "@wordpress/token-list", "userRequest": "@wordpress/token-list", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; @@ -65,8 +70,17 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`dynamic-import\` sh ] `; +exports[`DependencyExtractionWebpackPlugin modules Webpack \`error\` should produce expected output 1`] = ` +"ERROR in ./index.js 5:0-23 +Module not found: Error: Attempted to use WordPress script in a module: jquery, which is not supported yet. + +ERROR in ./index.js 6:23-55 +Module not found: Error: Attempted to use WordPress script in a module: @wordpress/api-fetch, which is not supported yet. +" +`; + exports[`DependencyExtractionWebpackPlugin modules Webpack \`function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => '2925e30449840a5a80f8', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => 'e325da3aa7dbb0c0c151', 'type' => 'module'); " `; @@ -77,11 +91,16 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`function-output-fil "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`has-extension-suffix\` should produce expected output: Asset file 'index.min.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => '26d6da43027f3522b0ca', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => '8308f1ac78c21f09c721', 'type' => 'module'); " `; @@ -92,6 +111,11 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`has-extension-suffi "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; @@ -130,7 +154,7 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`no-deps\` should pr exports[`DependencyExtractionWebpackPlugin modules Webpack \`no-deps\` should produce expected output: External modules should match snapshot 1`] = `[]`; exports[`DependencyExtractionWebpackPlugin modules Webpack \`option-function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => '2925e30449840a5a80f8', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => 'e325da3aa7dbb0c0c151', 'type' => 'module'); " `; @@ -141,11 +165,16 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`option-function-out "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`option-output-filename\` should produce expected output: Asset file 'main-foo.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => '2925e30449840a5a80f8', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => 'e325da3aa7dbb0c0c151', 'type' => 'module'); " `; @@ -156,12 +185,25 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`option-output-filen "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; -exports[`DependencyExtractionWebpackPlugin modules Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{"dependencies":[],"version":"34504aa793c63cd3d73a","type":"module"}"`; +exports[`DependencyExtractionWebpackPlugin modules Webpack \`output-format-json\` should produce expected output: Asset file 'main.asset.json' should match snapshot 1`] = `"{"dependencies":["lodash"],"version":"4e62c01516f9dab8041f","type":"module"}"`; -exports[`DependencyExtractionWebpackPlugin modules Webpack \`output-format-json\` should produce expected output: External modules should match snapshot 1`] = `[]`; +exports[`DependencyExtractionWebpackPlugin modules Webpack \`output-format-json\` should produce expected output: External modules should match snapshot 1`] = ` +[ + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, +] +`; exports[`DependencyExtractionWebpackPlugin modules Webpack \`overrides\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` " array('@wordpress/blob', '@wordpress/url', 'rxjs', 'rxjs/operators'), 'version' => '259fc706528651fc00c1', 'type' => 'module'); @@ -199,12 +241,12 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-singl `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'b.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => 'a0ec8ef279476bb51e19', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => 'e14dfa7260edaee86a85', 'type' => 'module'); " `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-single\` should produce expected output: Asset file 'runtime.asset.php' should match snapshot 1`] = ` -" array(), 'version' => '0bb8a9fae3dcfcc1ac38', 'type' => 'module'); +" array(), 'version' => 'e7402f5608a888d8fd66', 'type' => 'module'); " `; @@ -215,11 +257,16 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`runtime-chunk-singl "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`style-imports\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => '38479966fb62d588f05e', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => '22d18a3461df47fbaa79', 'type' => 'module'); " `; @@ -230,11 +277,16 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`style-imports\` sho "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`wordpress\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => '2925e30449840a5a80f8', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => 'e325da3aa7dbb0c0c151', 'type' => 'module'); " `; @@ -245,16 +297,26 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`wordpress\` should "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`wordpress-interactivity\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array(array('id' => '@wordpress/interactivity', 'type' => 'dynamic')), 'version' => 'f0242eb6da78af6ca4b8', 'type' => 'module'); +" array('lodash', array('id' => '@wordpress/interactivity', 'type' => 'dynamic')), 'version' => 'fcc07ce68574cdc2a6a5', 'type' => 'module'); " `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`wordpress-interactivity\` should produce expected output: External modules should match snapshot 1`] = ` [ + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, { "externalType": "module", "request": "@wordpress/interactivity", @@ -264,7 +326,7 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`wordpress-interacti `; exports[`DependencyExtractionWebpackPlugin modules Webpack \`wordpress-require\` should produce expected output: Asset file 'main.asset.php' should match snapshot 1`] = ` -" array('@wordpress/blob'), 'version' => '52c1849898b74d94f025', 'type' => 'module'); +" array('@wordpress/blob', 'lodash'), 'version' => 'f40de15d66b54da440d2', 'type' => 'module'); " `; @@ -275,6 +337,11 @@ exports[`DependencyExtractionWebpackPlugin modules Webpack \`wordpress-require\` "request": "@wordpress/blob", "userRequest": "@wordpress/blob", }, + { + "externalType": "import", + "request": "lodash", + "userRequest": "lodash", + }, ] `; @@ -363,6 +430,12 @@ exports[`DependencyExtractionWebpackPlugin scripts Webpack \`dynamic-import\` sh ] `; +exports[`DependencyExtractionWebpackPlugin scripts Webpack \`error\` should produce expected output 1`] = ` +"ERROR in main +Module not found: Error: Ensure error in script build. +" +`; + exports[`DependencyExtractionWebpackPlugin scripts Webpack \`function-output-filename\` should produce expected output: Asset file 'chunk--main--main.asset.php' should match snapshot 1`] = ` " array('lodash', 'wp-blob'), 'version' => 'fc2d750fc9e08c5749db'); " diff --git a/packages/dependency-extraction-webpack-plugin/test/build.js b/packages/dependency-extraction-webpack-plugin/test/build.js index 3b29d55caf2bb..0b28b679bba92 100644 --- a/packages/dependency-extraction-webpack-plugin/test/build.js +++ b/packages/dependency-extraction-webpack-plugin/test/build.js @@ -72,6 +72,16 @@ describe.each( /** @type {const} */ ( [ 'scripts', 'modules' ] ) )( } ) ); + /* eslint-disable jest/no-conditional-expect */ + if ( configCase.includes( 'error' ) ) { + expect( stats.hasErrors() ).toBe( true ); + expect( + stats.toString( { errors: true, all: false } ) + ).toMatchSnapshot(); + return; + } + /* eslint-enable jest/no-conditional-expect */ + if ( stats.hasErrors() ) { throw new Error( stats.toString( { errors: true, all: false } ) diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js index 420d5030ab1a6..ab407815bbee4 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/combine-assets/webpack.config.js @@ -12,7 +12,9 @@ module.exports = { new DependencyExtractionWebpackPlugin( { combineAssets: true, requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/error/index.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/error/index.js new file mode 100644 index 0000000000000..388963c34de89 --- /dev/null +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/error/index.js @@ -0,0 +1,10 @@ +/* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable eslint-comments/no-unlimited-disable */ +/* eslint-disable */ + +import $ from 'jquery'; +const apiFetch = await import( '@wordpress/api-fetch' ); + +$( () => { + apiFetch( { path: '/' } ); +} ); diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/error/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/error/webpack.config.js new file mode 100644 index 0000000000000..5f2e5f899237c --- /dev/null +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/error/webpack.config.js @@ -0,0 +1,15 @@ +/** + * Internal dependencies + */ +const DependencyExtractionWebpackPlugin = require( '../../..' ); + +module.exports = { + plugins: [ + new DependencyExtractionWebpackPlugin( { + // eslint-disable-next-line no-unused-vars + requestToExternal( request ) { + throw new Error( 'Ensure error in script build.' ); + }, + } ), + ], +}; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js index 0aea733327a54..7ec1c7ce8a5de 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/function-output-filename/webpack.config.js @@ -12,7 +12,9 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js index 4ef05f6986b9c..4816746976c5b 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/has-extension-suffix/webpack.config.js @@ -10,7 +10,9 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js index 40123021ae404..f65d51ffa5152 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-function-output-filename/webpack.config.js @@ -10,7 +10,9 @@ module.exports = { return `chunk--${ chunkData.chunk.name }--[name].asset.php`; }, requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js index fb02bc3c5bcd0..30aa1f0233e93 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/option-output-filename/webpack.config.js @@ -8,7 +8,9 @@ module.exports = { new DependencyExtractionWebpackPlugin( { outputFilename: '[name]-foo.asset.php', requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js index b82f422f949f8..3a6099354c86b 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/output-format-json/webpack.config.js @@ -5,6 +5,11 @@ const DependencyExtractionWebpackPlugin = require( '../../..' ); module.exports = { plugins: [ - new DependencyExtractionWebpackPlugin( { outputFormat: 'json' } ), + new DependencyExtractionWebpackPlugin( { + outputFormat: 'json', + requestToExternalModule( request ) { + return request === 'lodash'; + }, + } ), ], }; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js index b8fa673995e9b..0c6608a814b45 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/runtime-chunk-single/webpack.config.js @@ -11,7 +11,9 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js index bb412af5c61b8..0f5ac2b955538 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/style-imports/webpack.config.js @@ -12,7 +12,9 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), new MiniCSSExtractPlugin(), diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-interactivity/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-interactivity/webpack.config.js index bfffff3ae7831..7d18e80a8d4ea 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-interactivity/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-interactivity/webpack.config.js @@ -4,5 +4,11 @@ const DependencyExtractionWebpackPlugin = require( '../../..' ); module.exports = { - plugins: [ new DependencyExtractionWebpackPlugin() ], + plugins: [ + new DependencyExtractionWebpackPlugin( { + requestToExternalModule( request ) { + return request === 'lodash'; + }, + } ), + ], }; diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js index 59d4c5d2ead3a..bf6cb387dbd18 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress-require/webpack.config.js @@ -7,7 +7,9 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], diff --git a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js index 59d4c5d2ead3a..bf6cb387dbd18 100644 --- a/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js +++ b/packages/dependency-extraction-webpack-plugin/test/fixtures/wordpress/webpack.config.js @@ -7,7 +7,9 @@ module.exports = { plugins: [ new DependencyExtractionWebpackPlugin( { requestToExternalModule( request ) { - return request.startsWith( '@wordpress/' ); + return ( + request.startsWith( '@wordpress/' ) || request === 'lodash' + ); }, } ), ], From 1ce444fb69edc0f03f15dabed989932f973f1942 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Fri, 12 Jan 2024 17:20:56 +0100 Subject: [PATCH 092/296] Migrate ColorPalette and CircularOptionPicker tests from user-event to ariakit/test (#57809) * Migrate ColorPalette tests from user-event to ariakit/test * Migrate CircularOptionPicker tests from user-event to ariakit/test --- packages/components/CHANGELOG.md | 1 + .../src/circular-option-picker/test/index.tsx | 26 +++---- .../src/palette-edit/test/index.tsx | 71 ++++++++++--------- packages/components/src/tabs/test/index.tsx | 4 +- 4 files changed, 53 insertions(+), 49 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 022248e3706cb..acf1ae3dd165a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -35,6 +35,7 @@ - `Tooltip`: improve unit tests ([#57345](https://github.com/WordPress/gutenberg/pull/57345)). - `Tooltip`: no-op when nested inside other `Tooltip` components ([#57202](https://github.com/WordPress/gutenberg/pull/57202)). - `PaletteEdit`: improve unit tests ([#57645](https://github.com/WordPress/gutenberg/pull/57645)). +- `ColorPalette` and `CircularOptionPicker`: improve unit tests ([#57809](https://github.com/WordPress/gutenberg/pull/57809)). ### Experimental diff --git a/packages/components/src/circular-option-picker/test/index.tsx b/packages/components/src/circular-option-picker/test/index.tsx index 48a0aca40faf3..0a168432629f8 100644 --- a/packages/components/src/circular-option-picker/test/index.tsx +++ b/packages/components/src/circular-option-picker/test/index.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { press } from '@ariakit/test'; /** * Internal dependencies @@ -71,8 +71,6 @@ describe( 'CircularOptionPicker', () => { describe( 'when `loop` is not set', () => { it( 'should loop', async () => { - const user = userEvent.setup(); - render( { /> ); - await user.tab(); + await press.Tab(); expect( getOption( 'Option One' ) ).toHaveFocus(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( getOption( 'Option Two' ) ).toHaveFocus(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( getOption( 'Option One' ) ).toHaveFocus(); } ); } ); describe( 'when `loop` is true', () => { it( 'should loop', async () => { - const user = userEvent.setup(); - render( { /> ); - await user.tab(); + await press.Tab(); expect( getOption( 'Option One' ) ).toHaveFocus(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( getOption( 'Option Two' ) ).toHaveFocus(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( getOption( 'Option One' ) ).toHaveFocus(); } ); } ); describe( 'when `loop` is false', () => { it( 'should not loop', async () => { - const user = userEvent.setup(); - render( { /> ); - await user.tab(); + await press.Tab(); expect( getOption( 'Option One' ) ).toHaveFocus(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( getOption( 'Option Two' ) ).toHaveFocus(); - await user.keyboard( '[ArrowRight]' ); + await press.ArrowRight(); expect( getOption( 'Option Two' ) ).toHaveFocus(); } ); } ); diff --git a/packages/components/src/palette-edit/test/index.tsx b/packages/components/src/palette-edit/test/index.tsx index 31b712225a8dc..b37db91607f8e 100644 --- a/packages/components/src/palette-edit/test/index.tsx +++ b/packages/components/src/palette-edit/test/index.tsx @@ -2,7 +2,7 @@ * External dependencies */ import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { click, type, press } from '@ariakit/test'; /** * Internal dependencies @@ -12,6 +12,15 @@ import type { PaletteElement } from '../types'; const noop = () => {}; +async function clearInput( input: HTMLInputElement ) { + await click( input ); + + // Press backspace as many times as the input's current value + for ( const _ of Array( input.value.length ) ) { + await press.Backspace(); + } +} + describe( 'getNameForPosition', () => { test( 'should return 1 by default', () => { const slugPrefix = 'test-'; @@ -149,10 +158,9 @@ describe( 'PaletteEdit', () => { } ); it( 'shows an option to remove all colors', async () => { - const user = userEvent.setup(); render( ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Color options', } ) @@ -166,12 +174,11 @@ describe( 'PaletteEdit', () => { } ); it( 'shows a reset option when the `canReset` prop is enabled', async () => { - const user = userEvent.setup(); render( ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Color options', } ) @@ -184,10 +191,9 @@ describe( 'PaletteEdit', () => { } ); it( 'does not show a reset colors option when `canReset` is disabled', async () => { - const user = userEvent.setup(); render( ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Color options', } ) @@ -200,7 +206,6 @@ describe( 'PaletteEdit', () => { } ); it( 'calls the `onChange` with the new color appended', async () => { - const user = userEvent.setup(); const onChange = jest.fn(); render( @@ -211,7 +216,7 @@ describe( 'PaletteEdit', () => { /> ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Add color', } ) @@ -230,7 +235,6 @@ describe( 'PaletteEdit', () => { } ); it( 'calls the `onChange` with the new gradient appended', async () => { - const user = userEvent.setup(); const onChange = jest.fn(); render( @@ -241,7 +245,7 @@ describe( 'PaletteEdit', () => { /> ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Add gradient', } ) @@ -271,7 +275,6 @@ describe( 'PaletteEdit', () => { } ); it( 'can remove a color', async () => { - const user = userEvent.setup(); const onChange = jest.fn(); render( @@ -282,18 +285,18 @@ describe( 'PaletteEdit', () => { /> ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Color options', } ) ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Show details', } ) ); - await user.click( screen.getByText( 'Primary' ) ); - await user.click( + await click( screen.getByText( 'Primary' ) ); + await click( screen.getByRole( 'button', { name: 'Remove color', } ) @@ -305,7 +308,6 @@ describe( 'PaletteEdit', () => { } ); it( 'can update palette name', async () => { - const user = userEvent.setup(); const onChange = jest.fn(); render( @@ -316,22 +318,24 @@ describe( 'PaletteEdit', () => { /> ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Color options', } ) ); - await user.click( + await click( screen.getByRole( 'button', { name: 'Show details', } ) ); - await user.click( screen.getByText( 'Primary' ) ); + await click( screen.getByText( 'Primary' ) ); const nameInput = screen.getByRole( 'textbox', { name: 'Color name', } ); - await user.clear( nameInput ); - await user.type( nameInput, 'Primary Updated' ); + + await clearInput( nameInput as HTMLInputElement ); + + await type( 'Primary Updated' ); await waitFor( () => { expect( onChange ).toHaveBeenCalledWith( [ @@ -346,7 +350,6 @@ describe( 'PaletteEdit', () => { } ); it( 'can update color palette value', async () => { - const user = userEvent.setup(); const onChange = jest.fn(); render( @@ -357,12 +360,14 @@ describe( 'PaletteEdit', () => { /> ); - await user.click( screen.getByLabelText( 'Color: Primary' ) ); + await click( screen.getByLabelText( 'Color: Primary' ) ); const hexInput = screen.getByRole( 'textbox', { name: 'Hex color', } ); - await user.clear( hexInput ); - await user.type( hexInput, '000000' ); + + await clearInput( hexInput as HTMLInputElement ); + + await type( '000000' ); await waitFor( () => { expect( onChange ).toHaveBeenCalledWith( [ @@ -376,7 +381,6 @@ describe( 'PaletteEdit', () => { } ); it( 'can update gradient palette value', async () => { - const user = userEvent.setup(); const onChange = jest.fn(); render( @@ -387,12 +391,15 @@ describe( 'PaletteEdit', () => { /> ); - await user.click( screen.getByLabelText( 'Gradient: Pale ocean' ) ); + await click( screen.getByLabelText( 'Gradient: Pale ocean' ) ); - const typeSelectElement = screen.getByRole( 'combobox', { - name: 'Type', - } ); - await user.selectOptions( typeSelectElement, 'radial-gradient' ); + // Select radial gradient option + await click( + screen.getByRole( 'combobox', { + name: 'Type', + } ) + ); + await click( screen.getByRole( 'option', { name: 'Radial' } ) ); await waitFor( () => { expect( onChange ).toHaveBeenCalledWith( [ diff --git a/packages/components/src/tabs/test/index.tsx b/packages/components/src/tabs/test/index.tsx index 24b7e75b6e72b..8c2c2d0fd2fa2 100644 --- a/packages/components/src/tabs/test/index.tsx +++ b/packages/components/src/tabs/test/index.tsx @@ -1260,7 +1260,9 @@ describe( 'Tabs', () => { // Tab key should focus the currently selected tab, which is Beta. await press.Tab(); - expect( await getSelectedTab() ).toHaveFocus(); + await waitFor( async () => + expect( await getSelectedTab() ).toHaveFocus() + ); rerender( Date: Fri, 12 Jan 2024 17:21:24 +0100 Subject: [PATCH 093/296] Remove check-latest-npm validation (#57797) Package publishing via GitHub Actions is currently broken because the actions are not passing the npm version check. This check was introduced when package-lock formats were changing frequently and causing issues. Those difficulties seem to have passed for the most part. --- bin/check-latest-npm.js | 134 ---------------------------------------- bin/tsconfig.json | 1 - package.json | 3 +- 3 files changed, 1 insertion(+), 137 deletions(-) delete mode 100644 bin/check-latest-npm.js diff --git a/bin/check-latest-npm.js b/bin/check-latest-npm.js deleted file mode 100644 index 8b9f86e95dd56..0000000000000 --- a/bin/check-latest-npm.js +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env node - -/** - * External dependencies - */ -const { green, red, yellow } = require( 'chalk' ); -const { get } = require( 'https' ); -const { spawn } = require( 'child_process' ); -const semver = require( 'semver' ); - -/** - * Internal dependencies - */ -const { - engines: { npm: npmRange }, - // Ignore reason: `package.json` exists outside `bin` `rootDir`. - // @ts-ignore -} = require( '../package.json' ); - -/** - * Returns a promise resolving with the version number of the latest available - * version of npm. - * - * @return {Promise} Promise resolving with latest npm version. - */ -async function getLatestNPMVersion() { - return new Promise( ( resolve, reject ) => { - get( - 'https://registry.npmjs.org/npm', - { - headers: { - // By passing a specialized `Accept` header, the registry - // will return an abbreviated form of the package data which - // includes enough detail to determine the latest version. - // - // See: https://github.com/npm/registry/blob/HEAD/docs/responses/package-metadata.md - Accept: 'application/vnd.npm.install-v1+json', - }, - }, - async ( response ) => { - if ( response.statusCode !== 200 ) { - return reject( - new Error( 'Package data for npm not found' ) - ); - } - - let body = ''; - for await ( const chunk of response ) { - body += chunk.toString(); - } - - let data; - try { - data = JSON.parse( body ); - } catch { - return reject( - new Error( - 'Package data for npm returned invalid response body' - ) - ); - } - - const versions = Object.values( data[ 'dist-tags' ] ); - - resolve( semver.maxSatisfying( versions, npmRange ) ); - } - ).on( 'error', ( error ) => { - if ( - /** @type {NodeJS.ErrnoException} */ ( error ).code === - 'ENOTFOUND' - ) { - error = - new Error( `Could not contact the npm registry to determine latest version. - -This could be due to an intermittent outage of the service, or because you are not connected to the internet. - -Because it is important that \`package-lock.json\` files only be committed while running the latest version of npm, this commit has been blocked. - -If you are certain of your changes and desire to commit anyways, you should either connect to the internet or bypass commit verification using ${ yellow( - 'git commit --no-verify' - ) } .` ); - } - - reject( error ); - } ); - } ); -} - -/** - * Returns a promise resolving with the version number of the local installed - * version of npm. - * - * @return {Promise} Promise resolving with local installed npm version. - */ -async function getLocalNPMVersion() { - return new Promise( async ( resolve ) => { - // 'npm' doesn't work correctly on Windows. - // https://github.com/WordPress/gutenberg/issues/22484 - const command = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - const childProcess = spawn( command, [ '-v' ] ); - - let output = ''; - for await ( const chunk of childProcess.stdout ) { - output += chunk.toString(); - } - - resolve( output.trim() ); - } ); -} - -Promise.all( [ getLatestNPMVersion(), getLocalNPMVersion() ] ) - .then( ( [ latest, local ] ) => { - if ( latest !== local ) { - throw new Error( - `The local npm version does not match the expected latest version. Expected ${ green( - latest - ) }, found ${ red( local ) }. - -It is required that you have the expected latest version of npm installed in order to commit a change to the package-lock.json file. - -Run ${ yellow( - `npm install --global npm@${ latest }` - ) } to install the expected latest version of npm. Before retrying your commit, run ${ yellow( - 'npm install' - ) } once more to ensure the package-lock.json contents are correct. If there are any changes to the file, they should be included in your commit.` - ); - } - } ) - .catch( ( error ) => { - console.error( - 'Latest npm check failed!\n\n' + error.toString() + '\n' - ); - process.exitCode = 1; - } ); diff --git a/bin/tsconfig.json b/bin/tsconfig.json index f3d576c178d0c..48fd553e4c049 100644 --- a/bin/tsconfig.json +++ b/bin/tsconfig.json @@ -17,7 +17,6 @@ }, "files": [ "./api-docs/update-api-docs.js", - "./check-latest-npm.js", "./plugin/config.js", "./plugin/commands/changelog.js", "./plugin/commands/performance.js", diff --git a/package.json b/package.json index 684f35d408d3c..aae8275e714ab 100644 --- a/package.json +++ b/package.json @@ -359,8 +359,7 @@ "wp-scripts lint-style" ], "package-lock.json": [ - "npm run lint:lockfile", - "node ./bin/check-latest-npm.js" + "npm run lint:lockfile" ], "packages/*/package.json": [ "wp-scripts lint-pkg-json" From 49a63d36798fd9dd3fd3009bf49eee91277ff927 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Fri, 12 Jan 2024 17:08:14 +0000 Subject: [PATCH 094/296] Fix regression: Content locking does not stops when an outside block is selected (#57737) * Fix regression: Content locking does not stops automatically when an outside block is selected. * Make the new action private --- .../src/components/block-list/index.js | 68 +++++++++++++----- .../block-editor/src/hooks/content-lock-ui.js | 70 ++++--------------- packages/block-editor/src/store/actions.js | 5 +- .../block-editor/src/store/private-actions.js | 22 ++++++ packages/block-editor/src/store/reducer.js | 16 +++++ packages/block-editor/src/store/selectors.js | 11 +++ 6 files changed, 120 insertions(+), 72 deletions(-) diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index ab57605563e1d..09a4712a3dd6d 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -17,7 +17,12 @@ import { useMergeRefs, useDebounce, } from '@wordpress/compose'; -import { createContext, useMemo, useCallback } from '@wordpress/element'; +import { + createContext, + useMemo, + useCallback, + useEffect, +} from '@wordpress/element'; /** * Internal dependencies @@ -34,6 +39,7 @@ import { DEFAULT_BLOCK_EDIT_CONTEXT, } from '../block-edit/context'; import { useTypingObserver } from '../observe-typing'; +import { unlock } from '../../lock-unlock'; export const IntersectionObserver = createContext(); const pendingBlockVisibilityUpdatesPerRegistry = new WeakMap(); @@ -113,6 +119,27 @@ function Root( { className, ...settings } ) { ); } +function StopEditingAsBlocksOnOutsideSelect( { clientId } ) { + const { stopEditingAsBlocks } = unlock( useDispatch( blockEditorStore ) ); + const isBlockOrDescendantSelected = useSelect( + ( select ) => { + const { isBlockSelected, hasSelectedInnerBlock } = + select( blockEditorStore ); + return ( + isBlockSelected( clientId ) || + hasSelectedInnerBlock( clientId, true ) + ); + }, + [ clientId ] + ); + useEffect( () => { + if ( ! isBlockOrDescendantSelected ) { + stopEditingAsBlocks( clientId ); + } + }, [ isBlockOrDescendantSelected, clientId, stopEditingAsBlocks ] ); + return null; +} + export default function BlockList( settings ) { return ( @@ -128,21 +155,25 @@ function Items( { __experimentalAppenderTagName, layout = defaultLayout, } ) { - const { order, selectedBlocks, visibleBlocks } = useSelect( - ( select ) => { - const { - getBlockOrder, - getSelectedBlockClientIds, - __unstableGetVisibleBlocks, - } = select( blockEditorStore ); - return { - order: getBlockOrder( rootClientId ), - selectedBlocks: getSelectedBlockClientIds(), - visibleBlocks: __unstableGetVisibleBlocks(), - }; - }, - [ rootClientId ] - ); + const { order, selectedBlocks, visibleBlocks, temporarilyEditingAsBlocks } = + useSelect( + ( select ) => { + const { + getBlockOrder, + getSelectedBlockClientIds, + __unstableGetVisibleBlocks, + __unstableGetTemporarilyEditingAsBlocks, + } = select( blockEditorStore ); + return { + order: getBlockOrder( rootClientId ), + selectedBlocks: getSelectedBlockClientIds(), + visibleBlocks: __unstableGetVisibleBlocks(), + temporarilyEditingAsBlocks: + __unstableGetTemporarilyEditingAsBlocks(), + }; + }, + [ rootClientId ] + ); return ( @@ -163,6 +194,11 @@ function Items( { ) ) } { order.length < 1 && placeholder } + { !! temporarilyEditingAsBlocks && ( + + ) } { - const { isBlockSelected, hasSelectedInnerBlock } = - select( blockEditorStore ); - return ( - isBlockSelected( clientId ) || - hasSelectedInnerBlock( clientId, true ) - ); - }, - [ clientId ] - ); - useEffect( () => { - if ( ! isBlockOrDescendantSelected ) { - stopEditingAsBlock(); - } - }, [ isBlockOrDescendantSelected, stopEditingAsBlock ] ); - return null; -} +// The implementation of content locking is mainly in this file, although the mechanism +// to stop temporarily editing as blocks when an outside block is selected is on component StopEditingAsBlocksOnOutsideSelect +// at block-editor/src/components/block-list/index.js. +// Besides the components on this file and the file referenced above the implementation +// also includes artifacts on the store (actions, reducers, and selector). function ContentLockControlsPure( { clientId, isSelected } ) { const { getBlockListSettings, getSettings } = useSelect( blockEditorStore ); - const focusModeToRevert = useRef(); const { templateLock, isLockedByParent, isEditingAsBlocks } = useSelect( ( select ) => { const { @@ -61,31 +44,15 @@ function ContentLockControlsPure( { clientId, isSelected } ) { updateBlockListSettings, __unstableSetTemporarilyEditingAsBlocks, } = useDispatch( blockEditorStore ); + const { stopEditingAsBlocks } = unlock( useDispatch( blockEditorStore ) ); const isContentLocked = ! isLockedByParent && templateLock === 'contentOnly'; const { __unstableMarkNextChangeAsNotPersistent, updateBlockAttributes } = useDispatch( blockEditorStore ); - const stopEditingAsBlock = useCallback( () => { - __unstableMarkNextChangeAsNotPersistent(); - updateBlockAttributes( clientId, { - templateLock: 'contentOnly', - } ); - updateBlockListSettings( clientId, { - ...getBlockListSettings( clientId ), - templateLock: 'contentOnly', - } ); - updateSettings( { focusMode: focusModeToRevert.current } ); - __unstableSetTemporarilyEditingAsBlocks(); - }, [ - clientId, - updateSettings, - updateBlockListSettings, - getBlockListSettings, - __unstableMarkNextChangeAsNotPersistent, - updateBlockAttributes, - __unstableSetTemporarilyEditingAsBlocks, - ] ); + const stopEditingAsBlockCallback = useCallback( () => { + stopEditingAsBlocks( clientId ); + }, [ clientId, stopEditingAsBlocks ] ); if ( ! isContentLocked && ! isEditingAsBlocks ) { return null; @@ -99,16 +66,8 @@ function ContentLockControlsPure( { clientId, isSelected } ) { <> { showStopEditingAsBlocks && ( <> - - { - stopEditingAsBlock(); - } } - > + { __( 'Done' ) } @@ -127,11 +86,12 @@ function ContentLockControlsPure( { clientId, isSelected } ) { ...getBlockListSettings( clientId ), templateLock: false, } ); - focusModeToRevert.current = + const focusModeToRevert = getSettings().focusMode; updateSettings( { focusMode: true } ); __unstableSetTemporarilyEditingAsBlocks( - clientId + clientId, + focusModeToRevert ); onClose(); } } diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index 4b396045a73c2..47d530c8319a2 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1686,13 +1686,16 @@ export function setBlockVisibility( updates ) { * removed anytime without any warning, causing breakage on any plugin or theme invoking it. * * @param {?string} temporarilyEditingAsBlocks The block's clientId being temporarily edited as blocks. + * @param {?string} focusModeToRevert The focus mode to revert after temporarily edit as blocks finishes. */ export function __unstableSetTemporarilyEditingAsBlocks( - temporarilyEditingAsBlocks + temporarilyEditingAsBlocks, + focusModeToRevert ) { return { type: 'SET_TEMPORARILY_EDITING_AS_BLOCKS', temporarilyEditingAsBlocks, + focusModeToRevert, }; } diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 43c392bc7ce8c..a31455a0b7e7b 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -338,3 +338,25 @@ export function setLastFocus( lastFocus = null ) { lastFocus, }; } + +/** + * Action that stops temporarily editing as blocks. + * + * @param {string} clientId The block's clientId. + */ +export function stopEditingAsBlocks( clientId ) { + return ( { select, dispatch } ) => { + const focusModeToRevert = + select.__unstableGetTemporarilyEditingFocusModeToRevert(); + dispatch.__unstableMarkNextChangeAsNotPersistent(); + dispatch.updateBlockAttributes( clientId, { + templateLock: 'contentOnly', + } ); + dispatch.updateBlockListSettings( clientId, { + ...select.getBlockListSettings( clientId ), + templateLock: 'contentOnly', + } ); + dispatch.updateSettings( { focusMode: focusModeToRevert } ); + dispatch.__unstableSetTemporarilyEditingAsBlocks(); + }; +} diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index 11811afd83f6f..fa6c8942e66ad 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -1906,6 +1906,21 @@ export function temporarilyEditingAsBlocks( state = '', action ) { return state; } +/** + * Reducer returning the focus mode that should be used when temporarily edit as blocks finishes. + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function temporarilyEditingFocusModeRevert( state = '', action ) { + if ( action.type === 'SET_TEMPORARILY_EDITING_AS_BLOCKS' ) { + return action.focusModeToRevert; + } + return state; +} + /** * Reducer returning a map of block client IDs to block editing modes. * @@ -2024,6 +2039,7 @@ const combinedReducers = combineReducers( { highlightedBlock, lastBlockInserted, temporarilyEditingAsBlocks, + temporarilyEditingFocusModeRevert, blockVisibility, blockEditingModes, styleOverrides, diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 55d157c6927a2..1cf3705454f9b 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -2767,6 +2767,17 @@ export function __unstableGetTemporarilyEditingAsBlocks( state ) { return state.temporarilyEditingAsBlocks; } +/** + * DO-NOT-USE in production. + * This selector is created for internal/experimental only usage and may be + * removed anytime without any warning, causing breakage on any plugin or theme invoking it. + * + * @param {Object} state Global application state. + */ +export function __unstableGetTemporarilyEditingFocusModeToRevert( state ) { + return state.temporarilyEditingFocusModeRevert; +} + export function __unstableHasActiveBlockOverlayActive( state, clientId ) { // Prevent overlay on blocks with a non-default editing mode. If the mdoe is // 'disabled' then the overlay is redundant since the block can't be From f4e8d485f8e496d2c9aa8eb3fb81f16b92de53ef Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Fri, 12 Jan 2024 17:21:46 +0000 Subject: [PATCH 095/296] Fix: Typo on BlockListBlock comments. (#57814) --- packages/block-editor/src/components/block-list/block.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index c053235c2d0d3..0bfc940865a09 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -482,7 +482,7 @@ BlockListBlock = compose( )( BlockListBlock ); // This component provides all the information we need through a single store -// subscription (useSelect mapping). Only the necesssary props are passed down +// subscription (useSelect mapping). Only the necessary props are passed down // to the BlockListBlock component, which is a filtered component, so these // props are public API. To avoid adding to the public API, we use a private // context to pass the rest of the information to the filtered BlockListBlock From 2be3711fef483d764156d0f56780b54591709285 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Fri, 12 Jan 2024 17:25:32 +0000 Subject: [PATCH 096/296] Add: End to end test to content locking stop editing as blocks behaviour. (#57812) --- .../editor/various/content-only-lock.spec.js | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/e2e/specs/editor/various/content-only-lock.spec.js b/test/e2e/specs/editor/various/content-only-lock.spec.js index e7d52562636f3..9784aea1ee068 100644 --- a/test/e2e/specs/editor/various/content-only-lock.spec.js +++ b/test/e2e/specs/editor/various/content-only-lock.spec.js @@ -76,4 +76,56 @@ test.describe( 'Content-only lock', () => { }, ] ); } ); + + test( 'should be able to automatically stop temporarily modify as blocks when an outside block is selected', async ( { + editor, + page, + pageUtils, + } ) => { + // Add content only locked block in the code editor + await pageUtils.pressKeys( 'secondary+M' ); // Emulates CTRL+Shift+Alt + M => toggle code editor + + await page.getByPlaceholder( 'Start writing with text or HTML' ) + .fill( ` +
        +

        Locked block a

        + + + +

        Locked block b

        +
        + + + +

        outside block

        + ` ); + + await pageUtils.pressKeys( 'secondary+M' ); + // Select the content locked block. + await editor.canvas + .locator( 'role=document[name="Block: Group"i]' ) + .click(); + // Press modify to temporarily edit as blocks. + await editor.clickBlockOptionsMenuItem( 'Modify' ); + // Selected a nest paragraph verify Block is not content locked + // Styles can be changed and nested blocks can be removed + await editor.canvas + .locator( 'role=document[name="Block: Paragraph"i]' ) + .first() + .click(); + await expect( + page.locator( '.color-block-support-panel' ) + ).toBeAttached(); + await editor.clickBlockOptionsMenuItem( 'Delete' ); + // Select an outside block + await editor.canvas + .locator( 'role=document[name="Block: Heading"i]' ) + .click(); + // Select a locked nested paragraph block again + await pageUtils.pressKeys( 'ArrowUp' ); + // Block is content locked again simple styles like position can not be changed. + await expect( + page.locator( '.color-block-support-panel' ) + ).not.toBeAttached(); + } ); } ); From 0e48342278e3991f474759e2a537b3ad15d58b37 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Sat, 13 Jan 2024 01:50:38 +0800 Subject: [PATCH 097/296] Fix flaky create new pattern test (#57747) --- test/e2e/specs/site-editor/patterns.spec.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/e2e/specs/site-editor/patterns.spec.js b/test/e2e/specs/site-editor/patterns.spec.js index 79bd8b782ecc4..e4fffe14fcc9a 100644 --- a/test/e2e/specs/site-editor/patterns.spec.js +++ b/test/e2e/specs/site-editor/patterns.spec.js @@ -70,9 +70,6 @@ test.describe( 'Patterns', () => { .getByRole( 'textbox', { name: 'Name' } ) .fill( 'My pattern' ); await page.keyboard.press( 'Enter' ); - await expect( - createPatternDialog.getByRole( 'button', { name: 'Create' } ) - ).toBeDisabled(); await expect( page ).toHaveTitle( /^My pattern/ ); await expect( From 203ca9e84fb0f5ae73c2495dd33b4555f368ae72 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Fri, 12 Jan 2024 17:22:06 -0300 Subject: [PATCH 098/296] Font Library: disable font library UI using a PHP filter (#57818) * disable font library UI Co-authored-by: Jeff Ong <5375500+jffng@users.noreply.github.com> * remove php code no longer used Co-authored-by: Jeff Ong <5375500+jffng@users.noreply.github.com> --------- Co-authored-by: Jeff Ong <5375500+jffng@users.noreply.github.com> --- lib/load.php | 9 --------- .../components/global-styles/screen-typography.js | 12 +++++++++--- packages/editor/src/store/defaults.js | 2 ++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/load.php b/lib/load.php index d413334227ee7..8988a05820425 100644 --- a/lib/load.php +++ b/lib/load.php @@ -185,15 +185,6 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/experimental/fonts/font-face/bc-layer/class-wp-web-fonts.php'; } elseif ( ! class_exists( 'WP_Fonts' ) ) { - // Disables the Font Library. - // @core-merge: this should not go to core. - add_action( - 'enqueue_block_editor_assets', - function () { - wp_add_inline_script( 'wp-block-editor', 'window.__experimentalDisableFontLibrary = true', 'before' ); - } - ); - // Turns off Font Face hooks in Core. // @since 6.4.0. remove_action( 'wp_head', 'wp_print_font_faces', 50 ); diff --git a/packages/edit-site/src/components/global-styles/screen-typography.js b/packages/edit-site/src/components/global-styles/screen-typography.js index 2a895b68fa717..f76dc6fb38100 100644 --- a/packages/edit-site/src/components/global-styles/screen-typography.js +++ b/packages/edit-site/src/components/global-styles/screen-typography.js @@ -3,6 +3,8 @@ */ import { __ } from '@wordpress/i18n'; import { __experimentalVStack as VStack } from '@wordpress/components'; +import { store as editorStore } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; /** * Internal dependencies @@ -12,6 +14,12 @@ import FontFamilies from './font-families'; import ScreenHeader from './header'; function ScreenTypography() { + const fontLibraryEnabled = useSelect( + ( select ) => + select( editorStore ).getEditorSettings().fontLibraryEnabled, + [] + ); + return ( <>
        - { ! window.__experimentalDisableFontLibrary && ( - - ) } + { fontLibraryEnabled && }
        diff --git a/packages/editor/src/store/defaults.js b/packages/editor/src/store/defaults.js index 686888f91de3d..485b65d24d3ad 100644 --- a/packages/editor/src/store/defaults.js +++ b/packages/editor/src/store/defaults.js @@ -10,6 +10,7 @@ import { SETTINGS_DEFAULTS } from '@wordpress/block-editor'; * @property {boolean} richEditingEnabled Whether rich editing is enabled or not * @property {boolean} codeEditingEnabled Whether code editing is enabled or not * @property {boolean} enableCustomFields Whether the WordPress custom fields are enabled or not. + * @property {boolean} fontLibraryEnabled Whether the font library is enabled or not. * true = the user has opted to show the Custom Fields panel at the bottom of the editor. * false = the user has opted to hide the Custom Fields panel at the bottom of the editor. * undefined = the current environment does not support Custom Fields, so the option toggle in Preferences -> Panels to enable the Custom Fields panel is not displayed. @@ -27,5 +28,6 @@ export const EDITOR_SETTINGS_DEFAULTS = { richEditingEnabled: true, codeEditingEnabled: true, enableCustomFields: undefined, + fontLibraryEnabled: true, defaultRenderingMode: 'post-only', }; From 22617edda082e73c00d9a0242df7b9df7a5be5f6 Mon Sep 17 00:00:00 2001 From: Rich Tabor Date: Fri, 12 Jan 2024 17:04:43 -0500 Subject: [PATCH 099/296] Remove "blocks" from copy and delete labels (#57769) --- .../block-settings-menu/block-settings-dropdown.js | 9 ++------- test/e2e/specs/editor/various/list-view.spec.js | 2 +- 2 files changed, 3 insertions(+), 8 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 4d43865a7f83a..657d685b966e0 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 @@ -41,9 +41,7 @@ const POPOVER_PROPS = { function CopyMenuItem( { blocks, onCopy, label } ) { const ref = useCopyToClipboard( () => serialize( blocks ), onCopy ); - const copyMenuItemBlocksLabel = - blocks.length > 1 ? __( 'Copy blocks' ) : __( 'Copy' ); - const copyMenuItemLabel = label ? label : copyMenuItemBlocksLabel; + const copyMenuItemLabel = label ? label : __( 'Copy' ); return { copyMenuItemLabel }; } @@ -202,9 +200,6 @@ export function BlockSettingsDropdown( { getSelectedBlockClientIds, ] ); - const removeBlockLabel = - count === 1 ? __( 'Delete' ) : __( 'Delete blocks' ); - // This can occur when the selected block (the parent) // displays child blocks within a List View. const parentBlockIsSelected = @@ -411,7 +406,7 @@ export function BlockSettingsDropdown( { ) } shortcut={ shortcuts.remove } > - { removeBlockLabel } + { __( 'Delete' ) } ) } diff --git a/test/e2e/specs/editor/various/list-view.spec.js b/test/e2e/specs/editor/various/list-view.spec.js index f05b7760c4cc7..674801cf94aba 100644 --- a/test/e2e/specs/editor/various/list-view.spec.js +++ b/test/e2e/specs/editor/various/list-view.spec.js @@ -820,7 +820,7 @@ test.describe( 'List View', () => { .click(); await page .getByRole( 'menu', { name: 'Options for Heading' } ) - .getByRole( 'menuitem', { name: 'Delete blocks' } ) + .getByRole( 'menuitem', { name: 'Delete' } ) .click(); await expect .poll( From 1e11103505416b4ca6a366b9afa1dd9962acdf69 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 13 Jan 2024 20:38:59 +0900 Subject: [PATCH 100/296] ToggleGroupControl: Improve controlled value detection (#57770) * Add tests * ToggleGroupControl: Improve controlled value detection * Update changelog * Improve test descriptions Co-authored-by: Marco Ciampini --------- Co-authored-by: Marco Ciampini --- packages/components/CHANGELOG.md | 1 + .../test/__snapshots__/index.tsx.snap | 12 +- .../src/toggle-group-control/test/index.tsx | 109 ++++++++++++------ .../toggle-group-control/utils.ts | 11 +- 4 files changed, 88 insertions(+), 45 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index acf1ae3dd165a..4152b3c654bab 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -19,6 +19,7 @@ - `DuotonePicker`: Remove top margin when no duotone options ([#57489](https://github.com/WordPress/gutenberg/pull/57489)). - `Snackbar`: Fix icon positioning ([#57377](https://github.com/WordPress/gutenberg/pull/57377)). - `GradientPicker`: Use slug while iterating over gradient entries to avoid React "duplicated key" warning ([#57361](https://github.com/WordPress/gutenberg/pull/57361)). +- `ToggleGroupControl`: Improve controlled value detection ([#57770](https://github.com/WordPress/gutenberg/pull/57770)). - `NavigatorProvider`: Exclude `size` value from `contain` CSS rule ([#57498](https://github.com/WordPress/gutenberg/pull/57498)). ### Enhancements diff --git a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap index 990d8144ff33c..bac30669f1851 100644 --- a/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap +++ b/packages/components/src/toggle-group-control/test/__snapshots__/index.tsx.snap @@ -240,7 +240,7 @@ exports[`ToggleGroupControl controlled should render correctly with icons 1`] = class="components-toggle-group-control emotion-8 emotion-9" data-wp-c16t="true" data-wp-component="ToggleGroupControl" - id="toggle-group-control-as-radio-group-8" + id="toggle-group-control-as-radio-group-10" role="radiogroup" >