From b8f2ad080aeac1aa97ca76ffca553d7eed083c25 Mon Sep 17 00:00:00 2001 From: Kerry Liu Date: Tue, 29 Jun 2021 13:19:09 -0700 Subject: [PATCH 01/13] Safari: see if compositing layer size is more reasonable when position fixed divs are not inserted into content (#32824) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Safari: remove position:fixed div in scrollable content so compositing layers are of more reasonable size * Prevent scroll after focusing focus capture element * Prevent scrolling when tabbing back to a focus capture div Co-authored-by: Ella van Durpe --- .../components/writing-flow/use-tab-nav.js | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/packages/block-editor/src/components/writing-flow/use-tab-nav.js b/packages/block-editor/src/components/writing-flow/use-tab-nav.js index b94a6cfb86fb9..04456706a6bb4 100644 --- a/packages/block-editor/src/components/writing-flow/use-tab-nav.js +++ b/packages/block-editor/src/components/writing-flow/use-tab-nav.js @@ -12,12 +12,6 @@ import { useRef } from '@wordpress/element'; */ import { store as blockEditorStore } from '../../store'; -/** - * Useful for positioning an element within the viewport so focussing the - * element does not scroll the page. - */ -const PREVENT_SCROLL_ON_FOCUS = { position: 'fixed' }; - function isFormElement( element ) { const { tagName } = element; return ( @@ -75,7 +69,6 @@ export default function useTabNav() { ref={ focusCaptureBeforeRef } tabIndex={ focusCaptureTabIndex } onFocus={ onFocusCapture } - style={ PREVENT_SCROLL_ON_FOCUS } /> ); @@ -84,7 +77,6 @@ export default function useTabNav() { ref={ focusCaptureAfterRef } tabIndex={ focusCaptureTabIndex } onFocus={ onFocusCapture } - style={ PREVENT_SCROLL_ON_FOCUS } /> ); @@ -131,16 +123,62 @@ export default function useTabNav() { // doesn't refocus this block and so it allows default behaviour // (moving focus to the next tabbable element). noCapture.current = true; - next.current.focus(); + + // Focusing the focus capture element, which is located above and + // below the editor, should not scroll the page all the way up or + // down. + next.current.focus( { preventScroll: true } ); } function onFocusOut( event ) { lastFocus.current = event.target; } + // When tabbing back to an element in block list, this event handler prevents scrolling if the + // focus capture divs (before/after) are outside of the viewport. (For example shift+tab back to a paragraph + // when focus is on a sidebar element. This prevents the scrollable writing area from jumping either to the + // top or bottom of the document. + // + // Note that it isn't possible to disable scrolling in the onFocus event. We need to intercept this + // earlier in the keypress handler, and call focus( { preventScroll: true } ) instead. + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus#parameters + function preventScrollOnTab( event ) { + if ( event.keyCode !== TAB ) { + return; + } + + if ( event.target?.getAttribute( 'role' ) === 'region' ) { + return; + } + + if ( container.current === event.target ) { + return; + } + + const isShift = event.shiftKey; + const direction = isShift ? 'findPrevious' : 'findNext'; + const target = focus.tabbable[ direction ]( event.target ); + // only do something when the next tabbable is a focus capture div (before/after) + if ( + target === focusCaptureBeforeRef.current || + target === focusCaptureAfterRef.current + ) { + event.preventDefault(); + target.focus( { preventScroll: true } ); + } + } + + node.ownerDocument.defaultView.addEventListener( + 'keydown', + preventScrollOnTab + ); node.addEventListener( 'keydown', onKeyDown ); node.addEventListener( 'focusout', onFocusOut ); return () => { + node.ownerDocument.defaultView.removeEventListener( + 'keydown', + preventScrollOnTab + ); node.removeEventListener( 'keydown', onKeyDown ); node.removeEventListener( 'focusout', onFocusOut ); }; From 7dac75fb905820884cd93366eb764efcbf60b129 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 6 Jul 2021 11:12:18 -0500 Subject: [PATCH 02/13] Site Logo Block: update Site Logo block UI and option syncing (#33179) --- packages/block-library/src/site-logo/edit.js | 90 +++++++++++-------- .../block-library/src/site-logo/editor.scss | 27 ++---- .../block-library/src/site-logo/index.php | 70 +++++++-------- 3 files changed, 92 insertions(+), 95 deletions(-) diff --git a/packages/block-library/src/site-logo/edit.js b/packages/block-library/src/site-logo/edit.js index aecda3ec75d2b..e3cd822641797 100644 --- a/packages/block-library/src/site-logo/edit.js +++ b/packages/block-library/src/site-logo/edit.js @@ -17,7 +17,7 @@ import { ResizableBox, Spinner, ToggleControl, - Icon, + Placeholder, } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; import { @@ -257,37 +257,46 @@ export default function LogoEdit( { const [ error, setError ] = useState(); const ref = useRef(); - const { siteLogoId, canUserEdit, url, mediaItemData } = useSelect( - ( select ) => { - const { canUser, getEntityRecord, getEditedEntityRecord } = select( - coreStore - ); - const siteSettings = getEditedEntityRecord( 'root', 'site' ); - const siteData = getEntityRecord( 'root', '__unstableBase' ); - const _siteLogo = siteSettings?.site_logo; - const _readOnlyLogo = siteData?.site_logo; - const _canUserEdit = canUser( 'update', 'settings' ); - const _siteLogoId = _siteLogo || _readOnlyLogo; - const mediaItem = - _siteLogoId && - select( coreStore ).getEntityRecord( - 'root', - 'media', - _siteLogoId, - { context: 'view' } - ); - return { - siteLogoId: _siteLogoId, - canUserEdit: _canUserEdit, - url: siteData?.url, - mediaItemData: mediaItem && { - url: mediaItem.source_url, - alt: mediaItem.alt_text, - }, - }; - }, - [] - ); + const { + siteLogoId, + canUserEdit, + url, + mediaItemData, + isRequestingMediaItem, + } = useSelect( ( select ) => { + const { canUser, getEntityRecord, getEditedEntityRecord } = select( + coreStore + ); + const siteSettings = getEditedEntityRecord( 'root', 'site' ); + const siteData = getEntityRecord( 'root', '__unstableBase' ); + const _siteLogo = siteSettings?.site_logo; + const _readOnlyLogo = siteData?.site_logo; + const _canUserEdit = canUser( 'update', 'settings' ); + const _siteLogoId = _siteLogo || _readOnlyLogo; + const mediaItem = + _siteLogoId && + select( coreStore ).getEntityRecord( 'root', 'media', _siteLogoId, { + context: 'view', + } ); + const _isRequestingMediaItem = + _siteLogoId && + ! select( coreStore ).hasFinishedResolution( 'getEntityRecord', [ + 'root', + 'media', + _siteLogoId, + { context: 'view' }, + ] ); + return { + siteLogoId: _siteLogoId, + canUserEdit: _canUserEdit, + url: siteData?.url, + mediaItemData: mediaItem && { + url: mediaItem.source_url, + alt: mediaItem.alt_text, + }, + isRequestingMediaItem: _isRequestingMediaItem, + }; + }, [] ); const { editEntityRecord } = useDispatch( coreStore ); const setLogo = ( newValue ) => @@ -336,7 +345,7 @@ export default function LogoEdit( { const label = __( 'Site Logo' ); let logoImage; - const isLoading = siteLogoId === undefined || ( siteLogoId && ! logoUrl ); + const isLoading = siteLogoId === undefined || isRequestingMediaItem; if ( isLoading ) { logoImage = ; } @@ -366,10 +375,17 @@ export default function LogoEdit( { { controls } { !! logoUrl && logoImage } { ! logoUrl && ! canUserEdit && ( -
- -

{ __( 'Site Logo' ) }

-
+ + { isLoading && ( + + + + ) } + ) } { ! logoUrl && canUserEdit && ( svg { margin-right: $grid-unit-05; } @@ -80,23 +83,3 @@ } } } -.editor-styles-wrapper { - .site-logo_placeholder { - display: flex; - flex-direction: row; - align-items: flex-start; - border-radius: $radius-block-ui; - background-color: $white; - box-shadow: inset 0 0 0 $border-width $gray-900; - padding: $grid-unit-15; - svg { - margin-right: $grid-unit-15; - } - p { - font-family: $default-font; - font-size: $default-font-size; - margin: 0; - line-height: initial; - } - } -} diff --git a/packages/block-library/src/site-logo/index.php b/packages/block-library/src/site-logo/index.php index 44d63505cc4ff..ef24e73e1e218 100644 --- a/packages/block-library/src/site-logo/index.php +++ b/packages/block-library/src/site-logo/index.php @@ -14,7 +14,7 @@ */ function render_block_core_site_logo( $attributes ) { $adjust_width_height_filter = function ( $image ) use ( $attributes ) { - if ( empty( $attributes['width'] ) ) { + if ( empty( $attributes['width'] ) || empty( $image ) ) { return $image; } $height = (float) $attributes['width'] / ( (float) $image[1] / (float) $image[2] ); @@ -111,54 +111,52 @@ function _override_custom_logo_theme_mod( $custom_logo ) { /** * Updates the site_logo option when the custom_logo theme-mod gets updated. * - * This function is hooked on "update_option_theme_mods_$theme" and not - * "pre_set_theme_mod_custom_logo" because by hooking in `update_option` - * the function accounts for remove_theme_mod() as well. - * - * @param mixed $old_value The old option value. - * @param mixed $value The new option value. + * @param mixed $value Attachment ID of the custom logo or an empty value. + * @return mixed */ -function _sync_custom_logo_to_site_logo( $old_value, $value ) { - // Delete the option when the custom logo does not exist or was removed. - // This step ensures the option stays in sync. - if ( empty( $value['custom_logo'] ) ) { +function _sync_custom_logo_to_site_logo( $value ) { + if ( empty( $value ) ) { delete_option( 'site_logo' ); } else { - remove_action( 'update_option_site_logo', '_sync_site_logo_to_custom_logo' ); - update_option( 'site_logo', $value['custom_logo'] ); - add_action( 'update_option_site_logo', '_sync_site_logo_to_custom_logo', 10, 2 ); + update_option( 'site_logo', $value ); } + + return $value; } +add_filter( 'pre_set_theme_mod_custom_logo', '_sync_custom_logo_to_site_logo' ); + /** - * Hooks `_sync_custom_logo_to_site_logo` in `update_option_theme_mods_$theme`. + * Deletes the site_logo when the custom_logo theme mod is removed. * - * Runs on `setup_theme` to account for dynamically-switched themes in the Customizer. + * @param array $old_value Previous theme mod settings. + * @param array $value Updated theme mod settings. */ -function _sync_custom_logo_to_site_logo_on_setup_theme() { - $theme = get_option( 'stylesheet' ); - add_action( "update_option_theme_mods_$theme", '_sync_custom_logo_to_site_logo', 10, 2 ); +function _delete_site_logo_on_remove_custom_logo( $old_value, $value ) { + // If the custom_logo is being unset, it's being removed from theme mods. + if ( isset( $old_value['custom_logo'] ) && ! isset( $value['custom_logo'] ) ) { + delete_option( 'site_logo' ); + } } -add_action( 'setup_theme', '_sync_custom_logo_to_site_logo_on_setup_theme', 11 ); /** - * Updates the custom_logo theme-mod when the site_logo option gets updated. - * - * @param mixed $old_value The old option value. - * @param mixed $value The new option value. - * - * @return void + * Deletes the site logo when all theme mods are being removed. */ -function _sync_site_logo_to_custom_logo( $old_value, $value ) { - // Delete the option when the custom logo does not exist or was removed. - // This step ensures the option stays in sync. - if ( empty( $value ) ) { - remove_theme_mod( 'custom_logo' ); - } else { - remove_filter( 'pre_set_theme_mod_custom_logo', '_sync_custom_logo_to_site_logo' ); - set_theme_mod( 'custom_logo', $value ); - add_filter( 'pre_set_theme_mod_custom_logo', '_sync_custom_logo_to_site_logo' ); +function _delete_site_logo_on_remove_theme_mods() { + if ( false !== get_theme_support( 'custom-logo' ) ) { + delete_option( 'site_logo' ); } } -add_action( 'update_option_site_logo', '_sync_site_logo_to_custom_logo', 10, 2 ); +/** + * Hooks `_delete_site_logo_on_remove_custom_logo` in `update_option_theme_mods_$theme`. + * Hooks `_delete_site_logo_on_remove_theme_mods` in `delete_option_theme_mods_$theme`. + * + * Runs on `setup_theme` to account for dynamically-switched themes in the Customizer. + */ +function _delete_site_logo_on_remove_custom_logo_on_setup_theme() { + $theme = get_option( 'stylesheet' ); + add_action( "update_option_theme_mods_$theme", '_delete_site_logo_on_remove_custom_logo', 10, 2 ); + add_action( "delete_option_theme_mods_$theme", '_delete_site_logo_on_remove_theme_mods' ); +} +add_action( 'setup_theme', '_delete_site_logo_on_remove_custom_logo_on_setup_theme', 11 ); From bc0c153d4fffcd899c953fab7d6780e8bf94db14 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Thu, 8 Jul 2021 08:42:32 +0800 Subject: [PATCH 03/13] Fix moving inner blocks in the Widgets Customizer (#33243) --- .../use-sidebar-block-editor.js | 91 +------------- .../src/filters/move-to-sidebar.js | 44 +++++-- packages/customize-widgets/src/utils.js | 112 ++++++++++++++++++ .../specs/widgets/customizing-widgets.test.js | 63 ++++++++++ 4 files changed, 217 insertions(+), 93 deletions(-) diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/use-sidebar-block-editor.js b/packages/customize-widgets/src/components/sidebar-block-editor/use-sidebar-block-editor.js index 340aca24cb61d..d91ec2ac092ca 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/use-sidebar-block-editor.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/use-sidebar-block-editor.js @@ -1,100 +1,19 @@ /** * External dependencies */ -import { omit, isEqual } from 'lodash'; +import { isEqual } from 'lodash'; /** * WordPress dependencies */ -import { serialize, parse, createBlock } from '@wordpress/blocks'; import { useState, useEffect, useCallback } from '@wordpress/element'; import isShallowEqual from '@wordpress/is-shallow-equal'; import { getWidgetIdFromBlock, addWidgetIdToBlock } from '@wordpress/widgets'; -function blockToWidget( block, existingWidget = null ) { - let widget; - - const isValidLegacyWidgetBlock = - block.name === 'core/legacy-widget' && - ( block.attributes.id || block.attributes.instance ); - - if ( isValidLegacyWidgetBlock ) { - if ( block.attributes.id ) { - // Widget that does not extend WP_Widget. - widget = { - id: block.attributes.id, - }; - } else { - const { encoded, hash, raw, ...rest } = block.attributes.instance; - - // Widget that extends WP_Widget. - widget = { - idBase: block.attributes.idBase, - instance: { - ...existingWidget?.instance, - // Required only for the customizer. - is_widget_customizer_js_value: true, - encoded_serialized_instance: encoded, - instance_hash_key: hash, - raw_instance: raw, - ...rest, - }, - }; - } - } else { - const instance = { - content: serialize( block ), - }; - widget = { - idBase: 'block', - widgetClass: 'WP_Widget_Block', - instance: { - raw_instance: instance, - }, - }; - } - - return { - ...omit( existingWidget, [ 'form', 'rendered' ] ), - ...widget, - }; -} - -function widgetToBlock( { id, idBase, number, instance } ) { - let block; - - const { - encoded_serialized_instance: encoded, - instance_hash_key: hash, - raw_instance: raw, - ...rest - } = instance; - - if ( idBase === 'block' ) { - const parsedBlocks = parse( raw.content ); - block = parsedBlocks.length - ? parsedBlocks[ 0 ] - : createBlock( 'core/paragraph', {} ); - } else if ( number ) { - // Widget that extends WP_Widget. - block = createBlock( 'core/legacy-widget', { - idBase, - instance: { - encoded, - hash, - raw, - ...rest, - }, - } ); - } else { - // Widget that does not extend WP_Widget. - block = createBlock( 'core/legacy-widget', { - id, - } ); - } - - return addWidgetIdToBlock( block, id ); -} +/** + * Internal dependencies + */ +import { blockToWidget, widgetToBlock } from '../../utils'; function widgetsToBlocks( widgets ) { return widgets.map( ( widget ) => widgetToBlock( widget ) ); diff --git a/packages/customize-widgets/src/filters/move-to-sidebar.js b/packages/customize-widgets/src/filters/move-to-sidebar.js index e91dafe09aa0f..9e7910ce785a6 100644 --- a/packages/customize-widgets/src/filters/move-to-sidebar.js +++ b/packages/customize-widgets/src/filters/move-to-sidebar.js @@ -11,7 +11,7 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { createHigherOrderComponent } from '@wordpress/compose'; -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { addFilter } from '@wordpress/hooks'; import { MoveToWidgetArea, getWidgetIdFromBlock } from '@wordpress/widgets'; @@ -22,14 +22,17 @@ import { useSidebarControls, useActiveSidebarControl, } from '../components/sidebar-controls'; +import { useFocusControl } from '../components/focus-control'; +import { blockToWidget } from '../utils'; const withMoveToSidebarToolbarItem = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const widgetId = getWidgetIdFromBlock( props ); + let widgetId = getWidgetIdFromBlock( props ); const sidebarControls = useSidebarControls(); const activeSidebarControl = useActiveSidebarControl(); const hasMultipleSidebars = sidebarControls?.length > 1; const blockName = props.name; + const clientId = props.clientId; const canInsertBlockInSidebar = useSelect( ( select ) => { // Use an empty string to represent the root block list, which @@ -41,19 +44,46 @@ const withMoveToSidebarToolbarItem = createHigherOrderComponent( }, [ blockName ] ); + const block = useSelect( + ( select ) => select( blockEditorStore ).getBlock( clientId ), + [ clientId ] + ); + const { removeBlock } = useDispatch( blockEditorStore ); + const [ , focusWidget ] = useFocusControl(); function moveToSidebar( sidebarControlId ) { const newSidebarControl = sidebarControls.find( ( sidebarControl ) => sidebarControl.id === sidebarControlId ); - const oldSetting = activeSidebarControl.setting; - const newSetting = newSidebarControl.setting; + if ( widgetId ) { + /** + * If there's a widgetId, move it to the other sidebar. + */ + const oldSetting = activeSidebarControl.setting; + const newSetting = newSidebarControl.setting; + + oldSetting( without( oldSetting(), widgetId ) ); + newSetting( [ ...newSetting(), widgetId ] ); + } else { + /** + * If there isn't a widgetId, it's most likely a inner block. + * First, remove the block in the original sidebar, + * then, create a new widget in the new sidebar and get back its widgetId. + */ + const sidebarAdapter = newSidebarControl.sidebarAdapter; - oldSetting( without( oldSetting(), widgetId ) ); - newSetting( [ ...newSetting(), widgetId ] ); + removeBlock( clientId ); + const addedWidgetIds = sidebarAdapter.setWidgets( [ + ...sidebarAdapter.getWidgets(), + blockToWidget( block ), + ] ); + // The last non-null id is the added widget's id. + widgetId = addedWidgetIds.reverse().find( ( id ) => !! id ); + } - newSidebarControl.expand(); + // Move focus to the moved widget and expand the sidebar. + focusWidget( widgetId ); } return ( diff --git a/packages/customize-widgets/src/utils.js b/packages/customize-widgets/src/utils.js index baa8c70b01ac2..68fc6b2fe2d71 100644 --- a/packages/customize-widgets/src/utils.js +++ b/packages/customize-widgets/src/utils.js @@ -1,4 +1,14 @@ // @ts-check +/** + * WordPress dependencies + */ +import { serialize, parse, createBlock } from '@wordpress/blocks'; +import { addWidgetIdToBlock } from '@wordpress/widgets'; + +/** + * External dependencies + */ +import { omit } from 'lodash'; /** * Convert settingId to widgetId. @@ -18,3 +28,105 @@ export function settingIdToWidgetId( settingId ) { return settingId; } + +/** + * Transform a block to a customizable widget. + * + * @param {WPBlock} block The block to be transformed from. + * @param {Object} existingWidget The widget to be extended from. + * @return {Object} The transformed widget. + */ +export function blockToWidget( block, existingWidget = null ) { + let widget; + + const isValidLegacyWidgetBlock = + block.name === 'core/legacy-widget' && + ( block.attributes.id || block.attributes.instance ); + + if ( isValidLegacyWidgetBlock ) { + if ( block.attributes.id ) { + // Widget that does not extend WP_Widget. + widget = { + id: block.attributes.id, + }; + } else { + const { encoded, hash, raw, ...rest } = block.attributes.instance; + + // Widget that extends WP_Widget. + widget = { + idBase: block.attributes.idBase, + instance: { + ...existingWidget?.instance, + // Required only for the customizer. + is_widget_customizer_js_value: true, + encoded_serialized_instance: encoded, + instance_hash_key: hash, + raw_instance: raw, + ...rest, + }, + }; + } + } else { + const instance = { + content: serialize( block ), + }; + widget = { + idBase: 'block', + widgetClass: 'WP_Widget_Block', + instance: { + raw_instance: instance, + }, + }; + } + + return { + ...omit( existingWidget, [ 'form', 'rendered' ] ), + ...widget, + }; +} + +/** + * Transform a widget to a block. + * + * @param {Object} widget The widget to be transformed from. + * @param {string} widget.id The widget id. + * @param {string} widget.idBase The id base of the widget. + * @param {number} widget.number The number/index of the widget. + * @param {Object} widget.instance The instance of the widget. + * @return {WPBlock} The transformed block. + */ +export function widgetToBlock( { id, idBase, number, instance } ) { + let block; + + const { + encoded_serialized_instance: encoded, + instance_hash_key: hash, + raw_instance: raw, + ...rest + } = instance; + + if ( idBase === 'block' ) { + const parsedBlocks = parse( raw.content ); + block = parsedBlocks.length + ? parsedBlocks[ 0 ] + : createBlock( 'core/paragraph', {} ); + } else if ( number ) { + // Widget that extends WP_Widget. + block = createBlock( 'core/legacy-widget', { + idBase, + instance: { + encoded, + hash, + raw, + ...rest, + }, + } ); + } else { + // Widget that does not extend WP_Widget. + block = createBlock( 'core/legacy-widget', { + id, + } ); + } + + return addWidgetIdToBlock( block, id ); +} diff --git a/packages/e2e-tests/specs/widgets/customizing-widgets.test.js b/packages/e2e-tests/specs/widgets/customizing-widgets.test.js index 701ece8f38220..960d93ce1b299 100644 --- a/packages/e2e-tests/specs/widgets/customizing-widgets.test.js +++ b/packages/e2e-tests/specs/widgets/customizing-widgets.test.js @@ -700,6 +700,69 @@ describe( 'Widgets Customizer', () => { "The page delivered both an 'X-Frame-Options' header and a 'Content-Security-Policy' header with a 'frame-ancestors' directive. Although the 'X-Frame-Options' header alone would have blocked embedding, it has been ignored." ); } ); + + it( 'should move (inner) blocks to another sidebar', async () => { + const widgetsPanel = await find( { + role: 'heading', + name: /Widgets/, + level: 3, + } ); + await widgetsPanel.click(); + + const footer1Section = await find( { + role: 'heading', + name: /Footer #1/, + level: 3, + } ); + await footer1Section.click(); + + await addBlock( 'Paragraph' ); + await page.keyboard.type( 'First Paragraph' ); + + await showBlockToolbar(); + await clickBlockToolbarButton( 'Options' ); + const groupButton = await find( { + role: 'menuitem', + name: 'Group', + } ); + await groupButton.click(); + + // Refocus the paragraph block. + const paragraphBlock = await find( { + role: 'group', + name: 'Paragraph block', + value: 'First Paragraph', + } ); + await paragraphBlock.focus(); + await showBlockToolbar(); + await clickBlockToolbarButton( 'Move to widget area' ); + + const footer2Option = await find( { + role: 'menuitemradio', + name: 'Footer #2', + } ); + await footer2Option.click(); + + // Should switch to and expand Footer #2. + await expect( { + role: 'heading', + name: 'Customizing ▸ Widgets Footer #2', + } ).toBeFound(); + + // The paragraph block should be moved to the new sidebar and have focus. + const movedParagraphBlockQuery = { + role: 'group', + name: 'Paragraph block', + value: 'First Paragraph', + }; + await expect( movedParagraphBlockQuery ).toBeFound(); + const movedParagraphBlock = await find( movedParagraphBlockQuery ); + await expect( movedParagraphBlock ).toHaveFocus(); + + expect( console ).toHaveWarned( + "The page delivered both an 'X-Frame-Options' header and a 'Content-Security-Policy' header with a 'frame-ancestors' directive. Although the 'X-Frame-Options' header alone would have blocked embedding, it has been ignored." + ); + } ); } ); /** From d6cbacad1a17dd5e9d052ba4cd7c54ae8831bb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Thu, 8 Jul 2021 15:17:34 +0200 Subject: [PATCH 04/13] Allow themes to provide empty values for `color.duotone` and `spacing.units` and remove `layout.units` (#33280) --- lib/class-wp-theme-json-gutenberg.php | 4 +- packages/block-editor/src/hooks/layout.js | 2 +- packages/block-library/src/column/edit.js | 2 +- .../block-library/src/column/edit.native.js | 2 +- .../block-library/src/columns/edit.native.js | 2 +- phpunit/class-wp-theme-json-test.php | 197 ++++++++++++++++++ 6 files changed, 203 insertions(+), 6 deletions(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index c7a38362c964f..19785910251b2 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1172,8 +1172,8 @@ public function merge( $incoming ) { foreach ( $nodes as $metadata ) { foreach ( $to_replace as $property_path ) { $path = array_merge( $metadata['path'], $property_path ); - $node = _wp_array_get( $incoming_data, $path, array() ); - if ( ! empty( $node ) ) { + $node = _wp_array_get( $incoming_data, $path, null ); + if ( isset( $node ) ) { gutenberg_experimental_set( $this->theme_json, $path, $node ); } } diff --git a/packages/block-editor/src/hooks/layout.js b/packages/block-editor/src/hooks/layout.js index 14a540cf3284f..95f9bdd77dede 100644 --- a/packages/block-editor/src/hooks/layout.js +++ b/packages/block-editor/src/hooks/layout.js @@ -39,7 +39,7 @@ function LayoutPanel( { setAttributes, attributes } ) { }, [] ); const units = useCustomUnits( { - availableUnits: useSetting( 'layout.units' ) || [ + availableUnits: useSetting( 'spacing.units' ) || [ '%', 'px', 'em', diff --git a/packages/block-library/src/column/edit.js b/packages/block-library/src/column/edit.js index ae8d3083e2f01..ebd91ee0b4925 100644 --- a/packages/block-library/src/column/edit.js +++ b/packages/block-library/src/column/edit.js @@ -34,7 +34,7 @@ function ColumnEdit( { } ); const units = useCustomUnits( { - availableUnits: useSetting( 'layout.units' ) || [ + availableUnits: useSetting( 'spacing.units' ) || [ '%', 'px', 'em', diff --git a/packages/block-library/src/column/edit.native.js b/packages/block-library/src/column/edit.native.js index e30db2f4b0fd8..71bab15b4f924 100644 --- a/packages/block-library/src/column/edit.native.js +++ b/packages/block-library/src/column/edit.native.js @@ -65,7 +65,7 @@ function ColumnEdit( { const [ widthUnit, setWidthUnit ] = useState( valueUnit || '%' ); const units = useCustomUnits( { - availableUnits: useSetting( 'layout.units' ) || [ + availableUnits: useSetting( 'spacing.units' ) || [ '%', 'px', 'em', diff --git a/packages/block-library/src/columns/edit.native.js b/packages/block-library/src/columns/edit.native.js index e72fdabd88b57..0905167504e90 100644 --- a/packages/block-library/src/columns/edit.native.js +++ b/packages/block-library/src/columns/edit.native.js @@ -107,7 +107,7 @@ function ColumnsEditContainer( { const { width } = sizes || {}; const units = useCustomUnits( { - availableUnits: useSetting( 'layout.units' ) || [ + availableUnits: useSetting( 'spacing.units' ) || [ '%', 'px', 'em', diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 5b9cc20c3233d..3df33165d6211 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -649,6 +649,203 @@ public function test_merge_incoming_data() { $this->assertEqualSetsWithIndex( $expected, $actual ); } + public function test_merge_incoming_data_empty_presets() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'duotone' => array( + array( + 'slug' => 'value', + 'colors' => array( 'red', 'green' ), + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em' ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'size', + 'value' => 'size', + ), + ), + ), + ), + ) + ); + + $theme_json->merge( + new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'duotone' => array(), + 'gradients' => array(), + 'palette' => array(), + ), + 'spacing' => array( + 'units' => array(), + ), + 'typography' => array( + 'fontSizes' => array(), + ), + ), + ) + ) + ); + + $actual = $theme_json->get_raw_data(); + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'duotone' => array(), + 'gradients' => array( + 'theme' => array(), + ), + 'palette' => array( + 'theme' => array(), + ), + ), + 'spacing' => array( + 'units' => array(), + ), + 'typography' => array( + 'fontSizes' => array( + 'theme' => array(), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + + public function test_merge_incoming_data_null_presets() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'duotone' => array( + array( + 'slug' => 'value', + 'colors' => array( 'red', 'green' ), + ), + ), + 'gradients' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + 'palette' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + ), + ), + 'spacing' => array( + 'units' => array( 'px', 'em' ), + ), + 'typography' => array( + 'fontSizes' => array( + array( + 'slug' => 'size', + 'value' => 'size', + ), + ), + ), + ), + ) + ); + + $theme_json->merge( + new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + ), + 'spacing' => array( + 'customMargin' => false, + ), + 'typography' => array( + 'customLineHeight' => false, + ), + ), + ) + ) + ); + + $actual = $theme_json->get_raw_data(); + $expected = array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'settings' => array( + 'color' => array( + 'custom' => false, + 'duotone' => array( + array( + 'slug' => 'value', + 'colors' => array( 'red', 'green' ), + ), + ), + 'gradients' => array( + 'theme' => array( + array( + 'slug' => 'gradient', + 'gradient' => 'gradient', + ), + ), + ), + 'palette' => array( + 'theme' => array( + array( + 'slug' => 'red', + 'color' => 'red', + ), + ), + ), + ), + 'spacing' => array( + 'customMargin' => false, + 'units' => array( 'px', 'em' ), + ), + 'typography' => array( + 'customLineHeight' => false, + 'fontSizes' => array( + 'theme' => array( + array( + 'slug' => 'size', + 'value' => 'size', + ), + ), + ), + ), + ), + ); + + $this->assertEqualSetsWithIndex( $expected, $actual ); + } + function test_remove_insecure_properties_removes_unsafe_styles() { $actual = WP_Theme_JSON_Gutenberg::remove_insecure_properties( array( From c0616d7763b1177642a8b521ecc60b1a2ebef798 Mon Sep 17 00:00:00 2001 From: Dominik Schilling Date: Mon, 12 Jul 2021 10:00:35 +0200 Subject: [PATCH 05/13] Update getTermsInfo() to workaround parsing issue for translatable strings (#33341) --- packages/block-library/src/query/utils.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/query/utils.js b/packages/block-library/src/query/utils.js index 5ac6a4dcd6684..f002a8e74fa04 100644 --- a/packages/block-library/src/query/utils.js +++ b/packages/block-library/src/query/utils.js @@ -39,9 +39,8 @@ import { store as coreStore } from '@wordpress/core-data'; * @param {WPTerm[]} terms The terms to extract of helper object. * @return {QueryTermsInfo} The object with the terms information. */ -export const getTermsInfo = ( terms ) => ( { - terms, - ...terms?.reduce( +export const getTermsInfo = ( terms ) => { + const mapping = terms?.reduce( ( accumulator, term ) => { const { mapById, mapByName, names } = accumulator; mapById[ term.id ] = term; @@ -50,8 +49,13 @@ export const getTermsInfo = ( terms ) => ( { return accumulator; }, { mapById: {}, mapByName: {}, names: [] } - ), -} ); + ); + + return { + terms, + ...mapping, + }; +}; /** * Returns a helper object that contains: From e83dcf089eb878610147e332eceaec3b4a7ed791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 12 Jul 2021 10:56:37 +0200 Subject: [PATCH 06/13] Specify what settings can be part of `settings.layout` (#33303) --- lib/class-wp-theme-json-gutenberg.php | 5 ++++- phpunit/class-wp-theme-json-test.php | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 19785910251b2..feb39b5c1cb79 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -101,7 +101,10 @@ class WP_Theme_JSON_Gutenberg { 'palette' => null, ), 'custom' => null, - 'layout' => null, + 'layout' => array( + 'contentSize' => null, + 'wideSize' => null, + ), 'spacing' => array( 'customMargin' => null, 'customPadding' => null, diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 3df33165d6211..7498e29844349 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -16,6 +16,10 @@ function test_get_settings() { 'color' => array( 'custom' => false, ), + 'layout' => array( + 'contentSize' => 'value', + 'invalid/key' => 'value', + ), 'invalid/key' => 'value', 'blocks' => array( 'core/group' => array( @@ -44,6 +48,9 @@ function test_get_settings() { 'color' => array( 'custom' => false, ), + 'layout' => array( + 'contentSize' => 'value', + ), 'blocks' => array( 'core/group' => array( 'color' => array( From cc758d578b6a51b6398f6ed3257773575289d726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Mon, 12 Jul 2021 11:02:48 +0200 Subject: [PATCH 07/13] Update conditions to hide duotone panel (#33295) --- docs/how-to-guides/themes/theme-json.md | 1 + lib/class-wp-theme-json-gutenberg.php | 1 + lib/experimental-default-theme.json | 5 +++-- .../duotone-control/duotone-picker-popover.js | 2 ++ .../src/components/duotone-control/index.js | 6 ++---- packages/block-editor/src/hooks/duotone.js | 14 ++++++++++++-- .../src/duotone-picker/duotone-picker.js | 6 ++++-- 7 files changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/how-to-guides/themes/theme-json.md b/docs/how-to-guides/themes/theme-json.md index 4a217a3c4d3ca..343f712cf372a 100644 --- a/docs/how-to-guides/themes/theme-json.md +++ b/docs/how-to-guides/themes/theme-json.md @@ -170,6 +170,7 @@ The settings section has the following structure: }, "color": { "custom": true, + "customDuotone": true, "customGradient": true, "duotone": [], "gradients": [], diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index feb39b5c1cb79..292f50f514596 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -94,6 +94,7 @@ class WP_Theme_JSON_Gutenberg { ), 'color' => array( 'custom' => null, + 'customDuotone' => null, 'customGradient' => null, 'duotone' => null, 'gradients' => null, diff --git a/lib/experimental-default-theme.json b/lib/experimental-default-theme.json index d92547a146895..72ca42dbaa968 100644 --- a/lib/experimental-default-theme.json +++ b/lib/experimental-default-theme.json @@ -169,8 +169,9 @@ } ], "custom": true, - "link": false, - "customGradient": true + "customDuotone": true, + "customGradient": true, + "link": false }, "typography": { "dropCap": true, diff --git a/packages/block-editor/src/components/duotone-control/duotone-picker-popover.js b/packages/block-editor/src/components/duotone-control/duotone-picker-popover.js index c277b07ad1487..64f4c1cf225c2 100644 --- a/packages/block-editor/src/components/duotone-control/duotone-picker-popover.js +++ b/packages/block-editor/src/components/duotone-control/duotone-picker-popover.js @@ -11,6 +11,7 @@ function DuotonePickerPopover( { duotonePalette, colorPalette, disableCustomColors, + disableCustomDuotone, } ) { return ( diff --git a/packages/block-editor/src/components/duotone-control/index.js b/packages/block-editor/src/components/duotone-control/index.js index eb675639a5f14..757e83803e86b 100644 --- a/packages/block-editor/src/components/duotone-control/index.js +++ b/packages/block-editor/src/components/duotone-control/index.js @@ -15,15 +15,12 @@ function DuotoneControl( { colorPalette, duotonePalette, disableCustomColors, + disableCustomDuotone, value, onChange, } ) { const [ isOpen, setIsOpen ] = useState( false ); - if ( ! duotonePalette ) { - return null; - } - const onToggle = () => { setIsOpen( ( prev ) => ! prev ); }; @@ -55,6 +52,7 @@ function DuotoneControl( { duotonePalette={ duotonePalette } colorPalette={ colorPalette } disableCustomColors={ disableCustomColors } + disableCustomDuotone={ disableCustomDuotone } /> ) } diff --git a/packages/block-editor/src/hooks/duotone.js b/packages/block-editor/src/hooks/duotone.js index c4845b34b49f8..3cb05246306b5 100644 --- a/packages/block-editor/src/hooks/duotone.js +++ b/packages/block-editor/src/hooks/duotone.js @@ -21,6 +21,8 @@ import { useSetting, } from '../components'; +const EMPTY_ARRAY = []; + /** * Convert a list of colors to an object of R, G, and B values. * @@ -123,15 +125,23 @@ function DuotonePanel( { attributes, setAttributes } ) { const style = attributes?.style; const duotone = style?.color?.duotone; - const duotonePalette = useSetting( 'color.duotone' ); - const colorPalette = useSetting( 'color.palette' ); + const duotonePalette = useSetting( 'color.duotone' ) || EMPTY_ARRAY; + const colorPalette = useSetting( 'color.palette' ) || EMPTY_ARRAY; const disableCustomColors = ! useSetting( 'color.custom' ); + const disableCustomDuotone = + ! useSetting( 'color.customDuotone' ) || + ( colorPalette?.length === 0 && disableCustomColors ); + + if ( duotonePalette?.length === 0 && disableCustomDuotone ) { + return null; + } return ( { diff --git a/packages/components/src/duotone-picker/duotone-picker.js b/packages/components/src/duotone-picker/duotone-picker.js index bbc546acec1c4..fa01fd8270767 100644 --- a/packages/components/src/duotone-picker/duotone-picker.js +++ b/packages/components/src/duotone-picker/duotone-picker.js @@ -22,6 +22,7 @@ function DuotonePicker( { colorPalette, duotonePalette, disableCustomColors, + disableCustomDuotone, value, onChange, } ) { @@ -29,6 +30,7 @@ function DuotonePicker( { () => getDefaultColors( colorPalette ), [ colorPalette ] ); + return ( { @@ -74,10 +76,10 @@ function DuotonePicker( { } > - { ! disableCustomColors && ( + { ! disableCustomColors && ! disableCustomDuotone && ( ) } - { colorPalette && ( + { ! disableCustomDuotone && ( Date: Mon, 12 Jul 2021 10:13:44 +0100 Subject: [PATCH 08/13] Skipping more e2e tests (#33353) --- .../e2e-tests/specs/editor/various/inserting-blocks.test.js | 2 +- .../specs/experiments/post-editor-template-mode.test.js | 2 +- packages/e2e-tests/specs/widgets/editing-widgets.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js b/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js index cda28f6a511f9..9e319c5a876a3 100644 --- a/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/inserting-blocks.test.js @@ -57,7 +57,7 @@ describe( 'Inserting blocks', () => { return page.mouse.click( x, y ); } - it( 'Should insert content using the placeholder and the regular inserter', async () => { + it.skip( 'Should insert content using the placeholder and the regular inserter', async () => { // This ensures the editor is loaded in navigation mode. await page.reload(); await page.waitForSelector( '.edit-post-layout' ); diff --git a/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js b/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js index 5e8d80b1f45c5..cf229daa06a40 100644 --- a/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js +++ b/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js @@ -145,7 +145,7 @@ describe( 'Post Editor Template mode', () => { ); } ); - it( 'Allow creating custom block templates in classic themes', async () => { + it.skip( 'Allow creating custom block templates in classic themes', async () => { await activateTheme( 'twentytwentyone' ); await createNewPost(); // Create a random post. diff --git a/packages/e2e-tests/specs/widgets/editing-widgets.test.js b/packages/e2e-tests/specs/widgets/editing-widgets.test.js index 642af7ee8171b..2395f444a1637 100644 --- a/packages/e2e-tests/specs/widgets/editing-widgets.test.js +++ b/packages/e2e-tests/specs/widgets/editing-widgets.test.js @@ -252,7 +252,7 @@ describe( 'Widgets screen', () => { ` ); } ); - it( 'Should insert content using the inline inserter', async () => { + it.skip( 'Should insert content using the inline inserter', async () => { const [ firstWidgetArea ] = await findAll( { role: 'group', name: 'Block: Widget Area', From 0365552dec4a7c6075b1b37c7ed1464bd4329866 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 12 Jul 2021 09:00:20 +0100 Subject: [PATCH 09/13] Skip unstable e2e tests (#33352) --- .../e2e-tests/specs/experiments/multi-entity-saving.test.js | 2 +- packages/e2e-tests/specs/widgets/customizing-widgets.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js index 357b0967197c9..81c4ca42a7cf9 100644 --- a/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js +++ b/packages/e2e-tests/specs/experiments/multi-entity-saving.test.js @@ -82,7 +82,7 @@ describe( 'Multi-entity save flow', () => { expect( multiSaveButton ).toBeNull(); }; - it( 'Save flow should work as expected.', async () => { + it.skip( 'Save flow should work as expected.', async () => { await createNewPost(); // Edit the page some. await page.click( '.editor-post-title' ); diff --git a/packages/e2e-tests/specs/widgets/customizing-widgets.test.js b/packages/e2e-tests/specs/widgets/customizing-widgets.test.js index 960d93ce1b299..e61709adf0e83 100644 --- a/packages/e2e-tests/specs/widgets/customizing-widgets.test.js +++ b/packages/e2e-tests/specs/widgets/customizing-widgets.test.js @@ -150,7 +150,7 @@ describe( 'Widgets Customizer', () => { ); } ); - it( 'should open the inspector panel', async () => { + it.skip( 'should open the inspector panel', async () => { const widgetsPanel = await find( { role: 'heading', name: /Widgets/, From bb010c16bc794d90e69430e88f4255351693c94e Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 12 Jul 2021 10:38:40 +0100 Subject: [PATCH 10/13] Prevent entering invalid values in the Query Loop block config (#33285) --- .../src/query/edit/query-toolbar.js | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js index db4de14f297b1..92db79c7dad7b 100644 --- a/packages/block-library/src/query/edit/query-toolbar.js +++ b/packages/block-library/src/query/edit/query-toolbar.js @@ -61,11 +61,18 @@ export default function QueryToolbar( { labelPosition="edge" min={ 1 } max={ 100 } - onChange={ ( value ) => + onChange={ ( value ) => { + if ( + isNaN( value ) || + value < 1 || + value > 100 + ) { + return; + } setQuery( { - perPage: +value ?? -1, - } ) - } + perPage: value, + } ); + } } step="1" value={ query.perPage } isDragEnabled={ false } @@ -78,9 +85,16 @@ export default function QueryToolbar( { labelPosition="edge" min={ 0 } max={ 100 } - onChange={ ( value ) => - setQuery( { offset: +value } ) - } + onChange={ ( value ) => { + if ( + isNaN( value ) || + value < 0 || + value > 100 + ) { + return; + } + setQuery( { offset: value } ); + } } step="1" value={ query.offset } isDragEnabled={ false } @@ -98,9 +112,12 @@ export default function QueryToolbar( { label={ __( 'Max page to show' ) } labelPosition="edge" min={ 0 } - onChange={ ( value ) => - setQuery( { pages: +value } ) - } + onChange={ ( value ) => { + if ( isNaN( value ) || value < 0 ) { + return; + } + setQuery( { pages: value } ); + } } step="1" value={ query.pages } isDragEnabled={ false } From 80d8525c61e3a1efb80b566ef3506887028f9231 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Fri, 9 Jul 2021 14:37:09 +0200 Subject: [PATCH 11/13] Merge conflicting `wp.editor` objects into a single, non-conflicting module (#33228) --- lib/client-assets.php | 14 ++++++++++++++ .../editor/plugins/wp-editor-meta-box.test.js | 18 +++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 04034bd9ff9c0..03fa3a1efd606 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -102,6 +102,20 @@ function gutenberg_override_script( $scripts, $handle, $src, $deps = array(), $v $output = sprintf( "wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ '%s' ] }, 'default' );", $ltr ); $scripts->add_inline_script( 'wp-i18n', $output, 'after' ); } + + /* + * Wp-editor module is exposed as window.wp.editor. + * Problem: there is quite some code expecting window.wp.oldEditor object available under window.wp.editor. + * Solution: fuse the two objects together to maintain backward compatibility. + * For more context, see https://github.com/WordPress/gutenberg/issues/33203 + */ + if ( 'wp-editor' === $handle ) { + $scripts->add_inline_script( + 'wp-editor', + 'Object.assign( window.wp.editor, window.wp.oldEditor );', + 'after' + ); + } } /** diff --git a/packages/e2e-tests/specs/editor/plugins/wp-editor-meta-box.test.js b/packages/e2e-tests/specs/editor/plugins/wp-editor-meta-box.test.js index 3045575ff15f5..867c3cf7b4013 100644 --- a/packages/e2e-tests/specs/editor/plugins/wp-editor-meta-box.test.js +++ b/packages/e2e-tests/specs/editor/plugins/wp-editor-meta-box.test.js @@ -39,8 +39,20 @@ describe( 'WP Editor Meta Boxes', () => { ( textarea ) => textarea.value ); - expect( content ).toMatchInlineSnapshot( - `"

Typing in a metabox

"` - ); + /* + * `content` may or may not contain the

tags depending on hasWpautop value in this line: + * https://github.com/WordPress/wordpress-develop/blob/2382765afa36e10bf3c74420024ad4e85763a47c/src/js/_enqueues/vendor/tinymce/plugins/wordpress/plugin.js#L15 + * + * Now, for the purposes of this e2e test we explicitly set wpautop to true in the test plugin: + * https://github.com/WordPress/gutenberg/blob/3da717b8d0ac7d7821fc6d0475695ccf3ae2829f/packages/e2e-tests/plugins/wp-editor-metabox.php#L36 + * + * If this test randomly fails because of the actual value being wrapped in

like

Typing in a metabox

, it means that + * hasWpautop has been errorneously set to false in the line above. You may want to check: + * * Is window.wp.editor.autop a function? It should be one since https://github.com/WordPress/gutenberg/pull/33228 + * * Is wpautop still true in the second link mentioned in this comment? + * + * For more context, see https://github.com/WordPress/gutenberg/pull/33228/files#r666897885 + */ + expect( content ).toBe( 'Typing in a metabox' ); } ); } ); From 0d2fa4171203687c2c592ffd4a2ce004d48c935a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= Date: Tue, 13 Jul 2021 12:51:42 +0200 Subject: [PATCH 12/13] Do not show color panel if no colors within (#33369) --- packages/block-editor/src/hooks/color.js | 54 +++++++++++++++++------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/hooks/color.js b/packages/block-editor/src/hooks/color.js index f8159240a20b8..2903e1912ff20 100644 --- a/packages/block-editor/src/hooks/color.js +++ b/packages/block-editor/src/hooks/color.js @@ -218,9 +218,11 @@ function immutableSet( object, path, value ) { */ export function ColorEdit( props ) { const { name: blockName, attributes } = props; - const isLinkColorEnabled = useSetting( 'color.link' ); - const colors = useSetting( 'color.palette' ) || EMPTY_ARRAY; + const solids = useSetting( 'color.palette' ) || EMPTY_ARRAY; const gradients = useSetting( 'color.gradients' ) || EMPTY_ARRAY; + const areCustomSolidsEnabled = useSetting( 'color.custom' ); + const areCustomGradientsEnabled = useSetting( 'color.customGradient' ); + const isLinkEnabled = useSetting( 'color.link' ); // Shouldn't be needed but right now the ColorGradientsPanel // can trigger both onChangeColor and onChangeBackground @@ -235,19 +237,39 @@ export function ColorEdit( props ) { return null; } - const hasBackground = hasBackgroundColorSupport( blockName ); - const hasGradient = hasGradientSupport( blockName ); + const hasLinkColor = + hasLinkColorSupport( blockName ) && + isLinkEnabled && + ( solids.length > 0 || areCustomSolidsEnabled ); + const hasTextColor = + hasTextColorSupport( blockName ) && + ( solids.length > 0 || areCustomSolidsEnabled ); + const hasBackgroundColor = + hasBackgroundColorSupport( blockName ) && + ( solids.length > 0 || areCustomSolidsEnabled ); + const hasGradientColor = + hasGradientSupport( blockName ) && + ( gradients.length > 0 || areCustomGradientsEnabled ); + + if ( + ! hasLinkColor && + ! hasTextColor && + ! hasBackgroundColor && + ! hasGradientColor + ) { + return null; + } const { style, textColor, backgroundColor, gradient } = attributes; let gradientValue; - if ( hasGradient && gradient ) { + if ( hasGradientColor && gradient ) { gradientValue = getGradientValueBySlug( gradients, gradient ); - } else if ( hasGradient ) { + } else if ( hasGradientColor ) { gradientValue = style?.color?.gradient; } const onChangeColor = ( name ) => ( value ) => { - const colorObject = getColorObjectByColorValue( colors, value ); + const colorObject = getColorObjectByColorValue( solids, value ); const attributeName = name + 'Color'; const newStyle = { ...localAttributes.current.style, @@ -306,7 +328,7 @@ export function ColorEdit( props ) { }; const onChangeLinkColor = ( value ) => { - const colorObject = getColorObjectByColorValue( colors, value ); + const colorObject = getColorObjectByColorValue( solids, value ); const newLinkColorValue = colorObject?.slug ? `var:preset|color|${ colorObject.slug }` : value; @@ -326,45 +348,45 @@ export function ColorEdit( props ) { } clientId={ props.clientId } settings={ [ - ...( hasTextColorSupport( blockName ) + ...( hasTextColor ? [ { label: __( 'Text color' ), onColorChange: onChangeColor( 'text' ), colorValue: getColorObjectByAttributeValues( - colors, + solids, textColor, style?.color?.text ).color, }, ] : [] ), - ...( hasBackground || hasGradient + ...( hasBackgroundColor || hasGradientColor ? [ { label: __( 'Background color' ), - onColorChange: hasBackground + onColorChange: hasBackgroundColor ? onChangeColor( 'background' ) : undefined, colorValue: getColorObjectByAttributeValues( - colors, + solids, backgroundColor, style?.color?.background ).color, gradientValue, - onGradientChange: hasGradient + onGradientChange: hasGradientColor ? onChangeGradient : undefined, }, ] : [] ), - ...( isLinkColorEnabled && hasLinkColorSupport( blockName ) + ...( hasLinkColor ? [ { label: __( 'Link Color' ), onColorChange: onChangeLinkColor, colorValue: getLinkColorFromAttributeValue( - colors, + solids, style?.elements?.link?.color?.text ), clearable: !! style?.elements?.link?.color From 52019e27d51c3ce42553d468891b2ec4d6d01d45 Mon Sep 17 00:00:00 2001 From: Jon Desrosiers Date: Tue, 13 Jul 2021 11:21:56 -0400 Subject: [PATCH 13/13] Avoid calling `gutenberg_` functions within code shipped through WordPress Core (#33331) --- lib/compat/wordpress-5.8/index.php | 61 +++++++++++++++++++ .../block-library/src/post-template/index.php | 60 ------------------ .../src/blocks/legacy-widget/index.php | 7 +++ 3 files changed, 68 insertions(+), 60 deletions(-) diff --git a/lib/compat/wordpress-5.8/index.php b/lib/compat/wordpress-5.8/index.php index 9427ce463500a..d1dc42880061f 100644 --- a/lib/compat/wordpress-5.8/index.php +++ b/lib/compat/wordpress-5.8/index.php @@ -103,3 +103,64 @@ function build_query_vars_from_query_block( $block, $page ) { return $query; } } + +if ( ! function_exists( 'gutenberg_register_legacy_query_loop_block' ) ) { + /** + * Renders the legacy `core/query-loop` block on the server. + * It triggers a developer warning and then calls the renamed + * block's `render_callback` function output. + * + * @param array $attributes Block attributes. + * @param string $content Block default content. + * @param WP_Block $block Block instance. + * + * @return string Returns the output of the query, structured using the layout defined by the block's inner blocks. + */ + function gutenberg_render_legacy_query_loop_block( $attributes, $content, $block ) { + trigger_error( + /* translators: %1$s: Block type */ + sprintf( __( 'Block %1$s has been renamed to Post Template. %1$s will be supported until WordPress version 5.9.', 'gutenberg' ), $block->name ), + headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE + ); + return render_block_core_post_template( $attributes, $content, $block ); + } +} + +if ( ! function_exists( 'gutenberg_register_legacy_query_loop_block' ) ) { + /** + * Complements the renaming of `Query Loop` to `Post Template`. + * This ensures backwards compatibility for any users running the Gutenberg + * plugin who have used Query Loop prior to its renaming. + * + * @see https://github.com/WordPress/gutenberg/pull/32514 + */ + function gutenberg_register_legacy_query_loop_block() { + $registry = WP_Block_Type_Registry::get_instance(); + if ( $registry->is_registered( 'core/query-loop' ) ) { + unregister_block_type( 'core/query-loop' ); + } + register_block_type( + 'core/query-loop', + array( + 'category' => 'design', + 'uses_context' => array( + 'queryId', + 'query', + 'queryContext', + 'displayLayout', + 'templateSlug', + ), + 'supports' => array( + 'reusable' => false, + 'html' => false, + 'align' => true, + ), + 'style' => 'wp-block-post-template', + 'render_callback' => 'gutenberg_render_legacy_query_loop_block', + 'skip_inner_blocks' => true, + ) + ); + } + + add_action( 'init', 'gutenberg_register_legacy_query_loop_block' ); +} diff --git a/packages/block-library/src/post-template/index.php b/packages/block-library/src/post-template/index.php index dd69fda45fe7f..3daacbad7f649 100644 --- a/packages/block-library/src/post-template/index.php +++ b/packages/block-library/src/post-template/index.php @@ -86,63 +86,3 @@ function register_block_core_post_template() { ); } add_action( 'init', 'register_block_core_post_template' ); - -/** - * Renders the legacy `core/query-loop` block on the server. - * It triggers a developer warning and then calls the renamed - * block's `render_callback` function output. - * - * This can be removed when WordPress 5.9 is released. - * - * @param array $attributes Block attributes. - * @param string $content Block default content. - * @param WP_Block $block Block instance. - * - * @return string Returns the output of the query, structured using the layout defined by the block's inner blocks. - */ -function render_legacy_query_loop_block( $attributes, $content, $block ) { - trigger_error( - /* translators: %1$s: Block type */ - sprintf( __( 'Block %1$s has been renamed to Post Template. %1$s will be supported until WordPress version 5.9.' ), $block->name ), - headers_sent() || WP_DEBUG ? E_USER_WARNING : E_USER_NOTICE - ); - return render_block_core_post_template( $attributes, $content, $block ); -} - -/** - * Complements the renaming of `Query Loop` to `Post Template`. - * This ensures backwards compatibility for any users running the Gutenberg - * plugin who have used Query Loop prior to its renaming. - * - * This can be removed when WordPress 5.9 is released. - * - * @see https://github.com/WordPress/gutenberg/pull/32514 - */ -function gutenberg_register_legacy_query_loop_block() { - $registry = WP_Block_Type_Registry::get_instance(); - if ( $registry->is_registered( 'core/query-loop' ) ) { - unregister_block_type( 'core/query-loop' ); - } - register_block_type( - 'core/query-loop', - array( - 'category' => 'design', - 'uses_context' => array( - 'queryId', - 'query', - 'queryContext', - 'displayLayout', - 'templateSlug', - ), - 'supports' => array( - 'reusable' => false, - 'html' => false, - 'align' => true, - ), - 'style' => 'wp-block-post-template', - 'render_callback' => 'render_legacy_query_loop_block', - 'skip_inner_blocks' => true, - ) - ); -} -add_action( 'init', 'gutenberg_register_legacy_query_loop_block' ); diff --git a/packages/widgets/src/blocks/legacy-widget/index.php b/packages/widgets/src/blocks/legacy-widget/index.php index 8a326056816c9..e827f8f7c5309 100644 --- a/packages/widgets/src/blocks/legacy-widget/index.php +++ b/packages/widgets/src/blocks/legacy-widget/index.php @@ -29,6 +29,13 @@ function render_block_core_legacy_widget( $attributes ) { $widget_key = $wp_widget_factory->get_widget_key( $id_base ); $widget_object = $wp_widget_factory->get_widget_object( $id_base ); } else { + /* + * This file is copied from the published @wordpress/widgets package when WordPress + * Core is built. Because the package is a dependency of both WordPress Core and the + * Gutenberg plugin where the block editor is developed, this fallback condition is + * required until the minimum required version of WordPress for the plugin is raised + * to 5.8. + */ $widget_key = gutenberg_get_widget_key( $id_base ); $widget_object = gutenberg_get_widget_object( $id_base ); }