diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index a66c0991e3d27..f2bc3374f9e72 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -136,7 +136,7 @@ _Parameters_ _Returns_ -- `Object | null`: The current global styles. +- `Array< object > | null`: The current global styles. ### getCurrentUser diff --git a/packages/block-library/src/footnotes/index.php b/packages/block-library/src/footnotes/index.php index ca9aca60abfb6..9815033820406 100644 --- a/packages/block-library/src/footnotes/index.php +++ b/packages/block-library/src/footnotes/index.php @@ -83,136 +83,132 @@ function register_block_core_footnotes() { } add_action( 'init', 'register_block_core_footnotes' ); -add_action( - 'wp_after_insert_post', - /** - * Saves the footnotes meta value to the revision. - * - * @since 6.3.0 - * - * @param int $revision_id The revision ID. - */ - static function( $revision_id ) { - $post_id = wp_is_post_revision( $revision_id ); - - if ( $post_id ) { +/** + * Saves the footnotes meta value to the revision. + * + * @since 6.3.0 + * + * @param int $revision_id The revision ID. + */ +function wp_save_footnotes_meta( $revision_id ) { + $post_id = wp_is_post_revision( $revision_id ); + + if ( $post_id ) { + $footnotes = get_post_meta( $post_id, 'footnotes', true ); + + if ( $footnotes ) { + // Can't use update_post_meta() because it doesn't allow revisions. + update_metadata( 'post', $revision_id, 'footnotes', $footnotes ); + } + } +} +add_action( 'wp_after_insert_post', 'wp_save_footnotes_meta' ); + +/** + * Keeps track of the revision ID for "rest_after_insert_{$post_type}". + * + * @since 6.3.0 + * + * @global int $wp_temporary_footnote_revision_id The footnote revision ID. + * + * @param int $revision_id The revision ID. + */ +function wp_keep_footnotes_revision_id( $revision_id ) { + global $wp_temporary_footnote_revision_id; + $wp_temporary_footnote_revision_id = $revision_id; +} +add_action( '_wp_put_post_revision', 'wp_keep_footnotes_revision_id' ); + +/** + * This is a specific fix for the REST API. The REST API doesn't update + * the post and post meta in one go (through `meta_input`). While it + * does fix the `wp_after_insert_post` hook to be called correctly after + * updating meta, it does NOT fix hooks such as post_updated and + * save_post, which are normally also fired after post meta is updated + * in `wp_insert_post()`. Unfortunately, `wp_save_post_revision` is + * added to the `post_updated` action, which means the meta is not + * available at the time, so we have to add it afterwards through the + * `"rest_after_insert_{$post_type}"` action. + * + * @since 6.3.0 + * + * @global int $wp_temporary_footnote_revision_id The footnote revision ID. + * + * @param WP_Post $post The post object. + */ +function wp_add_footnotes_revisions_to_post_meta( $post ) { + global $wp_temporary_footnote_revision_id; + + if ( $wp_temporary_footnote_revision_id ) { + $revision = get_post( $wp_temporary_footnote_revision_id ); + + if ( ! $revision ) { + return; + } + + $post_id = $revision->post_parent; + + // Just making sure we're updating the right revision. + if ( $post->ID === $post_id ) { $footnotes = get_post_meta( $post_id, 'footnotes', true ); if ( $footnotes ) { // Can't use update_post_meta() because it doesn't allow revisions. - update_metadata( 'post', $revision_id, 'footnotes', $footnotes ); + update_metadata( 'post', $wp_temporary_footnote_revision_id, 'footnotes', $footnotes ); } } } -); - -add_action( - '_wp_put_post_revision', - /** - * Keeps track of the revision ID for "rest_after_insert_{$post_type}". - * - * @param int $revision_id The revision ID. - */ - static function( $revision_id ) { - global $_gutenberg_revision_id; - $_gutenberg_revision_id = $revision_id; - } -); +} foreach ( array( 'post', 'page' ) as $post_type ) { - add_action( - "rest_after_insert_{$post_type}", - /** - * This is a specific fix for the REST API. The REST API doesn't update - * the post and post meta in one go (through `meta_input`). While it - * does fix the `wp_after_insert_post` hook to be called correctly after - * updating meta, it does NOT fix hooks such as post_updated and - * save_post, which are normally also fired after post meta is updated - * in `wp_insert_post()`. Unfortunately, `wp_save_post_revision` is - * added to the `post_updated` action, which means the meta is not - * available at the time, so we have to add it afterwards through the - * `"rest_after_insert_{$post_type}"` action. - * - * @since 6.3.0 - * - * @param WP_Post $post The post object. - */ - static function( $post ) { - global $_gutenberg_revision_id; - - if ( $_gutenberg_revision_id ) { - $revision = get_post( $_gutenberg_revision_id ); - $post_id = $revision->post_parent; - - // Just making sure we're updating the right revision. - if ( $post->ID === $post_id ) { - $footnotes = get_post_meta( $post_id, 'footnotes', true ); - - if ( $footnotes ) { - // Can't use update_post_meta() because it doesn't allow revisions. - update_metadata( 'post', $_gutenberg_revision_id, 'footnotes', $footnotes ); - } - } - } - } - ); + add_action( "rest_after_insert_{$post_type}", 'wp_add_footnotes_revisions_to_post_meta' ); } -add_action( - 'wp_restore_post_revision', - /** - * Restores the footnotes meta value from the revision. - * - * @since 6.3.0 - * - * @param int $post_id The post ID. - * @param int $revision_id The revision ID. - */ - static function( $post_id, $revision_id ) { - $footnotes = get_post_meta( $revision_id, 'footnotes', true ); +/** + * Restores the footnotes meta value from the revision. + * + * @since 6.3.0 + * + * @param int $post_id The post ID. + * @param int $revision_id The revision ID. + */ +function wp_restore_footnotes_from_revision( $post_id, $revision_id ) { + $footnotes = get_post_meta( $revision_id, 'footnotes', true ); - if ( $footnotes ) { - update_post_meta( $post_id, 'footnotes', $footnotes ); - } else { - delete_post_meta( $post_id, 'footnotes' ); - } - }, - 10, - 2 -); - -add_filter( - '_wp_post_revision_fields', - /** - * Adds the footnotes field to the revision. - * - * @since 6.3.0 - * - * @param array $fields The revision fields. - * @return array The revision fields. - */ - static function( $fields ) { - $fields['footnotes'] = __( 'Footnotes' ); - return $fields; + if ( $footnotes ) { + update_post_meta( $post_id, 'footnotes', $footnotes ); + } else { + delete_post_meta( $post_id, 'footnotes' ); } -); - -add_filter( - 'wp_post_revision_field_footnotes', - /** - * Gets the footnotes field from the revision. - * - * @since 6.3.0 - * - * @param string $revision_field The field value, but $revision->$field - * (footnotes) does not exist. - * @param string $field The field name, in this case "footnotes". - * @param object $revision The revision object to compare against. - * @return string The field value. - */ - static function( $revision_field, $field, $revision ) { - return get_metadata( 'post', $revision->ID, $field, true ); - }, - 10, - 3 -); +} +add_action( 'wp_restore_post_revision', 'wp_restore_footnotes_from_revision', 10, 2 ); + +/** + * Adds the footnotes field to the revision. + * + * @since 6.3.0 + * + * @param array $fields The revision fields. + * @return array The revision fields. + */ +function wp_add_footnotes_to_revision( $fields ) { + $fields['footnotes'] = __( 'Footnotes' ); + return $fields; +} +add_filter( '_wp_post_revision_fields', 'wp_add_footnotes_to_revision' ); + +/** + * Gets the footnotes field from the revision. + * + * @since 6.3.0 + * + * @param string $revision_field The field value, but $revision->$field + * (footnotes) does not exist. + * @param string $field The field name, in this case "footnotes". + * @param object $revision The revision object to compare against. + * @return string The field value. + */ +function wp_get_footnotes_from_revision( $revision_field, $field, $revision ) { + return get_metadata( 'post', $revision->ID, $field, true ); +} +add_filter( 'wp_post_revision_field_footnotes', 'wp_get_footnotes_from_revision', 10, 3 ); diff --git a/packages/block-library/src/image/deprecated.js b/packages/block-library/src/image/deprecated.js index bdfdca6ee3c4d..edbabd7fb2d83 100644 --- a/packages/block-library/src/image/deprecated.js +++ b/packages/block-library/src/image/deprecated.js @@ -9,7 +9,8 @@ import classnames from 'classnames'; import { RichText, useBlockProps, - __experimentalGetElementClassName as getBorderClassesAndStyles, + __experimentalGetElementClassName, + __experimentalGetBorderClassesAndStyles as getBorderClassesAndStyles, } from '@wordpress/block-editor'; /** @@ -545,11 +546,114 @@ const v5 = { /** * Deprecation for adding width and height as style rules on the inner img. - * It also updates the widht and height attributes to be strings instead of numbers. * * @see https://github.com/WordPress/gutenberg/pull/31366 */ const v6 = { + attributes: { + align: { + type: 'string', + }, + url: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + __experimentalRole: 'content', + }, + alt: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'alt', + default: '', + __experimentalRole: 'content', + }, + caption: { + type: 'string', + source: 'html', + selector: 'figcaption', + __experimentalRole: 'content', + }, + title: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'title', + __experimentalRole: 'content', + }, + href: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'href', + __experimentalRole: 'content', + }, + rel: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'rel', + }, + linkClass: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'class', + }, + id: { + type: 'number', + __experimentalRole: 'content', + }, + width: { + type: 'number', + }, + height: { + type: 'number', + }, + aspectRatio: { + type: 'string', + }, + scale: { + type: 'string', + }, + sizeSlug: { + type: 'string', + }, + linkDestination: { + type: 'string', + }, + linkTarget: { + type: 'string', + source: 'attribute', + selector: 'figure > a', + attribute: 'target', + }, + }, + supports: { + anchor: true, + behaviors: { + lightbox: true, + }, + color: { + text: false, + background: false, + }, + filter: { + duotone: true, + }, + __experimentalBorder: { + color: true, + radius: true, + width: true, + __experimentalSkipSerialization: true, + __experimentalDefaultControls: { + color: true, + radius: true, + width: true, + }, + }, + }, save( { attributes } ) { const { url, @@ -618,7 +722,9 @@ const v6 = { ) } { ! RichText.isEmpty( caption ) && ( diff --git a/packages/block-library/src/template-part/edit/import-controls.js b/packages/block-library/src/template-part/edit/import-controls.js index f629691535a32..c8f4fbc2e647d 100644 --- a/packages/block-library/src/template-part/edit/import-controls.js +++ b/packages/block-library/src/template-part/edit/import-controls.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __, _x, sprintf } from '@wordpress/i18n'; import { useMemo, useState } from '@wordpress/element'; import { useDispatch, useSelect, useRegistry } from '@wordpress/data'; import { @@ -163,7 +163,7 @@ export function TemplatePartImportControls( { area, setAttributes } ) { isBusy={ isBusy } aria-disabled={ isBusy || ! selectedSidebar } > - { __( 'Import' ) } + { _x( 'Import', 'button label' ) } diff --git a/packages/compose/README.md b/packages/compose/README.md index 0a3fed45bce8f..62ebdef6d798e 100644 --- a/packages/compose/README.md +++ b/packages/compose/README.md @@ -323,7 +323,7 @@ _Returns_ ### useFocusReturn -When opening modals/sidebars/dialogs, the focus must move to the opened area and return to the previously focused element when closed. The current hook implements the returning behavior. +Adds the unmount behavior of returning focus to the element which had it previously as is expected for roles like menus or dialogs. _Usage_ diff --git a/packages/compose/src/hooks/use-focus-return/index.js b/packages/compose/src/hooks/use-focus-return/index.js index 66751b7028d32..2cd93b279cd31 100644 --- a/packages/compose/src/hooks/use-focus-return/index.js +++ b/packages/compose/src/hooks/use-focus-return/index.js @@ -3,11 +3,12 @@ */ import { useRef, useEffect, useCallback } from '@wordpress/element'; +/** @type {Element|null} */ +let origin = null; + /** - * When opening modals/sidebars/dialogs, the focus - * must move to the opened area and return to the - * previously focused element when closed. - * The current hook implements the returning behavior. + * Adds the unmount behavior of returning focus to the element which had it + * previously as is expected for roles like menus or dialogs. * * @param {() => void} [onFocusReturn] Overrides the default return behavior. * @return {import('react').RefCallback} Element Ref. @@ -54,6 +55,7 @@ function useFocusReturn( onFocusReturn ) { ); if ( ref.current?.isConnected && ! isFocused ) { + origin ??= focusedBeforeMount.current; return; } @@ -64,10 +66,13 @@ function useFocusReturn( onFocusReturn ) { if ( onFocusReturnRef.current ) { onFocusReturnRef.current(); } else { - /** @type {null | HTMLElement} */ ( - focusedBeforeMount.current + /** @type {null|HTMLElement} */ ( + ! focusedBeforeMount.current.isConnected + ? origin + : focusedBeforeMount.current )?.focus(); } + origin = null; } }, [] ); } diff --git a/packages/core-data/README.md b/packages/core-data/README.md index 63e6e28db08d5..c778b724149ef 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -313,7 +313,7 @@ _Parameters_ _Returns_ -- `Object | null`: The current global styles. +- `Array< object > | null`: The current global styles. ### getCurrentUser diff --git a/packages/core-data/src/selectors.ts b/packages/core-data/src/selectors.ts index 142d24a9d2b8d..377134ab7c9a3 100644 --- a/packages/core-data/src/selectors.ts +++ b/packages/core-data/src/selectors.ts @@ -1266,7 +1266,7 @@ export function getBlockPatternCategories( state: State ): Array< any > { */ export function getCurrentThemeGlobalStylesRevisions( state: State -): Object | null { +): Array< object > | null { const currentGlobalStylesId = __experimentalGetCurrentGlobalStylesId( state ); diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 116dea17a5c04..fc5ce8c948e2a 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -65,7 +65,11 @@ export function initializeEditor( dispatch( blocksStore ).__experimentalReapplyBlockTypeFilters(); // Check if the block list view should be open by default. - if ( select( editPostStore ).isFeatureActive( 'showListViewByDefault' ) ) { + // If `distractionFree` mode is enabled, the block list view should not be open. + if ( + select( editPostStore ).isFeatureActive( 'showListViewByDefault' ) && + ! select( editPostStore ).isFeatureActive( 'distractionFree' ) + ) { dispatch( editPostStore ).setIsListViewOpened( true ); } diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/index.js b/packages/edit-site/src/components/global-styles/screen-revisions/index.js index c6920f3d63c24..b3f6c5cf8c500 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/index.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/index.js @@ -7,6 +7,7 @@ import { __experimentalUseNavigator as useNavigator, __experimentalConfirmDialog as ConfirmDialog, Spinner, + __experimentalSpacer as Spacer, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { useContext, useState, useEffect } from '@wordpress/element'; @@ -87,6 +88,7 @@ function ScreenRevisions() { const isLoadButtonEnabled = !! globalStylesRevision?.id && ! areGlobalStyleConfigsEqual( globalStylesRevision, userConfig ); + const shouldShowRevisions = ! isLoading && revisions.length; return ( <> @@ -99,68 +101,84 @@ function ScreenRevisions() { { isLoading && ( ) } - { ! isLoading && ( - - ) } -
- - { isLoadButtonEnabled && ( - - + + ) } +
+ { isLoadingRevisionWithUnsavedChanges && ( + + restoreRevision( globalStylesRevision ) + } + onCancel={ () => + setIsLoadingRevisionWithUnsavedChanges( false ) } - onClick={ () => { - if ( hasUnsavedChanges ) { - setIsLoadingRevisionWithUnsavedChanges( - true - ); - } else { - restoreRevision( globalStylesRevision ); - } - } } > - { __( 'Apply' ) } - - - ) } - - { isLoadingRevisionWithUnsavedChanges && ( - +

+ { __( + 'Loading this revision will discard all unsaved changes.' + ) } +

+

+ { __( + 'Do you want to replace your unsaved changes in the editor?' + ) } +

+ +
) } - isOpen={ isLoadingRevisionWithUnsavedChanges } - confirmButtonText={ __( ' Discard unsaved changes' ) } - onConfirm={ () => restoreRevision( globalStylesRevision ) } - onCancel={ () => - setIsLoadingRevisionWithUnsavedChanges( false ) + + ) : ( + + { + // Adding an existing translation here in case these changes are shipped to WordPress 6.3. + // Later we could update to something better, e.g., "There are currently no style revisions.". + __( 'No results found.' ) } - > - <> -

- { __( - 'Loading this revision will discard all unsaved changes.' - ) } -

-

- { __( - 'Do you want to replace your unsaved changes in the editor?' - ) } -

- -
+ ) } ); diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js b/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js index 0b7d086c1120f..8f618a4897edc 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/test/use-global-styles-revisions.js @@ -49,6 +49,7 @@ describe( 'useGlobalStylesRevisions', () => { styles: {}, }, ], + isLoadingGlobalStylesRevisions: false, }; it( 'returns loaded revisions with no unsaved changes', () => { @@ -117,11 +118,23 @@ describe( 'useGlobalStylesRevisions', () => { const { result } = renderHook( () => useGlobalStylesRevisions() ); const { revisions, isLoading, hasUnsavedChanges } = result.current; - expect( isLoading ).toBe( true ); + expect( isLoading ).toBe( false ); expect( hasUnsavedChanges ).toBe( false ); expect( revisions ).toEqual( [] ); } ); + it( 'returns loading status when resolving global revisions', () => { + useSelect.mockImplementation( () => ( { + ...selectValue, + isLoadingGlobalStylesRevisions: true, + } ) ); + + const { result } = renderHook( () => useGlobalStylesRevisions() ); + const { isLoading } = result.current; + + expect( isLoading ).toBe( true ); + } ); + it( 'returns empty revisions when authors are not yet available', () => { useSelect.mockImplementation( () => ( { ...selectValue, diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js index ce3123e3fd028..55b6117325a9b 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/use-global-styles-revisions.js @@ -21,34 +21,40 @@ const EMPTY_ARRAY = []; const { GlobalStylesContext } = unlock( blockEditorPrivateApis ); export default function useGlobalStylesRevisions() { const { user: userConfig } = useContext( GlobalStylesContext ); - const { authors, currentUser, isDirty, revisions } = useSelect( - ( select ) => { - const { - __experimentalGetDirtyEntityRecords, - getCurrentUser, - getUsers, - getCurrentThemeGlobalStylesRevisions, - } = select( coreStore ); - const dirtyEntityRecords = __experimentalGetDirtyEntityRecords(); - const _currentUser = getCurrentUser(); - const _isDirty = dirtyEntityRecords.length > 0; - const globalStylesRevisions = - getCurrentThemeGlobalStylesRevisions() || EMPTY_ARRAY; - const _authors = - getUsers( SITE_EDITOR_AUTHORS_QUERY ) || EMPTY_ARRAY; + const { + authors, + currentUser, + isDirty, + revisions, + isLoadingGlobalStylesRevisions, + } = useSelect( ( select ) => { + const { + __experimentalGetDirtyEntityRecords, + getCurrentUser, + getUsers, + getCurrentThemeGlobalStylesRevisions, + isResolving, + } = select( coreStore ); + const dirtyEntityRecords = __experimentalGetDirtyEntityRecords(); + const _currentUser = getCurrentUser(); + const _isDirty = dirtyEntityRecords.length > 0; + const globalStylesRevisions = + getCurrentThemeGlobalStylesRevisions() || EMPTY_ARRAY; + const _authors = getUsers( SITE_EDITOR_AUTHORS_QUERY ) || EMPTY_ARRAY; - return { - authors: _authors, - currentUser: _currentUser, - isDirty: _isDirty, - revisions: globalStylesRevisions, - }; - }, - [] - ); + return { + authors: _authors, + currentUser: _currentUser, + isDirty: _isDirty, + revisions: globalStylesRevisions, + isLoadingGlobalStylesRevisions: isResolving( + 'getCurrentThemeGlobalStylesRevisions' + ), + }; + }, [] ); return useMemo( () => { let _modifiedRevisions = []; - if ( ! authors.length || ! revisions.length ) { + if ( ! authors.length || isLoadingGlobalStylesRevisions ) { return { revisions: _modifiedRevisions, hasUnsavedChanges: isDirty, @@ -66,30 +72,32 @@ export default function useGlobalStylesRevisions() { }; } ); - // Flags the most current saved revision. - if ( _modifiedRevisions[ 0 ].id !== 'unsaved' ) { - _modifiedRevisions[ 0 ].isLatest = true; - } + if ( _modifiedRevisions.length ) { + // Flags the most current saved revision. + if ( _modifiedRevisions[ 0 ].id !== 'unsaved' ) { + _modifiedRevisions[ 0 ].isLatest = true; + } - // Adds an item for unsaved changes. - if ( - isDirty && - userConfig && - Object.keys( userConfig ).length > 0 && - currentUser - ) { - const unsavedRevision = { - id: 'unsaved', - styles: userConfig?.styles, - settings: userConfig?.settings, - author: { - name: currentUser?.name, - avatar_urls: currentUser?.avatar_urls, - }, - modified: new Date(), - }; + // Adds an item for unsaved changes. + if ( + isDirty && + userConfig && + Object.keys( userConfig ).length > 0 && + currentUser + ) { + const unsavedRevision = { + id: 'unsaved', + styles: userConfig?.styles, + settings: userConfig?.settings, + author: { + name: currentUser?.name, + avatar_urls: currentUser?.avatar_urls, + }, + modified: new Date(), + }; - _modifiedRevisions.unshift( unsavedRevision ); + _modifiedRevisions.unshift( unsavedRevision ); + } } return { @@ -97,5 +105,12 @@ export default function useGlobalStylesRevisions() { hasUnsavedChanges: isDirty, isLoading: false, }; - }, [ isDirty, revisions, currentUser, authors, userConfig ] ); + }, [ + isDirty, + revisions, + currentUser, + authors, + userConfig, + isLoadingGlobalStylesRevisions, + ] ); } diff --git a/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js b/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js index 6bc2d59ec9e97..49efa11368a7d 100644 --- a/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js +++ b/packages/edit-site/src/components/keyboard-shortcuts/edit-mode.js @@ -33,10 +33,8 @@ function KeyboardShortcutsEditMode() { ); const { redo, undo } = useDispatch( coreStore ); const { - isFeatureActive, setIsListViewOpened, switchEditorMode, - toggleFeature, setIsInserterOpened, closeGeneralSidebar, } = useDispatch( editSiteStore ); @@ -47,7 +45,8 @@ function KeyboardShortcutsEditMode() { const { getBlockName, getSelectedBlockClientId, getBlockAttributes } = useSelect( blockEditorStore ); - const { set: setPreference } = useDispatch( preferencesStore ); + const { get: getPreference } = useSelect( preferencesStore ); + const { set: setPreference, toggle } = useDispatch( preferencesStore ); const { createInfoNotice } = useDispatch( noticesStore ); const toggleDistractionFree = () => { @@ -135,9 +134,9 @@ function KeyboardShortcutsEditMode() { useShortcut( 'core/edit-site/toggle-distraction-free', () => { toggleDistractionFree(); - toggleFeature( 'distractionFree' ); + toggle( 'core/edit-site', 'distractionFree' ); createInfoNotice( - isFeatureActive( 'distractionFree' ) + getPreference( 'core/edit-site', 'distractionFree' ) ? __( 'Distraction free mode turned on.' ) : __( 'Distraction free mode turned off.' ), { diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 17c660b94f003..1a1c2f3a3830a 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -340,6 +340,12 @@ export default function Layout() { ! isEditorLoading } isFullWidth={ isEditing } + defaultSize={ { + width: + canvasSize.width - + 24 /* $canvas-padding */, + height: canvasSize.height, + } } isOversized={ isResizableFrameOversized } diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index d1fbedb141f70..a79da59c8bf06 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -57,6 +57,9 @@ width: 300px; } } + .edit-site-patterns__sync-status-filter-option:not([aria-checked="true"]) { + color: $gray-600; + } .edit-site-patterns__sync-status-filter-option:active { background: $gray-700; color: $gray-100; diff --git a/packages/edit-site/src/components/page-template-parts/index.js b/packages/edit-site/src/components/page-template-parts/index.js index 24b042ed92001..e03d726a57525 100644 --- a/packages/edit-site/src/components/page-template-parts/index.js +++ b/packages/edit-site/src/components/page-template-parts/index.js @@ -39,7 +39,6 @@ export default function PageTemplateParts() { params={ { postId: templatePart.id, postType: templatePart.type, - canvas: 'view', } } state={ { backPath: '/wp_template_part/all' } } > diff --git a/packages/edit-site/src/components/resizable-frame/index.js b/packages/edit-site/src/components/resizable-frame/index.js index 2b98e46a71532..ee5c98da09033 100644 --- a/packages/edit-site/src/components/resizable-frame/index.js +++ b/packages/edit-site/src/components/resizable-frame/index.js @@ -6,7 +6,7 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { useState, useRef, useEffect } from '@wordpress/element'; +import { useState, useRef } from '@wordpress/element'; import { ResizableBox, Tooltip, @@ -82,6 +82,8 @@ function ResizableFrame( { setIsOversized, isReady, children, + /** The default (unresized) width/height of the frame, based on the space availalbe in the viewport. */ + defaultSize, innerContentStyle, } ) { const [ frameSize, setFrameSize ] = useState( INITIAL_FRAME_SIZE ); @@ -95,22 +97,13 @@ function ResizableFrame( { [] ); const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); - const initialAspectRatioRef = useRef( null ); - // The width of the resizable frame on initial render. - const initialComputedWidthRef = useRef( null ); const FRAME_TRANSITION = { type: 'tween', duration: isResizing ? 0 : 0.5 }; const frameRef = useRef( null ); const resizableHandleHelpId = useInstanceId( ResizableFrame, 'edit-site-resizable-frame-handle-help' ); - - // Remember frame dimensions on initial render. - useEffect( () => { - const { offsetWidth, offsetHeight } = frameRef.current.resizable; - initialComputedWidthRef.current = offsetWidth; - initialAspectRatioRef.current = offsetWidth / offsetHeight; - }, [] ); + const defaultAspectRatio = defaultSize.width / defaultSize.height; const handleResizeStart = ( _event, _direction, ref ) => { // Remember the starting width so we don't have to get `ref.offsetWidth` on @@ -126,7 +119,7 @@ function ResizableFrame( { const maxDoubledDelta = delta.width < 0 // is shrinking ? deltaAbs - : ( initialComputedWidthRef.current - startingWidth ) / 2; + : ( defaultSize.width - startingWidth ) / 2; const deltaToDouble = Math.min( deltaAbs, maxDoubledDelta ); const doubleSegment = deltaAbs === 0 ? 0 : deltaToDouble / deltaAbs; const singleSegment = 1 - doubleSegment; @@ -135,17 +128,14 @@ function ResizableFrame( { const updatedWidth = startingWidth + delta.width; - setIsOversized( updatedWidth > initialComputedWidthRef.current ); + setIsOversized( updatedWidth > defaultSize.width ); // Width will be controlled by the library (via `resizeRatio`), // so we only need to update the height. setFrameSize( { height: isOversized ? '100%' - : calculateNewHeight( - updatedWidth, - initialAspectRatioRef.current - ), + : calculateNewHeight( updatedWidth, defaultAspectRatio ), } ); }; @@ -186,15 +176,12 @@ function ResizableFrame( { FRAME_MIN_WIDTH, frameRef.current.resizable.offsetWidth + delta ), - initialComputedWidthRef.current + defaultSize.width ); setFrameSize( { width: newWidth, - height: calculateNewHeight( - newWidth, - initialAspectRatioRef.current - ), + height: calculateNewHeight( newWidth, defaultAspectRatio ), } ); }; @@ -291,9 +278,7 @@ function ResizableFrame( { undefined } aria-valuemin={ FRAME_MIN_WIDTH } - aria-valuemax={ - initialComputedWidthRef.current - } + aria-valuemax={ defaultSize.width } onKeyDown={ handleResizableHandleKeyDown } initial="hidden" exit="hidden" diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js index d714ecb43b15d..14d78405b3abe 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/leaf-more-menu.js @@ -23,10 +23,12 @@ import { currentlyPreviewingTheme, } from '../../utils/is-previewing-theme'; import { unlock } from '../../lock-unlock'; +import { getPathFromURL } from '../sync-state-with-url/use-sync-path-with-url'; -const { useHistory } = unlock( routerPrivateApis ); +const { useLocation, useHistory } = unlock( routerPrivateApis ); export default function LeafMoreMenu( props ) { + const location = useLocation(); const history = useHistory(); const { block } = props; const { clientId } = block; @@ -63,22 +65,32 @@ export default function LeafMoreMenu( props ) { attributes.type && history ) { - history.push( { - postType: attributes.type, - postId: attributes.id, - ...( isPreviewingTheme() && { - wp_theme_preview: currentlyPreviewingTheme(), - } ), - } ); + history.push( + { + postType: attributes.type, + postId: attributes.id, + ...( isPreviewingTheme() && { + wp_theme_preview: currentlyPreviewingTheme(), + } ), + }, + { + backPath: getPathFromURL( location.params ), + } + ); } if ( name === 'core/page-list-item' && attributes.id && history ) { - history.push( { - postType: 'page', - postId: attributes.id, - ...( isPreviewingTheme() && { - wp_theme_preview: currentlyPreviewingTheme(), - } ), - } ); + history.push( + { + postType: 'page', + postId: attributes.id, + ...( isPreviewingTheme() && { + wp_theme_preview: currentlyPreviewingTheme(), + } ), + }, + { + backPath: getPathFromURL( location.params ), + } + ); } }, [ history ] diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js index 331221dde7985..ff6466f98ab5f 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js @@ -28,10 +28,15 @@ import { unlock } from '../../lock-unlock'; const { useHistory } = unlock( routerPrivateApis ); const PageItem = ( { postType = 'page', postId, ...props } ) => { - const linkInfo = useLink( { - postType, - postId, - } ); + const linkInfo = useLink( + { + postType, + postId, + }, + { + backPath: '/page', + } + ); return ; }; diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js index aaf7b871b8146..94a77ab7d88db 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js @@ -10,7 +10,6 @@ import { __experimentalHeading as Heading, } from '@wordpress/components'; import { useViewportMatch } from '@wordpress/compose'; -import { useSelect } from '@wordpress/data'; import { getTemplatePartIcon } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { getQueryArgs } from '@wordpress/url'; @@ -24,7 +23,6 @@ import SidebarNavigationItem from '../sidebar-navigation-item'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; import CategoryItem from './category-item'; import { DEFAULT_CATEGORY, DEFAULT_TYPE } from '../page-patterns/utils'; -import { store as editSiteStore } from '../../store'; import { useLink } from '../routes/link'; import usePatternCategories from './use-pattern-categories'; import useMyPatterns from './use-my-patterns'; @@ -106,11 +104,6 @@ export default function SidebarNavigationScreenPatterns() { const { patternCategories, hasPatterns } = usePatternCategories(); const { myPatterns } = useMyPatterns(); - const isTemplatePartsMode = useSelect( ( select ) => { - const settings = select( editSiteStore ).getSettings(); - return !! settings.supportsTemplatePartsMode; - }, [] ); - const templatePartsLink = useLink( { path: '/wp_template_part/all' } ); const footer = ! isMobileViewport ? ( @@ -129,7 +122,6 @@ export default function SidebarNavigationScreenPatterns() { return ( { + const settings = select( editSiteStore ).getSettings(); + + return !! settings.supportsTemplatePartsMode; + }, [] ); + return ( { @@ -31,11 +29,6 @@ const TemplateItem = ( { postType, postId, ...props } ) => { export default function SidebarNavigationScreenTemplates() { const isMobileViewport = useViewportMatch( 'medium', '<' ); - const isTemplatePartsMode = useSelect( ( select ) => { - const settings = select( editSiteStore ).getSettings(); - - return !! settings.supportsTemplatePartsMode; - }, [] ); const { records: templates, isResolving: isLoading } = useEntityRecords( 'postType', @@ -51,10 +44,9 @@ export default function SidebarNavigationScreenTemplates() { ); const browseAllLink = useLink( { path: '/wp_template/all' } ); - const canCreate = ! isMobileViewport && ! isTemplatePartsMode; + const canCreate = ! isMobileViewport; return ( { const { getSettings } = unlock( select( editSiteStore ) ); @@ -40,6 +43,7 @@ export default function SidebarNavigationScreen( { }; }, [] ); const { getTheme } = useSelect( coreStore ); + const location = useLocation(); const navigator = useNavigator(); const theme = getTheme( currentlyPreviewingTheme() ); const icon = isRTL() ? chevronRight : chevronLeft; @@ -56,13 +60,17 @@ export default function SidebarNavigationScreen( { alignment="flex-start" className="edit-site-sidebar-navigation-screen__title-icon" > - { ! isRoot && ! backPath && ( + { ! isRoot && ( { - if ( navigator.location.isInitial ) { - navigator.goToParent( { replace: true } ); + const backPath = + backPathProp ?? location.state?.backPath; + if ( backPath ) { + navigator.goTo( backPath, { + isBack: true, + } ); } else { - navigator.goBack(); + navigator.goToParent(); } } } icon={ icon } @@ -70,16 +78,6 @@ export default function SidebarNavigationScreen( { showTooltip={ false } /> ) } - { ! isRoot && backPath && ( - - navigator.goTo( backPath, { isBack: true } ) - } - icon={ icon } - label={ __( 'Back' ) } - showTooltip={ false } - /> - ) } { isRoot && ( ( { canvasMode: unlock( select( editSiteStore ) ).getCanvasMode(), @@ -150,7 +156,9 @@ function useEditUICommands() { [] ); const { openModal } = useDispatch( interfaceStore ); - const { toggle } = useDispatch( preferencesStore ); + const { get: getPreference } = useSelect( preferencesStore ); + const { set: setPreference, toggle } = useDispatch( preferencesStore ); + const { createInfoNotice } = useDispatch( noticesStore ); if ( canvasMode !== 'edit' ) { return { isLoading: false, commands: [] }; @@ -196,6 +204,29 @@ function useEditUICommands() { }, } ); + commands.push( { + name: 'core/toggle-distraction-free', + label: __( 'Toggle distraction free' ), + icon: cog, + callback: ( { close } ) => { + setPreference( 'core/edit-site', 'fixedToolbar', false ); + setIsInserterOpened( false ); + setIsListViewOpened( false ); + closeGeneralSidebar(); + toggle( 'core/edit-site', 'distractionFree' ); + createInfoNotice( + getPreference( 'core/edit-site', 'distractionFree' ) + ? __( 'Distraction free mode turned on.' ) + : __( 'Distraction free mode turned off.' ), + { + id: 'core/edit-site/distraction-free-mode/notice', + type: 'snackbar', + } + ); + close(); + }, + } ); + commands.push( { name: 'core/toggle-top-toolbar', label: __( 'Toggle top toolbar' ), diff --git a/packages/edit-site/src/store/private-actions.js b/packages/edit-site/src/store/private-actions.js index 1b97959277760..f3dd4c10cec43 100644 --- a/packages/edit-site/src/store/private-actions.js +++ b/packages/edit-site/src/store/private-actions.js @@ -18,11 +18,15 @@ export const setCanvasMode = mode, } ); // Check if the block list view should be open by default. + // If `distractionFree` mode is enabled, the block list view should not be open. if ( mode === 'edit' && registry .select( preferencesStore ) - .get( 'core/edit-site', 'showListViewByDefault' ) + .get( 'core/edit-site', 'showListViewByDefault' ) && + ! registry + .select( preferencesStore ) + .get( 'core/edit-site', 'distractionFree' ) ) { dispatch.setIsListViewOpened( true ); } diff --git a/test/e2e/specs/site-editor/hybrid-theme.spec.js b/test/e2e/specs/site-editor/hybrid-theme.spec.js index 9d5def2caf71f..060daf508491a 100644 --- a/test/e2e/specs/site-editor/hybrid-theme.spec.js +++ b/test/e2e/specs/site-editor/hybrid-theme.spec.js @@ -12,30 +12,40 @@ test.describe( 'Hybrid theme', () => { await requestUtils.activateTheme( 'twentytwentyone' ); } ); - test( 'should not show the Add Template Part button', async ( { - admin, - page, - } ) => { + test( 'can access template parts list page', async ( { admin, page } ) => { await admin.visitAdminPage( - '/site-editor.php', + 'site-editor.php', 'postType=wp_template_part&path=/wp_template_part/all' ); await expect( - page.locator( 'role=button[name="Add New Template Part"i]' ) - ).toBeHidden(); + page.getByRole( 'table' ).getByRole( 'link', { name: 'header' } ) + ).toBeVisible(); + } ); - // Back to Patterns page. - await page.click( - 'role=region[name="Navigation"i] >> role=button[name="Back"i]' + test( 'can view a template part', async ( { admin, editor, page } ) => { + await admin.visitAdminPage( + 'site-editor.php', + 'postType=wp_template_part&path=/wp_template_part/all' ); - await page.click( - 'role=region[name="Navigation"i] >> role=button[name="Create pattern"i]' - ); + const templatePart = page + .getByRole( 'table' ) + .getByRole( 'link', { name: 'header' } ); + + await expect( templatePart ).toBeVisible(); + await templatePart.click(); + + await expect( + page.getByRole( 'region', { name: 'Editor content' } ) + ).toBeVisible(); + + await editor.canvas.click( 'body' ); await expect( - page.locator( 'role=menuitem[name="Create template part"i]' ) - ).toBeHidden(); + editor.canvas.getByRole( 'document', { + name: 'Block: Site Title', + } ) + ).toBeVisible(); } ); } ); diff --git a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js index 4ab61111df419..cb90ebe5edf25 100644 --- a/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js +++ b/test/e2e/specs/site-editor/user-global-styles-revisions.spec.js @@ -22,9 +22,24 @@ test.describe( 'Global styles revisions', () => { await requestUtils.activateTheme( 'twentytwentyone' ); } ); - test.beforeEach( async ( { admin, editor } ) => { + test.beforeEach( async ( { admin } ) => { await admin.visitSiteEditor(); - await editor.canvas.click( 'body' ); + } ); + + test( 'should display no revisions message if landing via command center', async ( { + page, + } ) => { + await page + .getByRole( 'button', { name: 'Open command palette' } ) + .focus(); + await page.keyboard.press( 'Meta+k' ); + await page.keyboard.type( 'styles revisions' ); + await page + .getByRole( 'option', { name: 'Open styles revisions' } ) + .click(); + await expect( + page.getByTestId( 'global-styles-no-revisions' ) + ).toHaveText( 'No results found.' ); } ); test( 'should display revisions UI when there is more than 1 revision', async ( { @@ -32,6 +47,7 @@ test.describe( 'Global styles revisions', () => { editor, userGlobalStylesRevisions, } ) => { + await editor.canvas.click( 'body' ); const currentRevisions = await userGlobalStylesRevisions.getGlobalStylesRevisions(); await userGlobalStylesRevisions.openStylesPanel(); @@ -78,6 +94,7 @@ test.describe( 'Global styles revisions', () => { editor, userGlobalStylesRevisions, } ) => { + await editor.canvas.click( 'body' ); await userGlobalStylesRevisions.openStylesPanel(); await page.getByRole( 'button', { name: 'Colors styles' } ).click(); await page diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.html b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.html new file mode 100644 index 0000000000000..6c113ee0f09ee --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.html @@ -0,0 +1,3 @@ + +
+ diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.json b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.json new file mode 100644 index 0000000000000..7f83baa81fc63 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.json @@ -0,0 +1,22 @@ +[ + { + "name": "core/image", + "isValid": true, + "attributes": { + "align": "left", + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + "alt": "", + "caption": "", + "width": 164, + "height": 164, + "sizeSlug": "large", + "className": "is-style-rounded", + "style": { + "border": { + "radius": "100%" + } + } + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.parsed.json b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.parsed.json new file mode 100644 index 0000000000000..e9c061bb19125 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.parsed.json @@ -0,0 +1,22 @@ +[ + { + "blockName": "core/image", + "attrs": { + "align": "left", + "width": 164, + "height": 164, + "sizeSlug": "large", + "style": { + "border": { + "radius": "100%" + } + }, + "className": "is-style-rounded" + }, + "innerBlocks": [], + "innerHTML": "\n
\"\"
\n", + "innerContent": [ + "\n
\"\"
\n" + ] + } +] diff --git a/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html new file mode 100644 index 0000000000000..ad70fe65f3582 --- /dev/null +++ b/test/integration/fixtures/blocks/core__image__deprecated-v6-add-style-width-height.serialized.html @@ -0,0 +1,3 @@ + +
+