From 05755f32a9e458d5c159fcaed6bb900eea385b1e Mon Sep 17 00:00:00 2001 From: Rich Tabor Date: Mon, 2 Oct 2023 15:18:14 -0400 Subject: [PATCH 01/28] Add clearer labels and context to BlockPatternsSyncFilter (#54838) * Add help text & clearer labeling * Theme & Plugins --- .../inserter/block-patterns-filter.js | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/inserter/block-patterns-filter.js b/packages/block-editor/src/components/inserter/block-patterns-filter.js index 92a65efbd5c54e..0d6cabe6c239f3 100644 --- a/packages/block-editor/src/components/inserter/block-patterns-filter.js +++ b/packages/block-editor/src/components/inserter/block-patterns-filter.js @@ -7,10 +7,11 @@ import { DropdownMenu, MenuGroup, MenuItemsChoice, + ExternalLink, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; -import { useMemo } from '@wordpress/element'; +import { useMemo, createInterpolateElement } from '@wordpress/element'; /** * Internal dependencies @@ -73,13 +74,11 @@ export function BlockPatternsSyncFilter( { { value: SYNC_TYPES.full, label: __( 'Synced' ), - info: __( 'Updated everywhere' ), disabled: shouldDisableSyncFilter, }, { value: SYNC_TYPES.unsynced, - label: __( 'Standard' ), - info: __( 'Edit freely' ), + label: __( 'Not synced' ), disabled: shouldDisableSyncFilter, }, ], @@ -95,20 +94,17 @@ export function BlockPatternsSyncFilter( { }, { value: PATTERN_TYPES.directory, - label: __( 'Directory' ), - info: __( 'Pattern directory & core' ), + label: __( 'Pattern Directory' ), disabled: shouldDisableNonUserSources, }, { value: PATTERN_TYPES.theme, - label: __( 'Theme' ), - info: __( 'Bundled with the theme' ), + label: __( 'Theme & Plugins' ), disabled: shouldDisableNonUserSources, }, { value: PATTERN_TYPES.user, label: __( 'User' ), - info: __( 'Custom created' ), }, ], [ shouldDisableNonUserSources ] @@ -149,7 +145,7 @@ export function BlockPatternsSyncFilter( { > { () => ( <> - + { @@ -175,6 +171,22 @@ export function BlockPatternsSyncFilter( { value={ patternSyncFilter } /> +
+ { createInterpolateElement( + __( + 'Patterns are available from the WordPress.org Pattern Directory, bundled in the active theme, or created by users on this site. Only patterns created on this site can be synced.' + ), + { + Link: ( + + ), + } + ) } +
) } From 089c0a393c418a5d0337a0250185c509e1233714 Mon Sep 17 00:00:00 2001 From: Matias Benedetto Date: Mon, 2 Oct 2023 16:20:55 -0300 Subject: [PATCH 02/28] Font Library: use snake_case instead of camelCase on fontFamilies endpoint param (#54977) * use snake_case instead of camelCase on endpoint param * updating test --- .../class-wp-rest-font-library-controller.php | 12 ++++++------ .../global-styles/font-library-modal/resolvers.js | 2 +- .../global-styles/font-library-modal/utils/index.js | 2 +- .../utils/test/makeFormDataFromFontFamilies.spec.js | 2 +- .../wpRestFontLibraryController/installFonts.php | 4 ++-- .../wpRestFontLibraryController/uninstallFonts.php | 6 +++--- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php index 90fb3c6973ca3f..daeac3d7294e87 100644 --- a/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php +++ b/lib/experimental/fonts/font-library/class-wp-rest-font-library-controller.php @@ -45,7 +45,7 @@ public function register_routes() { 'callback' => array( $this, 'install_fonts' ), 'permission_callback' => array( $this, 'update_font_library_permissions_check' ), 'args' => array( - 'fontFamilies' => array( + 'font_families' => array( 'required' => true, 'type' => 'string', 'validate_callback' => array( $this, 'validate_install_font_families' ), @@ -147,13 +147,13 @@ private function get_validation_errors( $font_families, $files ) { $error_messages = array(); if ( ! is_array( $font_families ) ) { - $error_messages[] = __( 'fontFamilies should be an array of font families.', 'gutenberg' ); + $error_messages[] = __( 'font_families should be an array of font families.', 'gutenberg' ); return $error_messages; } // Checks if there is at least one font family. if ( count( $font_families ) < 1 ) { - $error_messages[] = __( 'fontFamilies should have at least one font family definition.', 'gutenberg' ); + $error_messages[] = __( 'font_families should have at least one font family definition.', 'gutenberg' ); return $error_messages; } @@ -260,7 +260,7 @@ public function validate_install_font_families( $param, $request ) { */ public function uninstall_schema() { return array( - 'fontFamilies' => array( + 'font_families' => array( 'type' => 'array', 'description' => __( 'The font families to install.', 'gutenberg' ), 'required' => true, @@ -289,7 +289,7 @@ public function uninstall_schema() { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function uninstall_fonts( $request ) { - $fonts_to_uninstall = $request->get_param( 'fontFamilies' ); + $fonts_to_uninstall = $request->get_param( 'font_families' ); $errors = array(); $successes = array(); @@ -397,7 +397,7 @@ private function needs_write_permission( $font_families ) { */ public function install_fonts( $request ) { // Get new fonts to install. - $fonts_param = $request->get_param( 'fontFamilies' ); + $fonts_param = $request->get_param( 'font_families' ); /* * As this is receiving form data, the font families are encoded as a string. 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 8f4fd2f7c22bda..425b3afb0e7c3c 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 @@ -18,7 +18,7 @@ export async function fetchInstallFonts( data ) { export async function fetchUninstallFonts( fonts ) { const data = { - fontFamilies: fonts, + font_families: fonts, }; const config = { path: '/wp/v2/fonts', 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 7afa0f7aee17a5..d0a57978bcce94 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 @@ -152,6 +152,6 @@ export function makeFormDataFromFontFamilies( fontFamilies ) { } return family; } ); - formData.append( 'fontFamilies', JSON.stringify( newFontFamilies ) ); + formData.append( 'font_families', JSON.stringify( newFontFamilies ) ); 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 index 9db0195f30072e..4adae7889cc5e5 100644 --- 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 @@ -55,7 +55,7 @@ describe( 'makeFormDataFromFontFamilies', () => { fontFamily: 'Bebas', }, ]; - expect( JSON.parse( formData.get( 'fontFamilies' ) ) ).toEqual( + expect( JSON.parse( formData.get( 'font_families' ) ) ).toEqual( expectedFontFamilies ); } ); diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php index 6ec15c71ade596..01ac1ff8436ed7 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/installFonts.php @@ -24,7 +24,7 @@ class Tests_Fonts_WPRESTFontLibraryController_InstallFonts extends WP_REST_Font_ public function test_install_fonts( $font_families, $files, $expected_response ) { $install_request = new WP_REST_Request( 'POST', '/wp/v2/fonts' ); $font_families_json = json_encode( $font_families ); - $install_request->set_param( 'fontFamilies', $font_families_json ); + $install_request->set_param( 'font_families', $font_families_json ); $install_request->set_file_params( $files ); $response = rest_get_server()->dispatch( $install_request ); $data = $response->get_data(); @@ -309,7 +309,7 @@ public function data_install_fonts() { public function test_install_with_improper_inputs( $font_families, $files = array() ) { $install_request = new WP_REST_Request( 'POST', '/wp/v2/fonts' ); $font_families_json = json_encode( $font_families ); - $install_request->set_param( 'fontFamilies', $font_families_json ); + $install_request->set_param( 'font_families', $font_families_json ); $install_request->set_file_params( $files ); $response = rest_get_server()->dispatch( $install_request ); diff --git a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php index 860f47d8764810..a3b613e6f983e0 100644 --- a/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php +++ b/phpunit/tests/fonts/font-library/wpRestFontLibraryController/uninstallFonts.php @@ -53,7 +53,7 @@ public function set_up() { $install_request = new WP_REST_Request( 'POST', '/wp/v2/fonts' ); $font_families_json = json_encode( $mock_families ); - $install_request->set_param( 'fontFamilies', $font_families_json ); + $install_request->set_param( 'font_families', $font_families_json ); rest_get_server()->dispatch( $install_request ); } @@ -68,7 +68,7 @@ public function test_uninstall() { ); $uninstall_request = new WP_REST_Request( 'DELETE', '/wp/v2/fonts' ); - $uninstall_request->set_param( 'fontFamilies', $font_families_to_uninstall ); + $uninstall_request->set_param( 'font_families', $font_families_to_uninstall ); $response = rest_get_server()->dispatch( $uninstall_request ); $this->assertSame( 200, $response->get_status(), 'The response status is not 200.' ); } @@ -88,7 +88,7 @@ public function test_uninstall_non_existing_fonts() { ), ); - $uninstall_request->set_param( 'fontFamilies', $non_existing_font_data ); + $uninstall_request->set_param( 'font_families', $non_existing_font_data ); $response = rest_get_server()->dispatch( $uninstall_request ); $data = $response->get_data(); $this->assertCount( 2, $data['errors'], 'The response should have 2 errors, one for each font family uninstall failure.' ); From 020570d87f9c472041db27283fda213cf81e7b80 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Tue, 3 Oct 2023 14:22:32 +1100 Subject: [PATCH 03/28] Fix output of Navigation block classnames in the editor. (#54992) --- .../src/navigation/edit/index.js | 43 +++++++++++-------- .../src/navigation/edit/inner-blocks.js | 1 + 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 483e8abaab24f1..7c29f18d4940d4 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -93,6 +93,7 @@ function Navigation( { // navigation block settings. hasSubmenuIndicatorSetting = true, customPlaceholder: CustomPlaceholder = null, + __unstableLayoutClassNames: layoutClassNames, } ) { const { openSubmenusOnClick, @@ -293,23 +294,31 @@ function Navigation( { const isResponsive = 'never' !== overlayMenu; const blockProps = useBlockProps( { ref: navRef, - className: classnames( className, { - 'items-justified-right': justifyContent === 'right', - 'items-justified-space-between': justifyContent === 'space-between', - 'items-justified-left': justifyContent === 'left', - 'items-justified-center': justifyContent === 'center', - 'is-vertical': orientation === 'vertical', - 'no-wrap': flexWrap === 'nowrap', - 'is-responsive': isResponsive, - 'has-text-color': !! textColor.color || !! textColor?.class, - [ getColorClassName( 'color', textColor?.slug ) ]: - !! textColor?.slug, - 'has-background': !! backgroundColor.color || backgroundColor.class, - [ getColorClassName( 'background-color', backgroundColor?.slug ) ]: - !! backgroundColor?.slug, - [ `has-text-decoration-${ textDecoration }` ]: textDecoration, - 'block-editor-block-content-overlay': hasBlockOverlay, - } ), + className: classnames( + className, + { + 'items-justified-right': justifyContent === 'right', + 'items-justified-space-between': + justifyContent === 'space-between', + 'items-justified-left': justifyContent === 'left', + 'items-justified-center': justifyContent === 'center', + 'is-vertical': orientation === 'vertical', + 'no-wrap': flexWrap === 'nowrap', + 'is-responsive': isResponsive, + 'has-text-color': !! textColor.color || !! textColor?.class, + [ getColorClassName( 'color', textColor?.slug ) ]: + !! textColor?.slug, + 'has-background': + !! backgroundColor.color || backgroundColor.class, + [ getColorClassName( + 'background-color', + backgroundColor?.slug + ) ]: !! backgroundColor?.slug, + [ `has-text-decoration-${ textDecoration }` ]: textDecoration, + 'block-editor-block-content-overlay': hasBlockOverlay, + }, + layoutClassNames + ), style: { color: ! textColor?.slug && textColor?.color, backgroundColor: ! backgroundColor?.slug && backgroundColor?.color, diff --git a/packages/block-library/src/navigation/edit/inner-blocks.js b/packages/block-library/src/navigation/edit/inner-blocks.js index 812b37ea71a641..19258213f26e5f 100644 --- a/packages/block-library/src/navigation/edit/inner-blocks.js +++ b/packages/block-library/src/navigation/edit/inner-blocks.js @@ -118,6 +118,7 @@ export default function NavigationInnerBlocks( { : false, placeholder: showPlaceholder ? placeholder : undefined, __experimentalCaptureToolbars: true, + __unstableDisableLayoutClassNames: true, } ); From 5c01d3dcfdfd8d368c5995058766cfbbc6f078ab Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 3 Oct 2023 07:52:10 +0400 Subject: [PATCH 04/28] Block Editor: Avoid double-wrapping selectors when transforming the styles (#54981) * Block Editor: Avoid double-wrapping selectors when transforming the styles * Include space in the check --- .../test/__snapshots__/wrap.js.snap | 19 +++++++++++++------ .../transform-styles/transforms/test/wrap.js | 9 +++++++++ .../utils/transform-styles/transforms/wrap.js | 5 +++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap b/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap index b55f74cfd7bb0b..b9815cdc700b38 100644 --- a/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap +++ b/packages/block-editor/src/utils/transform-styles/transforms/test/__snapshots__/wrap.js.snap @@ -22,6 +22,19 @@ color: red; }" `; +exports[`CSS selector wrap should not double wrap selectors 1`] = ` +".my-namespace h1, +.my-namespace .red { +color: red; +}" +`; + +exports[`CSS selector wrap should replace :root selectors 1`] = ` +".my-namespace { +--my-color: #ff0000; +}" +`; + exports[`CSS selector wrap should replace root tags 1`] = ` ".my-namespace, .my-namespace h1 { @@ -49,9 +62,3 @@ color: red; } }" `; - -exports[`CSS selector wrap should replace :root selectors 1`] = ` -".my-namespace { ---my-color: #ff0000; -}" -`; diff --git a/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js index c26bd3761212b1..a1f4f141d21c9b 100644 --- a/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js +++ b/packages/block-editor/src/utils/transform-styles/transforms/test/wrap.js @@ -83,4 +83,13 @@ describe( 'CSS selector wrap', () => { expect( output ).toMatchSnapshot(); } ); + + it( 'should not double wrap selectors', () => { + const callback = wrap( '.my-namespace' ); + const input = ` .my-namespace h1, .red { color: red; }`; + + const output = traverse( input, callback ); + + expect( output ).toMatchSnapshot(); + } ); } ); diff --git a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js index e61c78dc7e452f..74b940f80352b9 100644 --- a/packages/block-editor/src/utils/transform-styles/transforms/wrap.js +++ b/packages/block-editor/src/utils/transform-styles/transforms/wrap.js @@ -27,6 +27,11 @@ const wrap = return selector; } + // Skip the update when a selector already has a namespace + space (" "). + if ( selector.trim().startsWith( `${ namespace } ` ) ) { + return selector; + } + // Anything other than a root tag is always prefixed. { if ( ! selector.match( IS_ROOT_TAG ) ) { From f112f14e27b29e12eca458d1a85fb6d981f34f3b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Tue, 3 Oct 2023 09:36:42 +0400 Subject: [PATCH 05/28] Don't display the navigation section in template parts details when a menu is missing (#54993) --- .../template-part-navigation-menu-list-item.js | 4 +++- .../template-part-navigation-menu.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu-list-item.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu-list-item.js index 92c2f2450769f2..8a493130ad27db 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu-list-item.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu-list-item.js @@ -24,7 +24,9 @@ export default function TemplatePartNavigationMenuListItem( { id } ) { postType: NAVIGATION_POST_TYPE, } ); - if ( ! id ) return null; + if ( ! id || title === undefined ) { + return null; + } return ( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js index 6512bcbc6345aa..40012ec46a85e4 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/template-part-navigation-menu.js @@ -19,7 +19,9 @@ export default function TemplatePartNavigationMenu( { id } ) { id ); - if ( ! id ) return null; + if ( ! id || title === undefined ) { + return null; + } return ( <> From d36bd6da69156f74aecd51d9fa1681a9e60fe52b Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 3 Oct 2023 08:00:59 +0200 Subject: [PATCH 06/28] Scripts: Properly use CommonJS for default Playwright config (#54988) * Fix path to `globalSetup` in default Playwright config Oversight from #54856 * `module.exports` * Fix default export usage --- packages/scripts/config/playwright.config.js | 4 ++-- packages/scripts/config/playwright/global-setup.js | 2 +- test/e2e/playwright.config.ts | 2 +- test/performance/playwright.config.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/scripts/config/playwright.config.js b/packages/scripts/config/playwright.config.js index 16ee210bdfb86e..6f380017ccdc4f 100644 --- a/packages/scripts/config/playwright.config.js +++ b/packages/scripts/config/playwright.config.js @@ -25,7 +25,7 @@ const config = defineConfig( { snapshotPathTemplate: '{testDir}/{testFileDir}/__snapshots__/{arg}-{projectName}{ext}', globalSetup: fileURLToPath( - new URL( './playwright/global-setup.ts', 'file:' + __filename ).href + new URL( './playwright/global-setup.js', 'file:' + __filename ).href ), use: { baseURL: process.env.WP_BASE_URL || 'http://localhost:8889', @@ -60,4 +60,4 @@ const config = defineConfig( { ], } ); -export default config; +module.exports = config; diff --git a/packages/scripts/config/playwright/global-setup.js b/packages/scripts/config/playwright/global-setup.js index eace349c6309dd..9a4cbde9b7d241 100644 --- a/packages/scripts/config/playwright/global-setup.js +++ b/packages/scripts/config/playwright/global-setup.js @@ -32,4 +32,4 @@ async function globalSetup( config ) { await requestContext.dispose(); } -export default globalSetup; +module.exports = globalSetup; diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index 742ca54f4f2ace..cbbfbac3be4159 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -11,7 +11,7 @@ import { defineConfig, devices } from '@playwright/test'; const baseConfig = require( '@wordpress/scripts/config/playwright.config' ); const config = defineConfig( { - ...baseConfig.default, + ...baseConfig, reporter: process.env.CI ? [ [ 'github' ], [ './config/flaky-tests-reporter.ts' ] ] : 'list', diff --git a/test/performance/playwright.config.ts b/test/performance/playwright.config.ts index 0918b79d3144d2..a8208342ac2d81 100644 --- a/test/performance/playwright.config.ts +++ b/test/performance/playwright.config.ts @@ -13,7 +13,7 @@ const baseConfig = require( '@wordpress/scripts/config/playwright.config' ); process.env.ASSETS_PATH = path.join( __dirname, 'assets' ); const config = defineConfig( { - ...baseConfig.default, + ...baseConfig, reporter: process.env.CI ? './config/performance-reporter.ts' : [ [ 'list' ], [ './config/performance-reporter.ts' ] ], @@ -26,7 +26,7 @@ const config = defineConfig( { new URL( './config/global-setup.ts', 'file:' + __filename ).href ), use: { - ...baseConfig.default.use, + ...baseConfig.use, video: 'off', }, } ); From 283f4e7bb063656ef80425bd2208d8341dea64ec Mon Sep 17 00:00:00 2001 From: Ben Dwyer Date: Tue, 3 Oct 2023 21:13:48 +0100 Subject: [PATCH 07/28] Add template replace flow to template inspector (#54609) * Add a modal to allow template switching * fetch template info * Allow switching to different patterns * Allow switching to different patterns * Add columns * move availble templates to the actions * filter for the correct templates * create the right data structure in the use select * move to a hook * inject theme attribute into pattern again * put the overlay over the top of the dropdown * fix the pattern to templates hook * set the template on click * Also set the blocks * remove calls to set template with the current template, since setting blocks correctly updates the content in the editor * serialize blocks so that we have correctly processed template parts * remove duplicated code * Remove unnecessary mapping * refactor * memoize the patterns * combine the useSelect * Update packages/edit-site/src/components/sidebar-edit-mode/page-panels/hooks.js Co-authored-by: Andrei Draganescu * Fix ESLint error * Only show the button is there is more than 1 pattern * Copy update * Move the hook to a subdir * check that there are patterns * move the check * remove useCallback * change condition to show the button * change condition * move to use editEntityRecord * combine filters * add comments * Update packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js Co-authored-by: Andrei Draganescu --------- Co-authored-by: Andrei Draganescu Co-authored-by: Andrei Draganescu Co-authored-by: George Mamadashvili --- packages/base-styles/_z-index.scss | 1 + .../sidebar-edit-mode/template-panel/hooks.js | 97 +++++++++++++++++++ .../template-panel/replace-template-button.js | 89 +++++++++++++++++ .../template-panel/style.scss | 18 ++++ .../template-panel/template-actions.js | 39 +++++--- 5 files changed, 232 insertions(+), 12 deletions(-) create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js create mode 100644 packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js diff --git a/packages/base-styles/_z-index.scss b/packages/base-styles/_z-index.scss index 12443a30a96656..cbe495d3787cd9 100644 --- a/packages/base-styles/_z-index.scss +++ b/packages/base-styles/_z-index.scss @@ -128,6 +128,7 @@ $z-layers: ( ".block-editor-block-rename-modal": 1000001, ".edit-site-list__rename-modal": 1000001, ".edit-site-swap-template-modal": 1000001, + ".edit-site-template-panel__replace-template-modal": 1000001, // Note: The ConfirmDialog component's z-index is being set to 1000001 in packages/components/src/confirm-dialog/styles.ts // because it uses emotion and not sass. We need it to render on top its parent popover. diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js new file mode 100644 index 00000000000000..b5e5988491396a --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/hooks.js @@ -0,0 +1,97 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { parse } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../../store'; +import { PATTERN_CORE_SOURCES, PATTERN_TYPES } from '../../../utils/constants'; +import { unlock } from '../../../lock-unlock'; + +function injectThemeAttributeInBlockTemplateContent( + block, + currentThemeStylesheet +) { + block.innerBlocks = block.innerBlocks.map( ( innerBlock ) => { + return injectThemeAttributeInBlockTemplateContent( + innerBlock, + currentThemeStylesheet + ); + } ); + + if ( + block.name === 'core/template-part' && + block.attributes.theme === undefined + ) { + block.attributes.theme = currentThemeStylesheet; + } + return block; +} + +function preparePatterns( patterns, template, currentThemeStylesheet ) { + // Filter out duplicates. + const filterOutDuplicatesByName = ( currentItem, index, items ) => + index === items.findIndex( ( item ) => currentItem.name === item.name ); + + // Filter out core patterns. + const filterOutCorePatterns = ( pattern ) => + ! PATTERN_CORE_SOURCES.includes( pattern.source ); + + // Filter only the patterns that are compatible with the current template. + const filterCompatiblePatterns = ( pattern ) => + pattern.templateTypes?.includes( template.slug ); + + return patterns + .filter( + filterOutCorePatterns && + filterOutDuplicatesByName && + filterCompatiblePatterns + ) + .map( ( pattern ) => ( { + ...pattern, + keywords: pattern.keywords || [], + type: PATTERN_TYPES.theme, + blocks: parse( pattern.content, { + __unstableSkipMigrationLogs: true, + } ).map( ( block ) => + injectThemeAttributeInBlockTemplateContent( + block, + currentThemeStylesheet + ) + ), + } ) ); +} + +export function useAvailablePatterns( template ) { + const { blockPatterns, restBlockPatterns, currentThemeStylesheet } = + useSelect( ( select ) => { + const { getSettings } = unlock( select( editSiteStore ) ); + const settings = getSettings(); + + return { + blockPatterns: + settings.__experimentalAdditionalBlockPatterns ?? + settings.__experimentalBlockPatterns, + restBlockPatterns: select( coreStore ).getBlockPatterns(), + currentThemeStylesheet: + select( coreStore ).getCurrentTheme().stylesheet, + }; + }, [] ); + + return useMemo( () => { + const mergedPatterns = [ + ...( blockPatterns || [] ), + ...( restBlockPatterns || [] ), + ]; + return preparePatterns( + mergedPatterns, + template, + currentThemeStylesheet + ); + }, [ blockPatterns, restBlockPatterns, template, currentThemeStylesheet ] ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js new file mode 100644 index 00000000000000..658aacd331debc --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/replace-template-button.js @@ -0,0 +1,89 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { __experimentalBlockPatternsList as BlockPatternsList } from '@wordpress/block-editor'; +import { MenuItem, Modal } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { store as coreStore } from '@wordpress/core-data'; +import { useAsyncList } from '@wordpress/compose'; +import { serialize } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../../store'; + +export default function ReplaceTemplateButton( { + onClick, + availableTemplates, +} ) { + const { editEntityRecord } = useDispatch( coreStore ); + const [ showModal, setShowModal ] = useState( false ); + const onClose = () => { + setShowModal( false ); + }; + + const { postId, postType } = useSelect( ( select ) => { + return { + postId: select( editSiteStore ).getEditedPostId(), + postType: select( editSiteStore ).getEditedPostType(), + }; + }, [] ); + + const onTemplateSelect = async ( selectedTemplate ) => { + onClose(); // Close the template suggestions modal first. + onClick(); + await editEntityRecord( 'postType', postType, postId, { + blocks: selectedTemplate.blocks, + content: serialize( selectedTemplate.blocks ), + } ); + }; + + if ( ! availableTemplates.length || availableTemplates.length < 1 ) { + return null; + } + + return ( + <> + setShowModal( true ) } + > + { __( 'Replace template' ) } + + + { showModal && ( + +
+ +
+
+ ) } + + ); +} + +function TemplatesList( { availableTemplates, onSelect } ) { + const shownTemplates = useAsyncList( availableTemplates ); + + return ( + + ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss index 4c8ef94855dcb1..6eab753e8ad285 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/style.scss @@ -37,3 +37,21 @@ h3.edit-site-template-card__template-areas-title { font-weight: 500; margin: 0 0 $grid-unit-10; } + + +.edit-site-template-panel__replace-template-modal { + z-index: z-index(".edit-site-template-panel__replace-template-modal"); +} + +.edit-site-template-panel__replace-template-modal__content { + column-count: 2; + column-gap: $grid-unit-30; + + @include break-medium() { + column-count: 3; + } + + @include break-wide() { + column-count: 4; + } +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js index b68cf1ff617579..81acb244a11863 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/template-actions.js @@ -11,13 +11,21 @@ import { moreVertical } from '@wordpress/icons'; */ import { store as editSiteStore } from '../../../store'; import isTemplateRevertable from '../../../utils/is-template-revertable'; +import ReplaceTemplateButton from './replace-template-button'; +import { useAvailablePatterns } from './hooks'; export default function Actions( { template } ) { + const availablePatterns = useAvailablePatterns( template ); const { revertTemplate } = useDispatch( editSiteStore ); const isRevertable = isTemplateRevertable( template ); - if ( ! isRevertable ) { + + if ( + ! isRevertable && + ( ! availablePatterns.length || availablePatterns.length < 1 ) + ) { return null; } + return ( { ( { onClose } ) => ( - { - revertTemplate( template ); - onClose(); - } } - > - { __( 'Clear customizations' ) } - + { isRevertable && ( + { + revertTemplate( template ); + onClose(); + } } + > + { __( 'Clear customizations' ) } + + ) } + ) } From fba6504a4536f81b1a4beb83a70c078bbd2f3dd7 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:21:31 +1100 Subject: [PATCH 08/28] List View: Fix performance issue when selecting all blocks (#54900) * List View: Fix performance issue when selecting all blocks within the editor canvas in long posts * Add a comment, rename const * Move block focus to be performed only once at the root of the list view, instead of within each block --- .../src/components/list-view/block.js | 46 ++----------------- .../src/components/list-view/branch.js | 12 ++++- .../src/components/list-view/index.js | 12 +++-- .../src/components/list-view/utils.js | 37 +++++++++++++++ 4 files changed, 59 insertions(+), 48 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block.js b/packages/block-editor/src/components/list-view/block.js index 43fe4df4cb75aa..375f39a7cc3c81 100644 --- a/packages/block-editor/src/components/list-view/block.js +++ b/packages/block-editor/src/components/list-view/block.js @@ -13,16 +13,9 @@ import { } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { moreVertical } from '@wordpress/icons'; -import { - useState, - useRef, - useEffect, - useCallback, - memo, -} from '@wordpress/element'; +import { useState, useRef, useCallback, memo } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; import { sprintf, __ } from '@wordpress/i18n'; -import { focus } from '@wordpress/dom'; import { ESCAPE } from '@wordpress/keycodes'; /** @@ -36,7 +29,7 @@ import { } from '../block-mover/button'; import ListViewBlockContents from './block-contents'; import { useListViewContext } from './context'; -import { getBlockPositionDescription } from './utils'; +import { getBlockPositionDescription, focusListItem } from './utils'; import { store as blockEditorStore } from '../../store'; import useBlockDisplayInformation from '../use-block-display-information'; import { useBlockLock } from '../block-lock'; @@ -120,7 +113,6 @@ function ListViewBlock( { ); const { - isTreeGridMounted, expand, collapse, BlockSettingsMenu, @@ -142,15 +134,6 @@ function ListViewBlock( { { 'is-visible': isHovered || isFirstSelectedBlock } ); - // If ListView has experimental features related to the Persistent List View, - // only focus the selected list item on mount; otherwise the list would always - // try to steal the focus from the editor canvas. - useEffect( () => { - if ( ! isTreeGridMounted && isSelected ) { - cellRef.current.focus(); - } - }, [] ); - // If multiple blocks are selected, deselect all blocks when the user // presses the escape key. const onKeyDown = ( event ) => { @@ -188,30 +171,7 @@ function ListViewBlock( { selectBlock( undefined, focusClientId, null, null ); } - const getFocusElement = () => { - const row = treeGridElementRef.current?.querySelector( - `[role=row][data-block="${ focusClientId }"]` - ); - if ( ! row ) return null; - // Focus the first focusable in the row, which is the ListViewBlockSelectButton. - return focus.focusable.find( row )[ 0 ]; - }; - - let focusElement = getFocusElement(); - if ( focusElement ) { - focusElement.focus(); - } else { - // The element hasn't been painted yet. Defer focusing on the next frame. - // This could happen when all blocks have been deleted and the default block - // hasn't been added to the editor yet. - window.requestAnimationFrame( () => { - focusElement = getFocusElement(); - // Ignore if the element still doesn't exist. - if ( focusElement ) { - focusElement.focus(); - } - } ); - } + focusListItem( focusClientId, treeGridElementRef ); }, [ selectBlock, treeGridElementRef ] ); diff --git a/packages/block-editor/src/components/list-view/branch.js b/packages/block-editor/src/components/list-view/branch.js index d3b555c055afd1..e2e27f5f2cb5af 100644 --- a/packages/block-editor/src/components/list-view/branch.js +++ b/packages/block-editor/src/components/list-view/branch.js @@ -168,8 +168,18 @@ function ListViewBranch( props ) { ); const isSelectedBranch = isBranchSelected || ( isSelected && hasNestedBlocks ); + + // To avoid performance issues, we only render blocks that are in view, + // or blocks that are selected or dragged. If a block is selected, + // it is only counted if it is the first of the block selection. + // This prevents the entire tree from being rendered when a branch is + // selected, or a user selects all blocks, while still enabling scroll + // into view behavior when selecting a block or opening the list view. const showBlock = - isDragged || blockInView || isSelected || isBranchDragged; + isDragged || + blockInView || + isBranchDragged || + ( isSelected && clientId === selectedClientIds[ 0 ] ); return ( { showBlock && ( diff --git a/packages/block-editor/src/components/list-view/index.js b/packages/block-editor/src/components/list-view/index.js index 917ebd883aa8d1..085864c4c88f45 100644 --- a/packages/block-editor/src/components/list-view/index.js +++ b/packages/block-editor/src/components/list-view/index.js @@ -32,6 +32,7 @@ import useListViewDropZone from './use-list-view-drop-zone'; import useListViewExpandSelectedItem from './use-list-view-expand-selected-item'; import { store as blockEditorStore } from '../../store'; import { BlockSettingsDropdown } from '../block-settings-menu/block-settings-dropdown'; +import { focusListItem } from './utils'; const expanded = ( state, action ) => { if ( Array.isArray( action.clientIds ) ) { @@ -132,8 +133,6 @@ function ListViewComponent( const elementRef = useRef(); const treeGridRef = useMergeRefs( [ elementRef, dropZoneRef, ref ] ); - const isMounted = useRef( false ); - const [ insertedBlock, setInsertedBlock ] = useState( null ); const { setSelectedTreeId } = useListViewExpandSelectedItem( { @@ -156,7 +155,13 @@ function ListViewComponent( [ setSelectedTreeId, updateBlockSelection, onSelect, getBlock ] ); useEffect( () => { - isMounted.current = true; + // If a blocks are already selected when the list view is initially + // mounted, shift focus to the first selected block. + if ( selectedClientIds?.length ) { + focusListItem( selectedClientIds[ 0 ], elementRef ); + } + // Disable reason: Only focus on the selected item when the list view is mounted. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [] ); const expand = useCallback( @@ -204,7 +209,6 @@ function ListViewComponent( const contextValue = useMemo( () => ( { - isTreeGridMounted: isMounted.current, draggedClientIds, expandedState, expand, diff --git a/packages/block-editor/src/components/list-view/utils.js b/packages/block-editor/src/components/list-view/utils.js index f53f5a4cd4884a..632173e120691f 100644 --- a/packages/block-editor/src/components/list-view/utils.js +++ b/packages/block-editor/src/components/list-view/utils.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; +import { focus } from '@wordpress/dom'; export const getBlockPositionDescription = ( position, siblingCount, level ) => sprintf( @@ -56,3 +57,39 @@ export function getCommonDepthClientIds( end, }; } + +/** + * Shift focus to the list view item associated with a particular clientId. + * + * @typedef {import('@wordpress/element').RefObject} RefObject + * + * @param {string} focusClientId The client ID of the block to focus. + * @param {RefObject} treeGridElementRef The container element to search within. + */ +export function focusListItem( focusClientId, treeGridElementRef ) { + const getFocusElement = () => { + const row = treeGridElementRef.current?.querySelector( + `[role=row][data-block="${ focusClientId }"]` + ); + if ( ! row ) return null; + // Focus the first focusable in the row, which is the ListViewBlockSelectButton. + return focus.focusable.find( row )[ 0 ]; + }; + + let focusElement = getFocusElement(); + if ( focusElement ) { + focusElement.focus(); + } else { + // The element hasn't been painted yet. Defer focusing on the next frame. + // This could happen when all blocks have been deleted and the default block + // hasn't been added to the editor yet. + window.requestAnimationFrame( () => { + focusElement = getFocusElement(); + + // Ignore if the element still doesn't exist. + if ( focusElement ) { + focusElement.focus(); + } + } ); + } +} From 299844259c6a4a2dabaf3c5f707af6b38c21a8b5 Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Wed, 4 Oct 2023 15:17:07 +1100 Subject: [PATCH 09/28] Fix left and right aligmnent in children of Post Template (#54997) * Fix left and right aligmnent in children of Post Template * Add align center styles * Fix image placeholder disappearing --- .../src/post-featured-image/style.scss | 4 ++++ .../src/post-template/style.scss | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/block-library/src/post-featured-image/style.scss b/packages/block-library/src/post-featured-image/style.scss index 4821e634b60327..e740b8c56e608c 100644 --- a/packages/block-library/src/post-featured-image/style.scss +++ b/packages/block-library/src/post-featured-image/style.scss @@ -39,4 +39,8 @@ } } } + + &:where(.alignleft, .alignright) { + width: 100%; + } } diff --git a/packages/block-library/src/post-template/style.scss b/packages/block-library/src/post-template/style.scss index 00305a17123369..4af30e30b23098 100644 --- a/packages/block-library/src/post-template/style.scss +++ b/packages/block-library/src/post-template/style.scss @@ -37,3 +37,23 @@ grid-template-columns: 1fr; } } + +.wp-block-post-template-is-layout-constrained > li > .alignright, +.wp-block-post-template-is-layout-flow > li > .alignright { + float: right; + margin-inline-start: 2em; + margin-inline-end: 0; +} + +.wp-block-post-template-is-layout-constrained > li > .alignleft, +.wp-block-post-template-is-layout-flow > li > .alignleft { + float: left; + margin-inline-start: 0; + margin-inline-end: 2em; +} + +.wp-block-post-template-is-layout-constrained > li > .aligncenter, +.wp-block-post-template-is-layout-flow > li > .aligncenter { + margin-inline-start: auto; + margin-inline-end: auto; +} From e2eb0ab7873fd5369bde1e9ef0c34086ddc377f7 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 4 Oct 2023 08:42:03 +0400 Subject: [PATCH 10/28] Site Editor: Avoid stale navigation block values when parsing entity record (#54996) --- .../use-navigation-menu-content.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-navigation-menu-content.js b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-navigation-menu-content.js index 6c44ea5fbc9bbe..249124b1054cec 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-navigation-menu-content.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pattern/use-navigation-menu-content.js @@ -10,6 +10,16 @@ import TemplatePartNavigationMenus from './template-part-navigation-menus'; import useEditedEntityRecord from '../use-edited-entity-record'; import { TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; +function getBlocksFromRecord( record ) { + if ( record?.blocks ) { + return record?.blocks; + } + + return record?.content && typeof record.content !== 'function' + ? parse( record.content ) + : []; +} + /** * Retrieves a list of specific blocks from a given tree of blocks. * @@ -60,11 +70,7 @@ export default function useNavigationMenuContent( postType, postId ) { return; } - const blocks = - record?.content && typeof record.content !== 'function' - ? parse( record.content ) - : []; - + const blocks = getBlocksFromRecord( record ); const navigationBlocks = getBlocksOfTypeFromBlocks( 'core/navigation', blocks From d11de6728cfafd3c7397889cb85ced983ac394c4 Mon Sep 17 00:00:00 2001 From: mujuonly Date: Sat, 23 Sep 2023 08:00:33 +0530 Subject: [PATCH 11/28] Removed unwanted space from the string (#54654) * Update styles.js Removed unwanted space with translation * Update deleted-navigation-warning.js Unwanted space at the end of the string shows translation warning. * Update inspector-controls.js Unwanted space at the end of the string shows translation warning --- packages/block-library/src/cover/edit/inspector-controls.js | 2 +- .../src/navigation/edit/deleted-navigation-warning.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/cover/edit/inspector-controls.js b/packages/block-library/src/cover/edit/inspector-controls.js index 02058243f9f781..ed2501a0d0ec52 100644 --- a/packages/block-library/src/cover/edit/inspector-controls.js +++ b/packages/block-library/src/cover/edit/inspector-controls.js @@ -149,7 +149,7 @@ export default function CoverInspectorControls( { 'The
element should represent introductory content, typically a group of introductory or navigational aids.' ), main: __( - 'The
element should be used for the primary content of your document only. ' + 'The
element should be used for the primary content of your document only.' ), section: __( "The
element should represent a standalone portion of the document that can't be better represented by another element." diff --git a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js index c787b90b76682f..b9dd11763f77c6 100644 --- a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js +++ b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js @@ -8,7 +8,7 @@ import { __ } from '@wordpress/i18n'; function DeletedNavigationWarning( { onCreateNew } ) { return ( - { __( 'Navigation menu has been deleted or is unavailable. ' ) } + { __( 'Navigation menu has been deleted or is unavailable.' ) } From 3c227eb06ad13add4b637603b3e74d479a21b214 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Wed, 4 Oct 2023 10:44:37 +0100 Subject: [PATCH 12/28] Fix Deleted Navigation Menu warning string (#55033) --- .../navigation/edit/deleted-navigation-warning.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js index b9dd11763f77c6..6386cee71431e0 100644 --- a/packages/block-library/src/navigation/edit/deleted-navigation-warning.js +++ b/packages/block-library/src/navigation/edit/deleted-navigation-warning.js @@ -4,14 +4,19 @@ import { Warning } from '@wordpress/block-editor'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { createInterpolateElement } from '@wordpress/element'; function DeletedNavigationWarning( { onCreateNew } ) { return ( - { __( 'Navigation menu has been deleted or is unavailable.' ) } - + { createInterpolateElement( + __( + 'Navigation menu has been deleted or is unavailable. ' + ), + { + button: ' - . $img[0]; + + $button = + $img[0] + . ''; + $body_content = preg_replace( '/]+>/', $button, $body_content ); // We need both a responsive image and an enlarged image to animate @@ -183,7 +184,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { // image is a copy of the one in the body, which animates immediately // as the lightbox is opened, while the enlarged one is a full-sized // version that will likely still be loading as the animation begins. - $m = new WP_HTML_Tag_Processor( $content ); + $m = new WP_HTML_Tag_Processor( $block_content ); $m->next_tag( 'figure' ); $m->add_class( 'responsive-image' ); $m->next_tag( 'img' ); @@ -199,7 +200,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { $m->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' ); $initial_image_content = $m->get_updated_html(); - $q = new WP_HTML_Tag_Processor( $content ); + $q = new WP_HTML_Tag_Processor( $block_content ); $q->next_tag( 'figure' ); $q->add_class( 'enlarged-image' ); $q->next_tag( 'img' ); @@ -219,7 +220,7 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { $close_button_icon = ''; $close_button_color = esc_attr( wp_get_global_styles( array( 'color', 'text' ) ) ); - $dialog_label = $alt_attribute ? esc_attr( $alt_attribute ) : esc_attr__( 'Image', 'gutenberg' ); + $dialog_label = esc_attr__( 'Enlarged image', 'gutenberg' ); $close_button_label = esc_attr__( 'Close', 'gutenberg' ); $lightbox_html = <<next_tag( 'img' ); $alt_attribute = $processor->get_attribute( 'alt' ); - if ( null !== $alt_attribute ) { + // An empty alt attribute `alt=""` is valid for decorative images. + if ( is_string( $alt_attribute ) ) { $alt_attribute = trim( $alt_attribute ); } + // It only makes sense to append the alt text to the button aria-label when the alt text is non-empty. if ( $alt_attribute ) { /* translators: %s: Image alt text. */ $aria_label = sprintf( __( 'Enlarge image: %s' ), $alt_attribute ); } - $content = $processor->get_updated_html(); // Currently, we are only enabling the zoom animation. $lightbox_animation = 'zoom'; - // We want to store the src in the context so we can set it dynamically when the lightbox is opened. - $z = new WP_HTML_Tag_Processor( $content ); - $z->next_tag( 'img' ); - + // Note: We want to store the `src` in the context so we + // can set it dynamically when the lightbox is opened. if ( isset( $block['attrs']['id'] ) ) { $img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] ); $img_metadata = wp_get_attachment_metadata( $block['attrs']['id'] ); $img_width = $img_metadata['width']; $img_height = $img_metadata['height']; } else { - $img_uploaded_src = $z->get_attribute( 'src' ); + $img_uploaded_src = $processor->get_attribute( 'src' ); $img_width = 'none'; $img_height = 'none'; } @@ -160,7 +163,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { $scale_attr = false; } - $w = new WP_HTML_Tag_Processor( $content ); + $w = new WP_HTML_Tag_Processor( $block_content ); $w->next_tag( 'figure' ); $w->add_class( 'wp-lightbox-container' ); $w->set_attribute( 'data-wp-interactive', true ); @@ -180,7 +183,8 @@ function block_core_image_render_lightbox( $block_content, $block ) { "imageCurrentSrc": "", "targetWidth": "%s", "targetHeight": "%s", - "scaleAttr": "%s" + "scaleAttr": "%s", + "dialogLabel": "%s" } } }', @@ -188,7 +192,8 @@ function block_core_image_render_lightbox( $block_content, $block ) { $img_uploaded_src, $img_width, $img_height, - $scale_attr + $scale_attr, + __( 'Enlarged image' ) ) ); $w->next_tag( 'img' ); @@ -200,19 +205,20 @@ function block_core_image_render_lightbox( $block_content, $block ) { // Wrap the image in the body content with a button. $img = null; preg_match( '/]+>/', $body_content, $img ); - $button = - '' - . $img[0]; + + $button = + $img[0] + . ''; + $body_content = preg_replace( '/]+>/', $button, $body_content ); // We need both a responsive image and an enlarged image to animate @@ -220,7 +226,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { // image is a copy of the one in the body, which animates immediately // as the lightbox is opened, while the enlarged one is a full-sized // version that will likely still be loading as the animation begins. - $m = new WP_HTML_Tag_Processor( $content ); + $m = new WP_HTML_Tag_Processor( $block_content ); $m->next_tag( 'figure' ); $m->add_class( 'responsive-image' ); $m->next_tag( 'img' ); @@ -236,7 +242,7 @@ function block_core_image_render_lightbox( $block_content, $block ) { $m->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' ); $initial_image_content = $m->get_updated_html(); - $q = new WP_HTML_Tag_Processor( $content ); + $q = new WP_HTML_Tag_Processor( $block_content ); $q->next_tag( 'figure' ); $q->add_class( 'enlarged-image' ); $q->next_tag( 'img' ); @@ -268,20 +274,16 @@ function block_core_image_render_lightbox( $block_content, $block ) { } $close_button_icon = ''; - $dialog_label = $alt_attribute ? esc_attr( $alt_attribute ) : esc_attr__( 'Image' ); $close_button_label = esc_attr__( 'Close' ); $lightbox_html = << -
+ HTML; @@ -302,11 +304,13 @@ function block_core_image_render_lightbox( $block_content, $block ) { } /** - * Ensure that the view script has the `wp-interactivity` dependency. + * Ensures that the view script has the `wp-interactivity` dependency. * * @since 6.4.0 * * @global WP_Scripts $wp_scripts + * + * @return void */ function block_core_image_ensure_interactivity_dependency() { global $wp_scripts; @@ -322,6 +326,8 @@ function block_core_image_ensure_interactivity_dependency() { /** * Registers the `core/image` block on server. + * + * @return void */ function register_block_core_image() { register_block_type_from_metadata( diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index 752ff773394a44..2ef602982e57b5 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -154,6 +154,8 @@ .wp-lightbox-container { position: relative; + display: flex; + flex-direction: column; button { border: none; @@ -193,11 +195,16 @@ .close-button { position: absolute; - top: calc(env(safe-area-inset-top) + 20px); - right: calc(env(safe-area-inset-right) + 20px); + top: calc(env(safe-area-inset-top) + 16px); // equivalent to $grid-unit-20 + right: calc(env(safe-area-inset-right) + 16px); // equivalent to $grid-unit-20 padding: 0; cursor: pointer; z-index: 5000000; + min-width: 40px; // equivalent to $button-size-next-default-40px + min-height: 40px; // equivalent to $button-size-next-default-40px + display: flex; + align-items: center; + justify-content: center; &:hover, &:focus, diff --git a/packages/block-library/src/image/view.js b/packages/block-library/src/image/view.js index 13f20c9cd7cb68..3eb47dcc7cab4b 100644 --- a/packages/block-library/src/image/view.js +++ b/packages/block-library/src/image/view.js @@ -227,7 +227,17 @@ store( roleAttribute: ( { context } ) => { return context.core.image.lightboxEnabled ? 'dialog' - : ''; + : null; + }, + ariaModal: ( { context } ) => { + return context.core.image.lightboxEnabled + ? 'true' + : null; + }, + dialogLabel: ( { context } ) => { + return context.core.image.lightboxEnabled + ? context.core.image.dialogLabel + : null; }, lightboxObjectFit: ( { context } ) => { if ( context.core.image.initialized ) { @@ -237,7 +247,7 @@ store( enlargedImgSrc: ( { context } ) => { return context.core.image.initialized ? context.core.image.imageUploadedSrc - : ''; + : 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; }, }, }, @@ -360,9 +370,9 @@ function setStyles( context, event ) { naturalHeight, offsetWidth: originalWidth, offsetHeight: originalHeight, - } = event.target.nextElementSibling; + } = event.target.previousElementSibling; let { x: screenPosX, y: screenPosY } = - event.target.nextElementSibling.getBoundingClientRect(); + event.target.previousElementSibling.getBoundingClientRect(); // Natural ratio of the image clicked to open the lightbox. const naturalRatio = naturalWidth / naturalHeight; From 9398b4a992361d4aadc99300d6e2bd65bcc18473 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 9 Oct 2023 13:35:35 +1300 Subject: [PATCH 26/28] Patterns: Add category selector to pattern creation modal (#55024) --------- Co-authored-by: Kai Hao --- .../src/components/category-selector.js | 70 ++++++++----------- .../src/components/create-pattern-modal.js | 51 ++++++++++++-- packages/patterns/src/components/style.scss | 30 +++++++- 3 files changed, 103 insertions(+), 48 deletions(-) diff --git a/packages/patterns/src/components/category-selector.js b/packages/patterns/src/components/category-selector.js index 397d851d3886b9..7f00350e278ecf 100644 --- a/packages/patterns/src/components/category-selector.js +++ b/packages/patterns/src/components/category-selector.js @@ -4,8 +4,6 @@ import { __ } from '@wordpress/i18n'; import { useMemo, useState } from '@wordpress/element'; import { FormTokenField } from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; -import { store as coreStore } from '@wordpress/core-data'; import { useDebounce } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; @@ -13,40 +11,29 @@ const unescapeString = ( arg ) => { return decodeEntities( arg ); }; -const EMPTY_ARRAY = []; -const MAX_TERMS_SUGGESTIONS = 20; -const DEFAULT_QUERY = { - per_page: MAX_TERMS_SUGGESTIONS, - _fields: 'id,name', - context: 'view', -}; export const CATEGORY_SLUG = 'wp_pattern_category'; -export default function CategorySelector( { values, onChange } ) { +export default function CategorySelector( { + categoryTerms, + onChange, + categoryMap, +} ) { const [ search, setSearch ] = useState( '' ); const debouncedSearch = useDebounce( setSearch, 500 ); - const { searchResults } = useSelect( - ( select ) => { - const { getEntityRecords } = select( coreStore ); - - return { - searchResults: !! search - ? getEntityRecords( 'taxonomy', CATEGORY_SLUG, { - ...DEFAULT_QUERY, - search, - } ) - : EMPTY_ARRAY, - }; - }, - [ search ] - ); - const suggestions = useMemo( () => { - return ( searchResults ?? [] ).map( ( term ) => - unescapeString( term.name ) - ); - }, [ searchResults ] ); + return Array.from( categoryMap.values() ) + .map( ( category ) => unescapeString( category.label ) ) + .filter( ( category ) => { + if ( search !== '' ) { + return category + .toLowerCase() + .includes( search.toLowerCase() ); + } + return true; + } ) + .sort( ( a, b ) => a.localeCompare( b ) ); + }, [ search, categoryMap ] ); function handleChange( termNames ) { const uniqueTerms = termNames.reduce( ( terms, newTerm ) => { @@ -64,17 +51,16 @@ export default function CategorySelector( { values, onChange } ) { } return ( - <> - - + ); } diff --git a/packages/patterns/src/components/create-pattern-modal.js b/packages/patterns/src/components/create-pattern-modal.js index 531936da5e5c28..37dd725ef9226a 100644 --- a/packages/patterns/src/components/create-pattern-modal.js +++ b/packages/patterns/src/components/create-pattern-modal.js @@ -10,8 +10,8 @@ import { ToggleControl, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useState } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; +import { useState, useMemo } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; import { store as noticesStore } from '@wordpress/notices'; import { store as coreStore } from '@wordpress/core-data'; @@ -42,6 +42,42 @@ export default function CreatePatternModal( { const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); + const { corePatternCategories, userPatternCategories } = useSelect( + ( select ) => { + const { getUserPatternCategories, getBlockPatternCategories } = + select( coreStore ); + + return { + corePatternCategories: getBlockPatternCategories(), + userPatternCategories: getUserPatternCategories(), + }; + } + ); + + const categoryMap = useMemo( () => { + // Merge the user and core pattern categories and remove any duplicates. + const uniqueCategories = new Map(); + [ ...userPatternCategories, ...corePatternCategories ].forEach( + ( category ) => { + if ( + ! uniqueCategories.has( category.label ) && + // There are two core categories with `Post` label so explicitly remove the one with + // the `query` slug to avoid any confusion. + category.name !== 'query' + ) { + // We need to store the name separately as this is used as the slug in the + // taxonomy and may vary from the label. + uniqueCategories.set( category.label, { + label: category.label, + value: category.label, + name: category.name, + } ); + } + } + ); + return uniqueCategories; + }, [ userPatternCategories, corePatternCategories ] ); + async function onCreate( patternTitle, sync ) { if ( ! title || isSaving ) { return; @@ -84,10 +120,16 @@ export default function CreatePatternModal( { */ async function findOrCreateTerm( term ) { try { + // We need to match any existing term to the correct slug to prevent duplicates, eg. + // the core `Headers` category uses the singular `header` as the slug. + const existingTerm = categoryMap.get( term ); + const termData = existingTerm + ? { name: existingTerm.label, slug: existingTerm.name } + : { name: term }; const newTerm = await saveEntityRecord( 'taxonomy', CATEGORY_SLUG, - { name: term }, + termData, { throwOnError: true } ); invalidateResolution( 'getUserPatternCategories' ); @@ -126,8 +168,9 @@ export default function CreatePatternModal( { className="patterns-create-modal__name-input" /> [role="document"] { + width: 350px; + } + .patterns-menu-items__convert-modal-categories { - max-width: 300px; + width: 100%; + position: relative; + min-height: 40px; + } + .components-form-token-field__suggestions-list { + position: absolute; + box-sizing: border-box; + z-index: 1; + background-color: $white; + // Account for the border width of the token field. + width: calc(100% + 2px); + left: -1px; + min-width: initial; + border: 1px solid var(--wp-admin-theme-color); + border-top: none; + box-shadow: 0 0 0 0.5px var(--wp-admin-theme-color); + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; } } .patterns-create-modal__name-input input[type="text"] { - min-height: 34px; + // Match the minimal height of the category selector. + min-height: 40px; + // Override the default 1px margin-x. + margin: 0; } From 2e4bba141b03b8988989e7091f8df7a009da17df Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:52:01 +1100 Subject: [PATCH 27/28] Iframe: Fix positioning when dragging over an iframe (#55150) --- packages/block-editor/src/components/iframe/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 9a6371dcaf4256..28697324aa8b83 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -38,7 +38,14 @@ function bubbleEvent( event, Constructor, frame ) { init[ key ] = event[ key ]; } - if ( event instanceof frame.ownerDocument.defaultView.MouseEvent ) { + // Check if the event is a MouseEvent generated within the iframe. + // If so, adjust the coordinates to be relative to the position of + // the iframe. This ensures that components such as Draggable + // receive coordinates relative to the window, instead of relative + // to the iframe. Without this, the Draggable event handler would + // result in components "jumping" position as soon as the user + // drags over the iframe. + if ( event instanceof frame.contentDocument.defaultView.MouseEvent ) { const rect = frame.getBoundingClientRect(); init.clientX += rect.left; init.clientY += rect.top; From 95e1f627841f59117b8ef15496adbac1bc10b92b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 9 Oct 2023 11:04:51 +0400 Subject: [PATCH 28/28] Site Editor: Fix template part area listing when a template has no edits (#55115) * Alternative: Fix template part area listing when a template has no edits * Fix typos --- packages/edit-site/src/store/selectors.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/edit-site/src/store/selectors.js b/packages/edit-site/src/store/selectors.js index 3f44ab57ba8072..9e861c7567e4ac 100644 --- a/packages/edit-site/src/store/selectors.js +++ b/packages/edit-site/src/store/selectors.js @@ -292,22 +292,21 @@ export function isSaveViewOpened( state ) { * @return {Array} Template parts and their blocks in an array. */ export const getCurrentTemplateTemplateParts = createRegistrySelector( - ( select ) => ( state ) => { - const templateType = getEditedPostType( state ); - const templateId = getEditedPostId( state ); - const template = select( coreDataStore ).getEditedEntityRecord( - 'postType', - templateType, - templateId - ); - + ( select ) => () => { const templateParts = select( coreDataStore ).getEntityRecords( 'postType', TEMPLATE_PART_POST_TYPE, { per_page: -1 } ); - return getFilteredTemplatePartBlocks( template.blocks, templateParts ); + const clientIds = + select( blockEditorStore ).__experimentalGetGlobalBlocksByName( + 'core/template-part' + ); + const blocks = + select( blockEditorStore ).getBlocksByClientId( clientIds ); + + return getFilteredTemplatePartBlocks( blocks, templateParts ); } );