From 55ff770cc7e65d26c6480cd2d0c66ec74404cc78 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 12 Dec 2023 10:04:27 +0000 Subject: [PATCH 01/17] Fix: Use span on template list titles. (#56955) --- .../src/components/page-templates/dataviews-templates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/page-templates/dataviews-templates.js b/packages/edit-site/src/components/page-templates/dataviews-templates.js index e51f9ba970c4cd..07de0cb73ff445 100644 --- a/packages/edit-site/src/components/page-templates/dataviews-templates.js +++ b/packages/edit-site/src/components/page-templates/dataviews-templates.js @@ -83,7 +83,7 @@ function TemplateTitle( { item } ) { const { isCustomized } = useAddedBy( item.type, item.id ); return ( - + Date: Tue, 12 Dec 2023 11:25:59 +0100 Subject: [PATCH 02/17] Site editor: do not use navigator's internal classname (#56911) * Apply the `edit-site-sidebar__screen-wrapper` classname to all navigator screens in the site editor sidebar * Use the newly added classname instead of the private component's classname --- .../edit-site/src/components/sidebar/index.js | 65 ++++++++++++------- .../src/components/sidebar/style.scss | 22 +++---- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index b66bf4390a6bcf..3fa1280d59f427 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classNames from 'classnames'; + /** * WordPress dependencies */ @@ -33,53 +38,65 @@ import DataViewsSidebarContent from '../sidebar-dataviews'; const { useLocation } = unlock( routerPrivateApis ); +function SidebarScreenWrapper( { className, ...props } ) { + return ( + + ); +} + function SidebarScreens() { useSyncPathWithURL(); return ( <> - + - - + + - - + + - - + + - - + + - - + + - + { window?.__experimentalAdminViews && ( - + } /> - + ) } - + - - + + - - + + - - + + - - + + - + ); } diff --git a/packages/edit-site/src/components/sidebar/style.scss b/packages/edit-site/src/components/sidebar/style.scss index 9a3644cc830d56..ef24b0d4b8cf6f 100644 --- a/packages/edit-site/src/components/sidebar/style.scss +++ b/packages/edit-site/src/components/sidebar/style.scss @@ -1,14 +1,17 @@ .edit-site-sidebar__content { flex-grow: 1; overflow-y: auto; +} + +.edit-site-sidebar__screen-wrapper { + @include custom-scrollbars-on-hover(transparent, $gray-700); + scrollbar-gutter: stable; + display: flex; + flex-direction: column; + height: 100%; - .components-navigator-screen { - @include custom-scrollbars-on-hover(transparent, $gray-700); - scrollbar-gutter: stable; - display: flex; - flex-direction: column; - height: 100%; - } + // This matches the logo padding + padding: 0 $grid-unit-15; } .edit-site-sidebar__footer { @@ -17,8 +20,3 @@ margin: 0 $canvas-padding; padding: $canvas-padding 0; } - -.edit-site-sidebar__content > div { - // This matches the logo padding - padding: 0 $grid-unit-15; -} From 4aa1d984375cb9467e9cc5248c9804a4bd97b2f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 12 Dec 2023 19:23:43 +0100 Subject: [PATCH 03/17] DataViews: update sorting semantics (#56717) --- packages/dataviews/src/view-actions.js | 23 +++++++---------------- packages/dataviews/src/view-table.js | 25 +++++++------------------ 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/packages/dataviews/src/view-actions.js b/packages/dataviews/src/view-actions.js index 4d012d4e5a38ff..a5330c08f299ce 100644 --- a/packages/dataviews/src/view-actions.js +++ b/packages/dataviews/src/view-actions.js @@ -232,22 +232,13 @@ function SortMenu( { fields, view, onChangeView } ) { } onSelect={ ( event ) => { event.preventDefault(); - if ( - sortedDirection === direction - ) { - onChangeView( { - ...view, - sort: undefined, - } ); - } else { - onChangeView( { - ...view, - sort: { - field: field.id, - direction, - }, - } ); - } + onChangeView( { + ...view, + sort: { + field: field.id, + direction, + }, + } ); } } > { info.label } diff --git a/packages/dataviews/src/view-table.js b/packages/dataviews/src/view-table.js index e34d99008657bc..3f5891f076791e 100644 --- a/packages/dataviews/src/view-table.js +++ b/packages/dataviews/src/view-table.js @@ -118,24 +118,13 @@ function HeaderMenu( { field, view, onChangeView } ) { } onSelect={ ( event ) => { event.preventDefault(); - if ( - isSorted && - view.sort.direction === - direction - ) { - onChangeView( { - ...view, - sort: undefined, - } ); - } else { - onChangeView( { - ...view, - sort: { - field: field.id, - direction, - }, - } ); - } + onChangeView( { + ...view, + sort: { + field: field.id, + direction, + }, + } ); } } > { info.label } From 5b688d4656dfb9534aa478b1076eabeac2fd7969 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Tue, 12 Dec 2023 19:56:35 +0100 Subject: [PATCH 04/17] Create-block-interactive-template: Add all files to the generated plugin zip (#56943) * Add a files field to the package.json to add all files to the plugin zip * Update changelog --- packages/create-block-interactive-template/CHANGELOG.md | 1 + packages/create-block-interactive-template/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/create-block-interactive-template/CHANGELOG.md b/packages/create-block-interactive-template/CHANGELOG.md index 735790d07b803e..388c9de959e437 100644 --- a/packages/create-block-interactive-template/CHANGELOG.md +++ b/packages/create-block-interactive-template/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add all files to the generated plugin zip. [#56943](https://github.com/WordPress/gutenberg/pull/56943) - Prevent crash when Gutenberg plugin is not installed. [#56941](https://github.com/WordPress/gutenberg/pull/56941) ## 1.10.1 (2023-12-07) diff --git a/packages/create-block-interactive-template/index.js b/packages/create-block-interactive-template/index.js index b2682600f7af6d..6e5ffcb9cc9ae6 100644 --- a/packages/create-block-interactive-template/index.js +++ b/packages/create-block-interactive-template/index.js @@ -10,6 +10,7 @@ module.exports = { description: 'An interactive block with the Interactivity API', dashicon: 'media-interactive', npmDependencies: [ '@wordpress/interactivity' ], + customPackageJSON: { files: [ '[^.]*' ] }, supports: { interactivity: true, }, From 482ac0c420ce25b4794fcb87c46667cb62169f18 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Tue, 12 Dec 2023 19:13:10 +0000 Subject: [PATCH 05/17] Fix: Fatal php error if a template was created by an author that was deleted. (#56990) --- lib/compat/wordpress-6.5/rest-api.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.5/rest-api.php b/lib/compat/wordpress-6.5/rest-api.php index 3b82815c41e420..12d789fb58b869 100644 --- a/lib/compat/wordpress-6.5/rest-api.php +++ b/lib/compat/wordpress-6.5/rest-api.php @@ -91,7 +91,11 @@ function _gutenberg_get_wp_templates_author_text_field( $template_object ) { case 'site': return get_bloginfo( 'name' ); case 'user': - return get_user_by( 'id', $template_object['author'] )->get( 'display_name' ); + $author = get_user_by( 'id', $template_object['author'] ); + if ( ! $author ) { + return __( 'Unknown author', 'gutenberg' ); + } + return $author->get( 'display_name' ); } } From c90bb031ee5ee72ec382c10a123cd713e9c78f65 Mon Sep 17 00:00:00 2001 From: Chad Chadbourne <13856531+chad1008@users.noreply.github.com> Date: Tue, 12 Dec 2023 14:23:15 -0500 Subject: [PATCH 06/17] Implement `Tabs` in editor settings (#55360) * implement Tabs in editor settings sidebar * remove duplicated styles from editor sidebar * incorporate initial feedback * pass props to Tabs directly * add `closeGeneralSidebar` to `onSelect` callback * set TabPanels to `focuasable={false}` * detect when sidebar is closed. pass `selectedTabId` of `null` * improve `Tabs` `onSelect` callback * remove `aria-label` and `data-label` props * add note explaining null selected tab when sidebar is closed * update e2e test * style updates * update internal component structure to avoid rerenders * remove fragment * prevent infinite loop when opening third party sidebar * update e2e tests for `Tabs` compatibility * fix double top margin on tabpanels * update to use new tabId prop * remove import that is no longer needed after rebase * fix keyboard navigable blocks test * fix visibility and tab order tests * fix footnotes tests * fix change detection test --- .../editor/various/change-detection.test.js | 6 +- .../specs/editor/various/editor-modes.test.js | 15 +- .../specs/editor/various/preferences.test.js | 10 +- .../specs/editor/various/sidebar.test.js | 29 +-- .../sidebar/settings-header/index.js | 84 ++------- .../sidebar/settings-header/style.scss | 74 -------- .../sidebar/settings-sidebar/index.js | 172 ++++++++++++------ .../src/components/sidebar/style.scss | 16 +- packages/edit-post/src/style.scss | 1 - .../editor/plugins/custom-post-types.spec.js | 2 +- .../block-hierarchy-navigation.spec.js | 2 +- .../specs/editor/various/footnotes.spec.js | 4 +- .../various/keyboard-navigable-blocks.spec.js | 8 +- 13 files changed, 180 insertions(+), 243 deletions(-) delete mode 100644 packages/edit-post/src/components/sidebar/settings-header/style.scss diff --git a/packages/e2e-tests/specs/editor/various/change-detection.test.js b/packages/e2e-tests/specs/editor/various/change-detection.test.js index 62057c4cbb2bc0..0eb673671222f2 100644 --- a/packages/e2e-tests/specs/editor/various/change-detection.test.js +++ b/packages/e2e-tests/specs/editor/various/change-detection.test.js @@ -370,7 +370,11 @@ describe( 'Change detection', () => { it( 'consecutive edits to the same attribute should mark the post as dirty after a save', async () => { // Open the sidebar block settings. await openDocumentSettingsSidebar(); - await page.click( '.edit-post-sidebar__panel-tab[data-label="Block"]' ); + + const blockInspectorTab = await page.waitForXPath( + '//button[@role="tab"][contains(text(), "Block")]' + ); + await blockInspectorTab.click(); // Insert a paragraph. await clickBlockAppender(); diff --git a/packages/e2e-tests/specs/editor/various/editor-modes.test.js b/packages/e2e-tests/specs/editor/various/editor-modes.test.js index 81878ebf7208e3..aea6536f605bb6 100644 --- a/packages/e2e-tests/specs/editor/various/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor/various/editor-modes.test.js @@ -102,21 +102,24 @@ describe( 'Editing modes (visual/HTML)', () => { expect( title ).toBe( 'Paragraph' ); // The Block inspector should be active. - let blockInspectorTab = await page.$( - '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' + let [ blockInspectorTab ] = await page.$x( + '//button[@role="tab"][@aria-selected="true"][contains(text(), "Block")]' ); expect( blockInspectorTab ).not.toBeNull(); await switchEditorModeTo( 'Code' ); // The Block inspector should not be active anymore. - blockInspectorTab = await page.$( - '.edit-post-sidebar__panel-tab.is-active[data-label="Block"]' + [ blockInspectorTab ] = await page.$x( + '//button[@role="tab"][@aria-selected="true"][contains(text(), "Block")]' ); - expect( blockInspectorTab ).toBeNull(); + expect( blockInspectorTab ).toBeUndefined(); // No block is selected. - await page.click( '.edit-post-sidebar__panel-tab[data-label="Block"]' ); + const inactiveBlockInspectorTab = await page.waitForXPath( + '//button[@role="tab"][contains(text(), "Block")]' + ); + inactiveBlockInspectorTab.click(); const noBlocksElement = await page.$( '.block-editor-block-inspector__no-blocks' ); diff --git a/packages/e2e-tests/specs/editor/various/preferences.test.js b/packages/e2e-tests/specs/editor/various/preferences.test.js index 98249637c7e968..54990a4004422e 100644 --- a/packages/e2e-tests/specs/editor/various/preferences.test.js +++ b/packages/e2e-tests/specs/editor/various/preferences.test.js @@ -17,7 +17,7 @@ describe( 'preferences', () => { async function getActiveSidebarTabText() { try { return await page.$eval( - '.edit-post-sidebar__panel-tab.is-active', + 'div[aria-label="Editor settings"] [role="tab"][aria-selected="true"]', ( node ) => node.textContent ); } catch ( error ) { @@ -29,11 +29,15 @@ describe( 'preferences', () => { } it( 'remembers sidebar dismissal between sessions', async () => { + const blockTab = await page.waitForXPath( + `//button[@role="tab"][contains(text(), 'Block')]` + ); + // Open by default. expect( await getActiveSidebarTabText() ).toBe( 'Post' ); // Change to "Block" tab. - await page.click( '.edit-post-sidebar__panel-tab[aria-label="Block"]' ); + await blockTab.click(); expect( await getActiveSidebarTabText() ).toBe( 'Block' ); // Regression test: Reload resets to document tab. @@ -46,7 +50,7 @@ describe( 'preferences', () => { // Dismiss. await page.click( - '.edit-post-sidebar__panel-tabs [aria-label="Close Settings"]' + 'div[aria-label="Editor settings"] div[role="tablist"] + button[aria-label="Close Settings"]' ); expect( await getActiveSidebarTabText() ).toBe( null ); diff --git a/packages/e2e-tests/specs/editor/various/sidebar.test.js b/packages/e2e-tests/specs/editor/various/sidebar.test.js index 2e5d46eec2f7a2..0cd39093aabb8c 100644 --- a/packages/e2e-tests/specs/editor/various/sidebar.test.js +++ b/packages/e2e-tests/specs/editor/various/sidebar.test.js @@ -13,7 +13,8 @@ import { } from '@wordpress/e2e-test-utils'; const SIDEBAR_SELECTOR = '.edit-post-sidebar'; -const ACTIVE_SIDEBAR_TAB_SELECTOR = '.edit-post-sidebar__panel-tab.is-active'; +const ACTIVE_SIDEBAR_TAB_SELECTOR = + 'div[aria-label="Editor settings"] [role="tab"][aria-selected="true"]'; const ACTIVE_SIDEBAR_BUTTON_TEXT = 'Post'; describe( 'Sidebar', () => { @@ -99,22 +100,24 @@ describe( 'Sidebar', () => { // Tab lands at first (presumed selected) option "Post". await page.keyboard.press( 'Tab' ); - const isActiveDocumentTab = await page.evaluate( - () => - document.activeElement.textContent === 'Post' && - document.activeElement.classList.contains( 'is-active' ) + + // The Post tab should be focused and selected. + const [ documentInspectorTab ] = await page.$x( + '//button[@role="tab"][@aria-selected="true"][contains(text(), "Post")]' ); - expect( isActiveDocumentTab ).toBe( true ); + expect( documentInspectorTab ).toBeDefined(); + expect( documentInspectorTab ).toHaveFocus(); - // Tab into and activate "Block". - await page.keyboard.press( 'Tab' ); + // Arrow key into and activate "Block". + await page.keyboard.press( 'ArrowRight' ); await page.keyboard.press( 'Space' ); - const isActiveBlockTab = await page.evaluate( - () => - document.activeElement.textContent === 'Block' && - document.activeElement.classList.contains( 'is-active' ) + + // The Block tab should be focused and selected. + const [ blockInspectorTab ] = await page.$x( + '//button[@role="tab"][@aria-selected="true"][contains(text(), "Block")]' ); - expect( isActiveBlockTab ).toBe( true ); + expect( blockInspectorTab ).toBeDefined(); + expect( blockInspectorTab ).toHaveFocus(); } ); it( 'should be possible to programmatically remove Document Settings panels', async () => { diff --git a/packages/edit-post/src/components/sidebar/settings-header/index.js b/packages/edit-post/src/components/sidebar/settings-header/index.js index ef32450e7209fd..368bd3e9e50dbd 100644 --- a/packages/edit-post/src/components/sidebar/settings-header/index.js +++ b/packages/edit-post/src/components/sidebar/settings-header/index.js @@ -1,22 +1,20 @@ /** * WordPress dependencies */ -import { Button } from '@wordpress/components'; -import { __, _x, sprintf } from '@wordpress/i18n'; -import { useDispatch, useSelect } from '@wordpress/data'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; +import { __, _x } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies */ -import { store as editPostStore } from '../../../store'; +import { unlock } from '../../../lock-unlock'; +import { sidebars } from '../settings-sidebar'; -const SettingsHeader = ( { sidebarName } ) => { - const { openGeneralSidebar } = useDispatch( editPostStore ); - const openDocumentSettings = () => - openGeneralSidebar( 'edit-post/document' ); - const openBlockSettings = () => openGeneralSidebar( 'edit-post/block' ); +const { Tabs } = unlock( componentsPrivateApis ); +const SettingsHeader = () => { const { documentLabel, isTemplateMode } = useSelect( ( select ) => { const { getPostTypeLabel, getRenderingMode } = select( editorStore ); @@ -27,66 +25,16 @@ const SettingsHeader = ( { sidebarName } ) => { }; }, [] ); - const [ documentAriaLabel, documentActiveClass ] = - sidebarName === 'edit-post/document' - ? // translators: ARIA label for the Document sidebar tab, selected. %s: Document label. - [ sprintf( __( '%s (selected)' ), documentLabel ), 'is-active' ] - : [ documentLabel, '' ]; - - const [ blockAriaLabel, blockActiveClass ] = - sidebarName === 'edit-post/block' - ? // translators: ARIA label for the Block Settings Sidebar tab, selected. - [ __( 'Block (selected)' ), 'is-active' ] - : // translators: ARIA label for the Block Settings Sidebar tab, not selected. - [ __( 'Block' ), '' ]; - - const [ templateAriaLabel, templateActiveClass ] = - sidebarName === 'edit-post/document' - ? [ __( 'Template (selected)' ), 'is-active' ] - : [ __( 'Template' ), '' ]; - - /* Use a list so screen readers will announce how many tabs there are. */ return ( -
    - { ! isTemplateMode && ( -
  • - -
  • - ) } - { isTemplateMode && ( -
  • - -
  • - ) } -
  • - -
  • -
+ + + { isTemplateMode ? __( 'Template' ) : documentLabel } + + + { /* translators: Text label for the Block Settings Sidebar tab. */ } + { __( 'Block' ) } + + ); }; diff --git a/packages/edit-post/src/components/sidebar/settings-header/style.scss b/packages/edit-post/src/components/sidebar/settings-header/style.scss deleted file mode 100644 index aaf7698cb6ddb6..00000000000000 --- a/packages/edit-post/src/components/sidebar/settings-header/style.scss +++ /dev/null @@ -1,74 +0,0 @@ -// This tab style CSS is duplicated verbatim in -// /packages/components/src/tab-panel/style.scss -.components-button.edit-post-sidebar__panel-tab { - position: relative; - border-radius: 0; - height: $grid-unit-60; - background: transparent; - border: none; - box-shadow: none; - cursor: pointer; - padding: 3px $grid-unit-20; // Use padding to offset the is-active border, this benefits Windows High Contrast mode - margin-left: 0; - font-weight: 500; - - &:focus:not(:disabled) { - position: relative; - box-shadow: none; - outline: none; - } - - // Tab indicator - &::after { - content: ""; - position: absolute; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - - // Draw the indicator. - background: var(--wp-admin-theme-color); - height: calc(0 * var(--wp-admin-border-width-focus)); - border-radius: 0; - - // Animation - transition: all 0.1s linear; - @include reduce-motion("transition"); - } - - // Active. - &.is-active::after { - height: calc(1 * var(--wp-admin-border-width-focus)); - - // Windows high contrast mode. - outline: 2px solid transparent; - outline-offset: -1px; - } - - // Focus. - &::before { - content: ""; - position: absolute; - top: $grid-unit-15; - right: $grid-unit-15; - bottom: $grid-unit-15; - left: $grid-unit-15; - pointer-events: none; - - // Draw the indicator. - box-shadow: 0 0 0 0 transparent; - border-radius: $radius-block-ui; - - // Animation - transition: all 0.1s linear; - @include reduce-motion("transition"); - } - - &:focus-visible::before { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - - // Windows high contrast mode. - outline: 2px solid transparent; - } -} diff --git a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js index e566ea400c12b1..9fa27c6ac2adeb 100644 --- a/packages/edit-post/src/components/sidebar/settings-sidebar/index.js +++ b/packages/edit-post/src/components/sidebar/settings-sidebar/index.js @@ -5,8 +5,8 @@ import { BlockInspector, store as blockEditorStore, } from '@wordpress/block-editor'; -import { useSelect } from '@wordpress/data'; -import { Platform } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { Platform, useCallback, useContext } from '@wordpress/element'; import { isRTL, __ } from '@wordpress/i18n'; import { drawerLeft, drawerRight } from '@wordpress/icons'; import { store as interfaceStore } from '@wordpress/interface'; @@ -29,54 +29,43 @@ import PluginDocumentSettingPanel from '../plugin-document-setting-panel'; import PluginSidebarEditPost from '../plugin-sidebar'; import TemplateSummary from '../template-summary'; import { store as editPostStore } from '../../../store'; +import { privateApis as componentsPrivateApis } from '@wordpress/components'; +import { unlock } from '../../../lock-unlock'; + +const { Tabs } = unlock( componentsPrivateApis ); const SIDEBAR_ACTIVE_BY_DEFAULT = Platform.select( { web: true, native: false, } ); +export const sidebars = { + document: 'edit-post/document', + block: 'edit-post/block', +}; -const SettingsSidebar = () => { - const { sidebarName, keyboardShortcut, isTemplateMode } = useSelect( - ( select ) => { - // The settings sidebar is used by the edit-post/document and edit-post/block sidebars. - // sidebarName represents the sidebar that is active or that should be active when the SettingsSidebar toggle button is pressed. - // If one of the two sidebars is active the component will contain the content of that sidebar. - // When neither of the two sidebars is active we can not simply return null, because the PluginSidebarEditPost - // component, besides being used to render the sidebar, also renders the toggle button. In that case sidebarName - // should contain the sidebar that will be active when the toggle button is pressed. If a block - // is selected, that should be edit-post/block otherwise it's edit-post/document. - let sidebar = select( interfaceStore ).getActiveComplementaryArea( - editPostStore.name - ); - if ( - ! [ 'edit-post/document', 'edit-post/block' ].includes( - sidebar - ) - ) { - if ( select( blockEditorStore ).getBlockSelectionStart() ) { - sidebar = 'edit-post/block'; - } - sidebar = 'edit-post/document'; - } - const shortcut = select( - keyboardShortcutsStore - ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ); - return { - sidebarName: sidebar, - keyboardShortcut: shortcut, - isTemplateMode: - select( editorStore ).getRenderingMode() === - 'template-only', - }; - }, - [] - ); +const SidebarContent = ( { + sidebarName, + keyboardShortcut, + isTemplateMode, +} ) => { + // Because `PluginSidebarEditPost` renders a `ComplementaryArea`, we + // need to forward the `Tabs` context so it can be passed through the + // underlying slot/fill. + const tabsContextValue = useContext( Tabs.Context ); return ( } + header={ + + + + } closeLabel={ __( 'Close Settings' ) } + // This classname is added so we can apply a corrective negative + // margin to the panel. + // see https://github.com/WordPress/gutenberg/pull/55360#pullrequestreview-1737671049 + className="edit-post-sidebar__panel" headerClassName="edit-post-sidebar__panel-tabs" /* translators: button label text should, if possible, be under 16 characters. */ title={ __( 'Settings' ) } @@ -84,25 +73,96 @@ const SettingsSidebar = () => { icon={ isRTL() ? drawerLeft : drawerRight } isActiveByDefault={ SIDEBAR_ACTIVE_BY_DEFAULT } > - { ! isTemplateMode && sidebarName === 'edit-post/document' && ( - <> - - - - - - - - - - - ) } - { isTemplateMode && sidebarName === 'edit-post/document' && ( - - ) } - { sidebarName === 'edit-post/block' && } + + + { ! isTemplateMode && ( + <> + + + + + + + + + + + ) } + { isTemplateMode && } + + + + + ); }; +const SettingsSidebar = () => { + const { + sidebarName, + isSettingsSidebarActive, + keyboardShortcut, + isTemplateMode, + } = useSelect( ( select ) => { + // The settings sidebar is used by the edit-post/document and edit-post/block sidebars. + // sidebarName represents the sidebar that is active or that should be active when the SettingsSidebar toggle button is pressed. + // If one of the two sidebars is active the component will contain the content of that sidebar. + // When neither of the two sidebars is active we can not simply return null, because the PluginSidebarEditPost + // component, besides being used to render the sidebar, also renders the toggle button. In that case sidebarName + // should contain the sidebar that will be active when the toggle button is pressed. If a block + // is selected, that should be edit-post/block otherwise it's edit-post/document. + let sidebar = select( interfaceStore ).getActiveComplementaryArea( + editPostStore.name + ); + let isSettingsSidebar = true; + if ( ! [ sidebars.document, sidebars.block ].includes( sidebar ) ) { + isSettingsSidebar = false; + if ( select( blockEditorStore ).getBlockSelectionStart() ) { + sidebar = sidebars.block; + } + sidebar = sidebars.document; + } + const shortcut = select( + keyboardShortcutsStore + ).getShortcutRepresentation( 'core/edit-post/toggle-sidebar' ); + return { + sidebarName: sidebar, + isSettingsSidebarActive: isSettingsSidebar, + keyboardShortcut: shortcut, + isTemplateMode: + select( editorStore ).getRenderingMode() === 'template-only', + }; + }, [] ); + + const { openGeneralSidebar } = useDispatch( editPostStore ); + + const onTabSelect = useCallback( + ( newSelectedTabId ) => { + if ( !! newSelectedTabId ) { + openGeneralSidebar( newSelectedTabId ); + } + }, + [ openGeneralSidebar ] + ); + + return ( + + + + ); +}; + export default SettingsSidebar; diff --git a/packages/edit-post/src/components/sidebar/style.scss b/packages/edit-post/src/components/sidebar/style.scss index 7b10eaec0d2248..1921c5cfd7b312 100644 --- a/packages/edit-post/src/components/sidebar/style.scss +++ b/packages/edit-post/src/components/sidebar/style.scss @@ -1,20 +1,8 @@ .components-panel__header.edit-post-sidebar__panel-tabs { - justify-content: flex-start; padding-left: 0; padding-right: $grid-unit-20; - border-top: 0; - margin-top: 0; - - ul { - display: flex; - } - li { - margin: 0; - } .components-button.has-icon { - display: none; - margin: 0 0 0 auto; padding: 0; min-width: $icon-size; height: $icon-size; @@ -24,3 +12,7 @@ } } } + +.edit-post-sidebar__panel { + margin-top: -1px; +} diff --git a/packages/edit-post/src/style.scss b/packages/edit-post/src/style.scss index 53219bc6a37368..88916bf70f76d3 100644 --- a/packages/edit-post/src/style.scss +++ b/packages/edit-post/src/style.scss @@ -12,7 +12,6 @@ @import "./components/sidebar/post-format/style.scss"; @import "./components/sidebar/post-slug/style.scss"; @import "./components/sidebar/post-visibility/style.scss"; -@import "./components/sidebar/settings-header/style.scss"; @import "./components/sidebar/template-summary/style.scss"; @import "./components/text-editor/style.scss"; @import "./components/visual-editor/style.scss"; diff --git a/test/e2e/specs/editor/plugins/custom-post-types.spec.js b/test/e2e/specs/editor/plugins/custom-post-types.spec.js index 17a497f26cee02..01dde03650ef73 100644 --- a/test/e2e/specs/editor/plugins/custom-post-types.spec.js +++ b/test/e2e/specs/editor/plugins/custom-post-types.spec.js @@ -31,7 +31,7 @@ test.describe( 'Test Custom Post Types', () => { await editor.openDocumentSettingsSidebar(); await page .getByRole( 'region', { name: 'Editor settings' } ) - .getByRole( 'button', { + .getByRole( 'tab', { name: 'Hierarchical No Title', } ) .click(); diff --git a/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js b/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js index f0bfe5bff203fb..a695b0a9ead67e 100644 --- a/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js +++ b/test/e2e/specs/editor/various/block-hierarchy-navigation.spec.js @@ -127,7 +127,7 @@ test.describe( 'Navigating the block hierarchy', () => { await pageUtils.pressKeys( 'ctrl+`' ); // Navigate to the block settings sidebar and tweak the column count. - await pageUtils.pressKeys( 'Tab', { times: 5 } ); + await pageUtils.pressKeys( 'Tab', { times: 4 } ); await expect( page.getByRole( 'slider', { name: 'Columns' } ) ).toBeFocused(); diff --git a/test/e2e/specs/editor/various/footnotes.spec.js b/test/e2e/specs/editor/various/footnotes.spec.js index 14a2fc653e3873..6102f48749543f 100644 --- a/test/e2e/specs/editor/various/footnotes.spec.js +++ b/test/e2e/specs/editor/various/footnotes.spec.js @@ -362,7 +362,7 @@ test.describe( 'Footnotes', () => { await editor.openDocumentSettingsSidebar(); await page .getByRole( 'region', { name: 'Editor settings' } ) - .getByRole( 'button', { name: 'Post' } ) + .getByRole( 'tab', { name: 'Post' } ) .click(); await page.locator( 'a:text("2 Revisions")' ).click(); await page.locator( '.revisions-controls .ui-slider-handle' ).focus(); @@ -440,7 +440,7 @@ test.describe( 'Footnotes', () => { await editor.openDocumentSettingsSidebar(); await page .getByRole( 'region', { name: 'Editor settings' } ) - .getByRole( 'button', { name: 'Post' } ) + .getByRole( 'tab', { name: 'Post' } ) .click(); // Visit the published post. diff --git a/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js b/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js index 080abe011206a7..84536c88227ce9 100644 --- a/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js +++ b/test/e2e/specs/editor/various/keyboard-navigable-blocks.spec.js @@ -75,9 +75,7 @@ test.describe( 'Order of block keyboard navigation', () => { ); await page.keyboard.press( 'Tab' ); - await KeyboardNavigableBlocks.expectLabelToHaveFocus( - 'Post (selected)' - ); + await KeyboardNavigableBlocks.expectLabelToHaveFocus( 'Post' ); } ); test( 'allows tabbing in navigation mode if no block is selected (reverse)', async ( { @@ -151,7 +149,7 @@ test.describe( 'Order of block keyboard navigation', () => { ); await page.keyboard.press( 'Tab' ); - await KeyboardNavigableBlocks.expectLabelToHaveFocus( 'Post' ); + await KeyboardNavigableBlocks.expectLabelToHaveFocus( 'Block' ); await pageUtils.pressKeys( 'shift+Tab' ); await KeyboardNavigableBlocks.expectLabelToHaveFocus( @@ -233,7 +231,7 @@ class KeyboardNavigableBlocks { await expect( activeElement ).toHaveText( paragraphText ); await this.page.keyboard.press( 'Tab' ); - await this.expectLabelToHaveFocus( 'Post' ); + await this.expectLabelToHaveFocus( 'Block' ); // Need to shift+tab here to end back in the block. If not, we'll be in the next region and it will only require 4 region jumps instead of 5. await this.pageUtils.pressKeys( 'shift+Tab' ); From b149b9647f29fe57ab4feb04545c7e99cf61faa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9?= <583546+oandregal@users.noreply.github.com> Date: Tue, 12 Dec 2023 20:57:14 +0100 Subject: [PATCH 07/17] Fix e2e test (#56992) --- test/e2e/specs/site-editor/new-templates-list.spec.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/e2e/specs/site-editor/new-templates-list.spec.js b/test/e2e/specs/site-editor/new-templates-list.spec.js index 8bb98b6ad5355a..34daeb6d40f09b 100644 --- a/test/e2e/specs/site-editor/new-templates-list.spec.js +++ b/test/e2e/specs/site-editor/new-templates-list.spec.js @@ -33,10 +33,7 @@ test.describe( 'Templates', () => { name: 'Template', includeHidden: true, } ) - .getByRole( 'heading', { - level: 3, - includeHidden: true, - } ) + .getByRole( 'link', { includeHidden: true } ) .first(); await expect( firstTitle ).toHaveText( 'Tag Archives' ); // Ascending by title. @@ -57,7 +54,7 @@ test.describe( 'Templates', () => { await page.keyboard.type( 'tag' ); const titles = page .getByRole( 'region', { name: 'Template' } ) - .getByRole( 'heading', { level: 3 } ); + .getByRole( 'link' ); await expect( titles ).toHaveCount( 1 ); await expect( titles.first() ).toHaveText( 'Tag Archives' ); await page.getByRole( 'button', { name: 'Reset filters' } ).click(); From 4d61a94e0622eed5e45529a7fcb9a95cd09a61a7 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:54:27 +1100 Subject: [PATCH 08/17] Allow dragging between adjacent container blocks based on a threshold (#56466) * Try: Allow drag between blocks based on a threshold * Update insertion point to reflect threshold * Fix threshold for Group block by passing in dropZoneElement * Simplify by reusing operation option * Re-use logic that factors in the dragged blocks if we are ultimately dropping at the same level * Simplify a little further * Factor in orientation * Add minimum height for threshold so that short blocks are still usable * Allow threshold when parent block is horizontal, i.e. a Row block --- .../components/use-block-drop-zone/index.js | 128 ++++++++++++++++-- .../src/components/use-on-block-drop/index.js | 3 +- packages/block-library/src/group/edit.js | 5 +- 3 files changed, 120 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/use-block-drop-zone/index.js b/packages/block-editor/src/components/use-block-drop-zone/index.js index 25dc6ee408982f..cb3c3ae6a28a3d 100644 --- a/packages/block-editor/src/components/use-block-drop-zone/index.js +++ b/packages/block-editor/src/components/use-block-drop-zone/index.js @@ -20,6 +20,10 @@ import { } from '../../utils/math'; import { store as blockEditorStore } from '../../store'; +const THRESHOLD_DISTANCE = 30; +const MINIMUM_HEIGHT_FOR_THRESHOLD = 120; +const MINIMUM_WIDTH_FOR_THRESHOLD = 120; + /** @typedef {import('../../utils/math').WPPoint} WPPoint */ /** @typedef {import('../use-on-block-drop/types').WPDropOperation} WPDropOperation */ @@ -48,24 +52,86 @@ import { store as blockEditorStore } from '../../store'; * @param {WPBlockData[]} blocksData The block data list. * @param {WPPoint} position The position of the item being dragged. * @param {WPBlockListOrientation} orientation The orientation of the block list. + * @param {Object} options Additional options. * @return {[number, WPDropOperation]} The drop target position. */ export function getDropTargetPosition( blocksData, position, - orientation = 'vertical' + orientation = 'vertical', + options = {} ) { const allowedEdges = orientation === 'horizontal' ? [ 'left', 'right' ] : [ 'top', 'bottom' ]; - const isRightToLeft = isRTL(); - let nearestIndex = 0; let insertPosition = 'before'; let minDistance = Infinity; + const { + dropZoneElement, + parentBlockOrientation, + rootBlockIndex = 0, + } = options; + + // Allow before/after when dragging over the top/bottom edges of the drop zone. + if ( dropZoneElement && parentBlockOrientation !== 'horizontal' ) { + const rect = dropZoneElement.getBoundingClientRect(); + const [ distance, edge ] = getDistanceToNearestEdge( position, rect, [ + 'top', + 'bottom', + ] ); + + // If dragging over the top or bottom of the drop zone, insert the block + // before or after the parent block. This only applies to blocks that use + // a drop zone element, typically container blocks such as Group or Cover. + if ( + rect.height > MINIMUM_HEIGHT_FOR_THRESHOLD && + distance < THRESHOLD_DISTANCE + ) { + if ( edge === 'top' ) { + return [ rootBlockIndex, 'before' ]; + } + if ( edge === 'bottom' ) { + return [ rootBlockIndex + 1, 'after' ]; + } + } + } + + const isRightToLeft = isRTL(); + + // Allow before/after when dragging over the left/right edges of the drop zone. + if ( dropZoneElement && parentBlockOrientation === 'horizontal' ) { + const rect = dropZoneElement.getBoundingClientRect(); + const [ distance, edge ] = getDistanceToNearestEdge( position, rect, [ + 'left', + 'right', + ] ); + + // If dragging over the left or right of the drop zone, insert the block + // before or after the parent block. This only applies to blocks that use + // a drop zone element, typically container blocks such as Group. + if ( + rect.width > MINIMUM_WIDTH_FOR_THRESHOLD && + distance < THRESHOLD_DISTANCE + ) { + if ( + ( isRightToLeft && edge === 'right' ) || + ( ! isRightToLeft && edge === 'left' ) + ) { + return [ rootBlockIndex, 'before' ]; + } + if ( + ( isRightToLeft && edge === 'left' ) || + ( ! isRightToLeft && edge === 'right' ) + ) { + return [ rootBlockIndex + 1, 'after' ]; + } + } + } + blocksData.forEach( ( { isUnmodifiedDefaultBlock, getBoundingClientRect, blockIndex } ) => { const rect = getBoundingClientRect(); @@ -150,19 +216,27 @@ export default function useBlockDropZone( { operation: 'insert', } ); - const isDisabled = useSelect( + const { isDisabled, parentBlockClientId, rootBlockIndex } = useSelect( ( select ) => { const { __unstableIsWithinBlockOverlay, __unstableHasActiveBlockOverlayActive, + getBlockIndex, + getBlockParents, getBlockEditingMode, } = select( blockEditorStore ); const blockEditingMode = getBlockEditingMode( targetRootClientId ); - return ( - blockEditingMode !== 'default' || - __unstableHasActiveBlockOverlayActive( targetRootClientId ) || - __unstableIsWithinBlockOverlay( targetRootClientId ) - ); + return { + parentBlockClientId: + getBlockParents( targetRootClientId, true )[ 0 ] || '', + rootBlockIndex: getBlockIndex( targetRootClientId ), + isDisabled: + blockEditingMode !== 'default' || + __unstableHasActiveBlockOverlayActive( + targetRootClientId + ) || + __unstableIsWithinBlockOverlay( targetRootClientId ), + }; }, [ targetRootClientId ] ); @@ -172,9 +246,15 @@ export default function useBlockDropZone( { const { showInsertionPoint, hideInsertionPoint } = useDispatch( blockEditorStore ); - const onBlockDrop = useOnBlockDrop( targetRootClientId, dropTarget.index, { - operation: dropTarget.operation, - } ); + const onBlockDrop = useOnBlockDrop( + dropTarget.operation === 'before' || dropTarget.operation === 'after' + ? parentBlockClientId + : targetRootClientId, + dropTarget.index, + { + operation: dropTarget.operation, + } + ); const throttled = useThrottle( useCallback( ( event, ownerDocument ) => { @@ -211,7 +291,16 @@ export default function useBlockDropZone( { const [ targetIndex, operation ] = getDropTargetPosition( blocksData, { x: event.clientX, y: event.clientY }, - getBlockListSettings( targetRootClientId )?.orientation + getBlockListSettings( targetRootClientId )?.orientation, + { + dropZoneElement, + parentBlockClientId, + parentBlockOrientation: parentBlockClientId + ? getBlockListSettings( parentBlockClientId ) + ?.orientation + : undefined, + rootBlockIndex, + } ); registry.batch( () => { @@ -219,18 +308,29 @@ export default function useBlockDropZone( { index: targetIndex, operation, } ); - showInsertionPoint( targetRootClientId, targetIndex, { + + const insertionPointClientId = [ + 'before', + 'after', + ].includes( operation ) + ? parentBlockClientId + : targetRootClientId; + + showInsertionPoint( insertionPointClientId, targetIndex, { operation, } ); } ); }, [ + dropZoneElement, getBlocks, targetRootClientId, getBlockListSettings, registry, showInsertionPoint, getBlockIndex, + parentBlockClientId, + rootBlockIndex, ] ), 200 diff --git a/packages/block-editor/src/components/use-on-block-drop/index.js b/packages/block-editor/src/components/use-on-block-drop/index.js index 72ea6a698c3439..ab0da8ad99e2ab 100644 --- a/packages/block-editor/src/components/use-on-block-drop/index.js +++ b/packages/block-editor/src/components/use-on-block-drop/index.js @@ -292,9 +292,10 @@ export default function useOnBlockDrop( operation, getBlockOrder, getBlocksByClientId, - insertBlocks, moveBlocksToPosition, + registry, removeBlocks, + replaceBlocks, targetBlockIndex, targetRootClientId, ] diff --git a/packages/block-library/src/group/edit.js b/packages/block-library/src/group/edit.js index 9c8690c4e0e8e2..a763bc95e60d7c 100644 --- a/packages/block-library/src/group/edit.js +++ b/packages/block-library/src/group/edit.js @@ -10,6 +10,7 @@ import { store as blockEditorStore, } from '@wordpress/block-editor'; import { SelectControl } from '@wordpress/components'; +import { useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { View } from '@wordpress/primitives'; @@ -97,7 +98,8 @@ function GroupEdit( { attributes, name, setAttributes, clientId } ) { themeSupportsLayout || type === 'flex' || type === 'grid'; // Hooks. - const blockProps = useBlockProps(); + const ref = useRef(); + const blockProps = useBlockProps( { ref } ); const [ showPlaceholder, setShowPlaceholder ] = useShouldShowPlaceHolder( { attributes, @@ -124,6 +126,7 @@ function GroupEdit( { attributes, name, setAttributes, clientId } ) { ? blockProps : { className: 'wp-block-group__inner-container' }, { + dropZoneElement: ref.current, templateLock, allowedBlocks, renderAppender, From 9a46ad1773b736eeffb54755e7cddb7e25c8462c Mon Sep 17 00:00:00 2001 From: Ramon Date: Wed, 13 Dec 2023 09:15:36 +1100 Subject: [PATCH 09/17] Global style revisions: show change summary on selected item (#56577) * Moving date format setting call into the comoponent rejigging getLabel function adding css var * Testing a message to indicate that the revisions state is the same as the editor state. * Working on change list, adding translations. WIP * Adding more translations. * Revert button-in-item for another day Reduce depth of changeset and remove unused translations Display aria-label on button instead of tooltip * Removing shuffle function and fixing up block spacing translation * Using the revision has the Map key. This allows us to cache the revision sets themselves and account for changes to Unsaved revisions. * Used WeakMap in favour of Map for garbage collection, if it helps at all * Remove hasMore var - unneeded because it's only used once * Tidying up - remove .map loop * getGlobalStylesChanges was doing nothing! Removed. * Using revision + previousRevision combo for cache key to ensure that the results are cached for the same two objects Returning from cache where maxResults value is smaller than cached results Added first tests * Moving maxResults decisions to consuming component. getRevisionChanges returns an unadulterated array. * Move get blockNames to main component * Have to use map because WeakMap wants the same reference as the object key. * Remove the trailing comma on truncated results * Test commit: listing changes, showing `and n more` * Test commit: grouping changes using tuples * Reverting back to comma-separate list of changes Added e2e assertion * Swapping order of author name and changes block Moving everything into the button so it's clickable. * Don't live in the past, man --- .../screen-revisions/get-revision-changes.js | 171 ++++++++++++++++ .../global-styles/screen-revisions/index.js | 15 +- .../screen-revisions/revisions-buttons.js | 103 ++++++++-- .../global-styles/screen-revisions/style.scss | 12 +- .../test/get-revision-changes.js | 191 ++++++++++++++++++ .../user-global-styles-revisions.spec.js | 5 + 6 files changed, 467 insertions(+), 30 deletions(-) create mode 100644 packages/edit-site/src/components/global-styles/screen-revisions/get-revision-changes.js create mode 100644 packages/edit-site/src/components/global-styles/screen-revisions/test/get-revision-changes.js diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/get-revision-changes.js b/packages/edit-site/src/components/global-styles/screen-revisions/get-revision-changes.js new file mode 100644 index 00000000000000..fed075eb923ff4 --- /dev/null +++ b/packages/edit-site/src/components/global-styles/screen-revisions/get-revision-changes.js @@ -0,0 +1,171 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; + +const globalStylesChangesCache = new Map(); +const EMPTY_ARRAY = []; + +const translationMap = { + caption: __( 'Caption' ), + link: __( 'Link' ), + button: __( 'Button' ), + heading: __( 'Heading' ), + 'settings.color': __( 'Color settings' ), + 'settings.typography': __( 'Typography settings' ), + 'styles.color': __( 'Colors' ), + 'styles.spacing': __( 'Spacing' ), + 'styles.typography': __( 'Typography' ), +}; + +const isObject = ( obj ) => obj !== null && typeof obj === 'object'; + +/** + * Get the translation for a given global styles key. + * @param {string} key A key representing a path to a global style property or setting. + * @param {Record} blockNames A key/value pair object of block names and their rendered titles. + * @return {string|undefined} A translated key or undefined if no translation exists. + */ +function getTranslation( key, blockNames ) { + if ( translationMap[ key ] ) { + return translationMap[ key ]; + } + + const keyArray = key.split( '.' ); + + if ( keyArray?.[ 0 ] === 'blocks' ) { + const blockName = blockNames[ keyArray[ 1 ] ]; + return blockName + ? sprintf( + // translators: %s: block name. + __( '%s block' ), + blockName + ) + : keyArray[ 1 ]; + } + + if ( keyArray?.[ 0 ] === 'elements' ) { + return sprintf( + // translators: %s: element name, e.g., heading button, link, caption. + __( '%s element' ), + translationMap[ keyArray[ 1 ] ] + ); + } + + return undefined; +} + +/** + * A deep comparison of two objects, optimized for comparing global styles. + * @param {Object} changedObject The changed object to compare. + * @param {Object} originalObject The original object to compare against. + * @param {string} parentPath A key/value pair object of block names and their rendered titles. + * @return {string[]} An array of paths whose values have changed. + */ +function deepCompare( changedObject, originalObject, parentPath = '' ) { + // We have two non-object values to compare. + if ( ! isObject( changedObject ) && ! isObject( originalObject ) ) { + /* + * Only return a path if the value has changed. + * And then only the path name up to 2 levels deep. + */ + return changedObject !== originalObject + ? parentPath.split( '.' ).slice( 0, 2 ).join( '.' ) + : undefined; + } + + // Enable comparison when an object doesn't have a corresponding property to compare. + changedObject = isObject( changedObject ) ? changedObject : {}; + originalObject = isObject( originalObject ) ? originalObject : {}; + + const allKeys = new Set( [ + ...Object.keys( changedObject ), + ...Object.keys( originalObject ), + ] ); + + let diffs = []; + for ( const key of allKeys ) { + const path = parentPath ? parentPath + '.' + key : key; + const changedPath = deepCompare( + changedObject[ key ], + originalObject[ key ], + path + ); + if ( changedPath ) { + diffs = diffs.concat( changedPath ); + } + } + return diffs; +} + +/** + * Get an array of translated summarized global styles changes. + * Results are cached using a Map() key of `JSON.stringify( { revision, previousRevision } )`. + * + * @param {Object} revision The changed object to compare. + * @param {Object} previousRevision The original object to compare against. + * @param {Record} blockNames A key/value pair object of block names and their rendered titles. + * @return {string[]} An array of translated changes. + */ +export default function getRevisionChanges( + revision, + previousRevision, + blockNames +) { + const cacheKey = JSON.stringify( { revision, previousRevision } ); + + if ( globalStylesChangesCache.has( cacheKey ) ) { + return globalStylesChangesCache.get( cacheKey ); + } + + /* + * Compare the two revisions with normalized keys. + * The order of these keys determines the order in which + * they'll appear in the results. + */ + const changedValueTree = deepCompare( + { + styles: { + color: revision?.styles?.color, + typography: revision?.styles?.typography, + spacing: revision?.styles?.spacing, + }, + blocks: revision?.styles?.blocks, + elements: revision?.styles?.elements, + settings: revision?.settings, + }, + { + styles: { + color: previousRevision?.styles?.color, + typography: previousRevision?.styles?.typography, + spacing: previousRevision?.styles?.spacing, + }, + blocks: previousRevision?.styles?.blocks, + elements: previousRevision?.styles?.elements, + settings: previousRevision?.settings, + } + ); + + if ( ! changedValueTree.length ) { + globalStylesChangesCache.set( cacheKey, EMPTY_ARRAY ); + return EMPTY_ARRAY; + } + + // Remove duplicate results. + const result = [ ...new Set( changedValueTree ) ] + /* + * Translate the keys. + * Remove duplicate or empty translations. + */ + .reduce( ( acc, curr ) => { + const translation = getTranslation( curr, blockNames ); + if ( translation && ! acc.includes( translation ) ) { + acc.push( translation ); + } + return acc; + }, [] ); + + globalStylesChangesCache.set( cacheKey, result ); + + return result; +} 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 90bf68e579cb7c..aa380c5a9fbd0b 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,7 +7,6 @@ import { __experimentalUseNavigator as useNavigator, __experimentalConfirmDialog as ConfirmDialog, Spinner, - __experimentalSpacer as Spacer, } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; @@ -135,7 +134,8 @@ function ScreenRevisions() { } }, [ shouldSelectFirstItem, firstRevision ] ); - // Only display load button if there is a revision to load and it is different from the current editor styles. + // Only display load button if there is a revision to load, + // and it is different from the current editor styles. const isLoadButtonEnabled = !! currentlySelectedRevisionId && ! selectedRevisionMatchesEditorStyles; const shouldShowRevisions = ! isLoading && revisions.length; @@ -156,7 +156,7 @@ function ScreenRevisions() { { isLoading && ( ) } - { shouldShowRevisions ? ( + { shouldShowRevisions && ( <> { isLoadButtonEnabled && ( @@ -215,14 +216,6 @@ function ScreenRevisions() { ) } - ) : ( - - { - // 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.' ) - } - ) } ); diff --git a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js index 2786bf6d791212..08930069425729 100644 --- a/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js +++ b/packages/edit-site/src/components/global-styles/screen-revisions/revisions-buttons.js @@ -6,28 +6,69 @@ import classnames from 'classnames'; /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; +import { __, _n, sprintf } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { dateI18n, getDate, humanTimeDiff, getSettings } from '@wordpress/date'; import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; +import { useMemo } from '@wordpress/element'; +import { getBlockTypes } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import getRevisionChanges from './get-revision-changes'; const DAY_IN_MILLISECONDS = 60 * 60 * 1000 * 24; +const MAX_CHANGES = 7; + +function ChangesSummary( { revision, previousRevision, blockNames } ) { + const changes = getRevisionChanges( + revision, + previousRevision, + blockNames + ); + const changesLength = changes.length; + + if ( ! changesLength ) { + return null; + } + + // Truncate to `n` results if necessary. + if ( changesLength > MAX_CHANGES ) { + const deleteCount = changesLength - MAX_CHANGES; + const andMoreText = sprintf( + // translators: %d: number of global styles changes that are not displayed in the UI. + _n( '…and %d more change.', '…and %d more changes.', deleteCount ), + deleteCount + ); + changes.splice( MAX_CHANGES, deleteCount, andMoreText ); + } + + return ( + + { changes.join( ', ' ) } + + ); +} /** * Returns a button label for the revision. * * @param {string|number} id A revision object. - * @param {boolean} isLatest Whether the revision is the most current. * @param {string} authorDisplayName Author name. * @param {string} formattedModifiedDate Revision modified date formatted. + * @param {boolean} areStylesEqual Whether the revision matches the current editor styles. * @return {string} Translated label. */ function getRevisionLabel( id, - isLatest, authorDisplayName, - formattedModifiedDate + formattedModifiedDate, + areStylesEqual ) { if ( 'parent' === id ) { return __( 'Reset the styles to the theme defaults' ); @@ -35,21 +76,23 @@ function getRevisionLabel( if ( 'unsaved' === id ) { return sprintf( - /* translators: %s author display name */ + /* translators: %s: author display name */ __( 'Unsaved changes by %s' ), authorDisplayName ); } - return isLatest + return areStylesEqual ? sprintf( - /* translators: %1$s author display name, %2$s: revision creation date */ - __( 'Changes saved by %1$s on %2$s (current)' ), + // translators: %1$s: author display name, %2$s: revision creation date. + __( + 'Changes saved by %1$s on %2$s. This revision matches current editor styles.' + ), authorDisplayName, formattedModifiedDate ) : sprintf( - /* translators: %1$s author display name, %2$s: revision creation date */ + // translators: %1$s: author display name, %2$s: revision creation date. __( 'Changes saved by %1$s on %2$s' ), authorDisplayName, formattedModifiedDate @@ -67,7 +110,12 @@ function getRevisionLabel( * @param {props} Component props. * @return {JSX.Element} The modal component. */ -function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { +function RevisionsButtons( { + userRevisions, + selectedRevisionId, + onChange, + canApplyRevision, +} ) { const { currentThemeName, currentUser } = useSelect( ( select ) => { const { getCurrentTheme, getCurrentUser } = select( coreStore ); const currentTheme = getCurrentTheme(); @@ -77,8 +125,15 @@ function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { currentUser: getCurrentUser(), }; }, [] ); + const blockNames = useMemo( () => { + const blockTypes = getBlockTypes(); + return blockTypes.reduce( ( accumulator, { name, title } ) => { + accumulator[ name ] = title; + return accumulator; + }, {} ); + }, [] ); const dateNowInMs = getDate().getTime(); - const { date: dateFormat, datetimeAbbreviated } = getSettings().formats; + const { datetimeAbbreviated } = getSettings().formats; return (
    { userRevisions.map( ( revision, index ) => { - const { id, isLatest, author, modified } = revision; + const { id, author, modified } = revision; const isUnsaved = 'unsaved' === id; // Unsaved changes are created by the current user. const revisionAuthor = isUnsaved ? currentUser : author; const authorDisplayName = revisionAuthor?.name || __( 'User' ); const authorAvatar = revisionAuthor?.avatar_urls?.[ '48' ]; + const isFirstItem = index === 0; const isSelected = selectedRevisionId ? selectedRevisionId === id - : index === 0; + : isFirstItem; + const areStylesEqual = ! canApplyRevision && isSelected; const isReset = 'parent' === id; const modifiedDate = getDate( modified ); const displayDate = modified && dateNowInMs - modifiedDate.getTime() > DAY_IN_MILLISECONDS - ? dateI18n( dateFormat, modifiedDate ) + ? dateI18n( datetimeAbbreviated, modifiedDate ) : humanTimeDiff( modified ); const revisionLabel = getRevisionLabel( id, - isLatest, authorDisplayName, - dateI18n( datetimeAbbreviated, modifiedDate ) + dateI18n( datetimeAbbreviated, modifiedDate ), + areStylesEqual ); return ( @@ -116,6 +173,7 @@ function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { 'edit-site-global-styles-screen-revisions__revision-item', { 'is-selected': isSelected, + 'is-active': areStylesEqual, 'is-reset': isReset, } ) } @@ -127,7 +185,7 @@ function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { onClick={ () => { onChange( revision ); } } - label={ revisionLabel } + aria-label={ revisionLabel } > { isReset ? ( @@ -150,6 +208,17 @@ function RevisionsButtons( { userRevisions, selectedRevisionId, onChange } ) { { displayDate } ) } + { isSelected && ( + + ) } { { + const revision = { + id: 10, + styles: { + typography: { + fontSize: 'var(--wp--preset--font-size--potato)', + fontStyle: 'normal', + fontWeight: '600', + lineHeight: '1.85', + fontFamily: 'var(--wp--preset--font-family--asparagus)', + }, + spacing: { + padding: { + top: '36px', + right: '89px', + bottom: '133px', + left: 'var(--wp--preset--spacing--20)', + }, + blockGap: '114px', + }, + elements: { + heading: { + typography: { + letterSpacing: '37px', + }, + }, + caption: { + color: { + text: 'var(--wp--preset--color--pineapple)', + }, + }, + }, + color: { + text: 'var(--wp--preset--color--tomato)', + }, + blocks: { + 'core/paragraph': { + color: { + text: '#000000', + }, + }, + }, + }, + settings: { + color: { + palette: { + theme: [ + { + slug: 'one', + color: 'pink', + }, + ], + }, + }, + }, + }; + const previousRevision = { + id: 9, + styles: { + typography: { + fontSize: 'var(--wp--preset--font-size--fungus)', + fontStyle: 'normal', + fontWeight: '600', + lineHeight: '1.85', + fontFamily: 'var(--wp--preset--font-family--grapes)', + }, + spacing: { + padding: { + top: '36px', + right: '89px', + bottom: '133px', + left: 'var(--wp--preset--spacing--20)', + }, + blockGap: '114px', + }, + elements: { + heading: { + typography: { + letterSpacing: '37px', + }, + }, + caption: { + typography: { + fontSize: '1.11rem', + fontStyle: 'normal', + fontWeight: '600', + }, + }, + link: { + typography: { + lineHeight: 2, + textDecoration: 'line-through', + }, + color: { + text: 'var(--wp--preset--color--egg)', + }, + }, + }, + color: { + text: 'var(--wp--preset--color--tomato)', + background: 'var(--wp--preset--color--pumpkin)', + }, + blocks: { + 'core/paragraph': { + color: { + text: '#fff', + }, + }, + }, + }, + settings: { + color: { + palette: { + theme: [ + { + slug: 'one', + color: 'blue', + }, + ], + }, + }, + }, + }; + const blockNames = { + 'core/paragraph': 'Paragraph', + }; + it( 'returns a list of changes and caches them', () => { + const resultA = getRevisionChanges( + revision, + previousRevision, + blockNames + ); + expect( resultA ).toEqual( [ + 'Colors', + 'Typography', + 'Paragraph block', + 'Caption element', + 'Link element', + 'Color settings', + ] ); + + const resultB = getRevisionChanges( + revision, + previousRevision, + blockNames + ); + + expect( resultA ).toBe( resultB ); + } ); + + it( 'skips unknown and unchanged keys', () => { + const result = getRevisionChanges( + { + styles: { + frogs: { + legs: 'green', + }, + typography: { + fontSize: '1rem', + }, + settings: { + '': { + '': 'foo', + }, + }, + }, + }, + { + styles: { + frogs: { + legs: 'yellow', + }, + typography: { + fontSize: '1rem', + }, + settings: { + '': { + '': 'bar', + }, + }, + }, + } + ); + expect( result ).toEqual( [] ); + } ); +} ); 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 2d51b5ac5014b8..a27bb28adbb911 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 @@ -55,6 +55,11 @@ test.describe( 'Global styles revisions', () => { name: /^Changes saved by /, } ); + // Shows changes made in the revision. + await expect( + page.getByTestId( 'global-styles-revision-changes' ) + ).toHaveText( 'Colors' ); + // There should be 2 revisions not including the reset to theme defaults button. await expect( revisionButtons ).toHaveCount( currentRevisions.length + 1 From 1dd952f3267200ee5fad288df26033d5fd33854c Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Tue, 12 Dec 2023 22:24:17 +0000 Subject: [PATCH 10/17] Bump plugin version to 17.2.1 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 7970dd5461fc4f..f39a0cb519c8ae 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.3 * Requires PHP: 7.0 - * Version: 17.2.0 + * Version: 17.2.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index ded852521693f6..6d9382ec9fb2f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.2.0", + "version": "17.2.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.2.0", + "version": "17.2.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 122a1368eaf1ca..580c100354a966 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.2.0", + "version": "17.2.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors", From 2936660d526a8112479d9c87a92cd6ae7a455a94 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Tue, 12 Dec 2023 22:38:23 +0000 Subject: [PATCH 11/17] Update Changelog for 17.2.1 --- changelog.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/changelog.txt b/changelog.txt index 2f7d2f02ff6291..65745b416e4bdb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,20 @@ == Changelog == += 17.2.1 = + +## Changelog + +### Bug Fixes + +- Fix: Fatal php error if a template was created by an author that was deleted ([56990](https://github.com/WordPress/gutenberg/pull/56990)) + +## Contributors + +The following contributors merged PRs in this release: + +@jorgefilipecosta + + = 17.2.0 = From e07802b3d2395c4563c6e8a8013bb2224e0927d7 Mon Sep 17 00:00:00 2001 From: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Date: Wed, 13 Dec 2023 11:49:41 +0900 Subject: [PATCH 12/17] Editor Canvas: Fix animation when device type changes (#56970) * Editor Canvas: Fix animation when device type changes * Include margin in deviceStyles --- .../block-editor/src/components/use-resize-canvas/README.md | 2 +- .../block-editor/src/components/use-resize-canvas/index.js | 5 ++++- packages/editor/src/components/editor-canvas/index.js | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/use-resize-canvas/README.md b/packages/block-editor/src/components/use-resize-canvas/README.md index 51e583f8def474..ce8f06adea5d82 100644 --- a/packages/block-editor/src/components/use-resize-canvas/README.md +++ b/packages/block-editor/src/components/use-resize-canvas/README.md @@ -1,6 +1,6 @@ # useResizeCanvas -This React hook generates inline CSS suitable for resizing a container to fit a device's dimensions. It adjusts the CSS according to the current device dimensions. It has no effect on desktop. +This React hook generates inline CSS suitable for resizing a container to fit a device's dimensions. It adjusts the CSS according to the current device dimensions. On-page CSS media queries are also updated to match the width of the device. diff --git a/packages/block-editor/src/components/use-resize-canvas/index.js b/packages/block-editor/src/components/use-resize-canvas/index.js index fab0b7a15e2afd..a843f160056367 100644 --- a/packages/block-editor/src/components/use-resize-canvas/index.js +++ b/packages/block-editor/src/components/use-resize-canvas/index.js @@ -67,7 +67,10 @@ export default function useResizeCanvas( deviceType ) { overflowY: 'auto', }; default: - return null; + return { + marginLeft: marginHorizontal, + marginRight: marginHorizontal, + }; } }; diff --git a/packages/editor/src/components/editor-canvas/index.js b/packages/editor/src/components/editor-canvas/index.js index 921f3ce23c0ee4..1f74dfd262ff54 100644 --- a/packages/editor/src/components/editor-canvas/index.js +++ b/packages/editor/src/components/editor-canvas/index.js @@ -304,7 +304,10 @@ function EditorCanvas( height="100%" iframeProps={ { ...iframeProps, - style: { ...iframeProps?.style, ...deviceStyles }, + style: { + ...iframeProps?.style, + ...deviceStyles, + }, } } > { themeSupportsLayout && From 9855f1629143b107abb3a07a8ca689cc11b28fd2 Mon Sep 17 00:00:00 2001 From: Andrew Serong <14988353+andrewserong@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:11:36 +1100 Subject: [PATCH 13/17] Background image support: Remove double output of styling rules (#56997) --- lib/block-supports/background.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php index b4779b1a150e46..ab2fa84361fc20 100644 --- a/lib/block-supports/background.php +++ b/lib/block-supports/background.php @@ -103,4 +103,7 @@ function gutenberg_render_background_support( $block_content, $block ) { ) ); +if ( function_exists( 'wp_render_background_support' ) ) { + remove_filter( 'render_block', 'wp_render_background_support' ); +} add_filter( 'render_block', 'gutenberg_render_background_support', 10, 2 ); From 4fb8952c4ed1e8a918241bf4b1dabf623d9eb166 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 13 Dec 2023 08:54:29 +0200 Subject: [PATCH 14/17] Editor: Fix display of edit template blocks notification (#56978) --- .../editor-canvas/edit-template-blocks-notification.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js b/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js index 047ca6688ff021..566311e20cadc2 100644 --- a/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js +++ b/packages/editor/src/components/editor-canvas/edit-template-blocks-notification.js @@ -42,7 +42,7 @@ export default function EditTemplateBlocksNotification( { contentRef } ) { useEffect( () => { const handleClick = async ( event ) => { - if ( renderingMode === 'template-only' ) { + if ( renderingMode !== 'template-locked' ) { return; } if ( ! event.target.classList.contains( 'is-root-container' ) ) { @@ -71,7 +71,7 @@ export default function EditTemplateBlocksNotification( { contentRef } ) { }; const handleDblClick = ( event ) => { - if ( renderingMode === 'template-only' ) { + if ( renderingMode !== 'template-locked' ) { return; } if ( ! event.target.classList.contains( 'is-root-container' ) ) { From 77a8b55446c7a92ebdd430f2f09eaa804c32f66b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 13 Dec 2023 10:15:14 +0100 Subject: [PATCH 15/17] Framework: Bundle the BlockTools component within BlockCanvas (#56996) --- packages/block-editor/README.md | 5 +- .../src/components/block-canvas/index.js | 48 ++++++++++++------- .../components/sidebar-block-editor/index.js | 17 +++---- .../src/components/visual-editor/index.js | 11 ++--- .../components/block-editor/editor-canvas.js | 9 +--- .../block-editor/site-editor-canvas.js | 19 ++------ .../src/components/editor-canvas/index.js | 28 +++++------ platform-docs/docs/basic-concepts/ui.md | 2 +- storybook/stories/playground/box/index.js | 2 - .../stories/playground/fullpage/index.js | 5 +- .../playground/with-undo-redo/index.js | 2 - .../helpers/integration-test-editor.js | 5 +- 12 files changed, 62 insertions(+), 91 deletions(-) diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 56ab5f1bd94d93..6c39b5dcc44b46 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -19,7 +19,6 @@ import { useState } from 'react'; import { BlockEditorProvider, BlockList, - BlockTools, WritingFlow, } from '@wordpress/block-editor'; @@ -32,9 +31,7 @@ function MyEditorComponent() { onInput={ ( blocks ) => updateBlocks( blocks ) } onChange={ ( blocks ) => updateBlocks( blocks ) } > - - - + ); } diff --git a/packages/block-editor/src/components/block-canvas/index.js b/packages/block-editor/src/components/block-canvas/index.js index 97aec461df7d86..7d64897690721c 100644 --- a/packages/block-editor/src/components/block-canvas/index.js +++ b/packages/block-editor/src/components/block-canvas/index.js @@ -2,11 +2,13 @@ * WordPress dependencies */ import { useMergeRefs } from '@wordpress/compose'; +import { useRef } from '@wordpress/element'; /** * Internal dependencies */ import BlockList from '../block-list'; +import BlockTools from '../block-tools'; import EditorStyles from '../editor-styles'; import Iframe from '../iframe'; import WritingFlow from '../writing-flow'; @@ -23,11 +25,15 @@ export function ExperimentalBlockCanvas( { } ) { const resetTypingRef = useMouseMoveTypingReset(); const clearerRef = useBlockSelectionClearer(); - const contentRef = useMergeRefs( [ contentRefProp, clearerRef ] ); + const localRef = useRef(); + const contentRef = useMergeRefs( [ contentRefProp, clearerRef, localRef ] ); if ( ! shouldIframe ) { return ( - <> + { children } - + ); } return ( - + + ); } diff --git a/packages/customize-widgets/src/components/sidebar-block-editor/index.js b/packages/customize-widgets/src/components/sidebar-block-editor/index.js index c2e10bca16ec0b..80deb12dfcf74d 100644 --- a/packages/customize-widgets/src/components/sidebar-block-editor/index.js +++ b/packages/customize-widgets/src/components/sidebar-block-editor/index.js @@ -8,7 +8,6 @@ import { useMemo, createPortal } from '@wordpress/element'; import { BlockList, BlockToolbar, - BlockTools, BlockInspector, privateApis as blockEditorPrivateApis, __unstableBlockSettingsMenuFirstItem, @@ -120,15 +119,13 @@ export default function SidebarBlockEditor( { { ( isFixedToolbarActive || ! isMediumViewport ) && ( ) } - - - - - + + + { createPortal( // This is a temporary hack to prevent button component inside diff --git a/packages/edit-post/src/components/visual-editor/index.js b/packages/edit-post/src/components/visual-editor/index.js index b929e03bc453a4..fd9b4a6ff8bb6c 100644 --- a/packages/edit-post/src/components/visual-editor/index.js +++ b/packages/edit-post/src/components/visual-editor/index.js @@ -10,8 +10,7 @@ import { store as editorStore, privateApis as editorPrivateApis, } from '@wordpress/editor'; -import { BlockTools } from '@wordpress/block-editor'; -import { useRef, useMemo } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { store as blocksStore } from '@wordpress/blocks'; @@ -59,8 +58,6 @@ export default function VisualEditor( { styles } ) { paddingBottom = '40vh'; } - const ref = useRef(); - styles = useMemo( () => [ ...styles, @@ -80,21 +77,19 @@ export default function VisualEditor( { styles } ) { renderingMode === 'template-only'; return ( - - + ); } diff --git a/packages/edit-site/src/components/block-editor/editor-canvas.js b/packages/edit-site/src/components/block-editor/editor-canvas.js index d7dbf6fb07a7ab..01bc4cdfa2ddfc 100644 --- a/packages/edit-site/src/components/block-editor/editor-canvas.js +++ b/packages/edit-site/src/components/block-editor/editor-canvas.js @@ -25,13 +25,7 @@ import { const { EditorCanvas: EditorCanvasRoot } = unlock( editorPrivateApis ); -function EditorCanvas( { - enableResizing, - settings, - children, - contentRef, - ...props -} ) { +function EditorCanvas( { enableResizing, settings, children, ...props } ) { const { hasBlocks, isFocusMode, templateType, canvasMode, isZoomOutMode } = useSelect( ( select ) => { const { getBlockCount, __unstableGetEditorMode } = @@ -107,7 +101,6 @@ function EditorCanvas( { return ( { const { getEditedPostType, getCanvasMode } = unlock( select( editSiteStore ) @@ -53,7 +49,6 @@ export default function SiteEditorCanvas() { // Disable resizing in mobile viewport. ! isMobileViewport; - const contentRef = useRef(); const isTemplateTypeNavigation = templateType === NAVIGATION_POST_TYPE; const isNavigationFocusMode = isTemplateTypeNavigation && isFocusMode; const forceFullHeight = isNavigationFocusMode; @@ -66,18 +61,11 @@ export default function SiteEditorCanvas() { { editorCanvasView } ) : ( - { - // Clear selected block when clicking on the gray background. - if ( event.target === event.currentTarget ) { - clearSelectedBlock(); - } - } } > { resizeObserver } - + ) } diff --git a/packages/editor/src/components/editor-canvas/index.js b/packages/editor/src/components/editor-canvas/index.js index 1f74dfd262ff54..cd87db0d4bf5e3 100644 --- a/packages/editor/src/components/editor-canvas/index.js +++ b/packages/editor/src/components/editor-canvas/index.js @@ -16,7 +16,7 @@ import { privateApis as blockEditorPrivateApis, __experimentalUseResizeCanvas as useResizeCanvas, } from '@wordpress/block-editor'; -import { useEffect, useRef, useMemo, forwardRef } from '@wordpress/element'; +import { useEffect, useRef, useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { parse } from '@wordpress/blocks'; import { store as coreStore } from '@wordpress/core-data'; @@ -72,19 +72,16 @@ function checkForPostContentAtRootLevel( blocks ) { return false; } -function EditorCanvas( - { - // Ideally as we unify post and site editors, we won't need these props. - autoFocus, - className, - renderAppender, - styles, - disableIframe = false, - iframeProps, - children, - }, - ref -) { +function EditorCanvas( { + // Ideally as we unify post and site editors, we won't need these props. + autoFocus, + className, + renderAppender, + styles, + disableIframe = false, + iframeProps, + children, +} ) { const { renderingMode, postContentAttributes, @@ -288,7 +285,6 @@ function EditorCanvas( const typewriterRef = useTypewriter(); const contentRef = useMergeRefs( [ - ref, localRef, renderingMode === 'post-only' ? typewriterRef : undefined, ].filter( ( r ) => !! r ) @@ -382,4 +378,4 @@ function EditorCanvas( ); } -export default forwardRef( EditorCanvas ); +export default EditorCanvas; diff --git a/platform-docs/docs/basic-concepts/ui.md b/platform-docs/docs/basic-concepts/ui.md index 8b6e706683d085..0dccef3c239b03 100644 --- a/platform-docs/docs/basic-concepts/ui.md +++ b/platform-docs/docs/basic-concepts/ui.md @@ -17,7 +17,7 @@ The Gutenberg platform allows you to render these pieces separately and lay them ## The Block Toolbar -Wrapping your `BlockCanvas` component within the `BlockTools` wrapper allows the editor to render a block toolbar adjacent to the selected block. +The block toolbar is rendered automatically next to the selected block by default. But if you set the flag `hasFixedToolbar` to true in your `BlockEditorProvider` settings, you will be able to use the `BlockToolbar` component to render the block toolbar in your place of choice. ## The Block Inspector diff --git a/storybook/stories/playground/box/index.js b/storybook/stories/playground/box/index.js index 4cb7047b73ec20..3fb3c3b5862c47 100644 --- a/storybook/stories/playground/box/index.js +++ b/storybook/stories/playground/box/index.js @@ -7,7 +7,6 @@ import { BlockEditorProvider, BlockCanvas, BlockToolbar, - BlockTools, } from '@wordpress/block-editor'; /** @@ -38,7 +37,6 @@ export default function EditorBox() { } } > - diff --git a/storybook/stories/playground/fullpage/index.js b/storybook/stories/playground/fullpage/index.js index 961c15f71f31d0..8b8c037ceb72a3 100644 --- a/storybook/stories/playground/fullpage/index.js +++ b/storybook/stories/playground/fullpage/index.js @@ -5,7 +5,6 @@ import { useEffect, useState } from '@wordpress/element'; import { BlockCanvas, BlockEditorProvider, - BlockTools, BlockInspector, } from '@wordpress/block-editor'; import { registerCoreBlocks } from '@wordpress/block-library'; @@ -46,9 +45,9 @@ export default function EditorFullPage() {
    - +
    - +
    ); diff --git a/storybook/stories/playground/with-undo-redo/index.js b/storybook/stories/playground/with-undo-redo/index.js index 537ea16aade99b..8bef2d184f8c59 100644 --- a/storybook/stories/playground/with-undo-redo/index.js +++ b/storybook/stories/playground/with-undo-redo/index.js @@ -8,7 +8,6 @@ import { BlockEditorProvider, BlockCanvas, BlockToolbar, - BlockTools, } from '@wordpress/block-editor'; import { Button } from '@wordpress/components'; import { undo as undoIcon, redo as redoIcon } from '@wordpress/icons'; @@ -60,7 +59,6 @@ export default function EditorWithUndoRedo() { label="Redo" /> - diff --git a/test/integration/helpers/integration-test-editor.js b/test/integration/helpers/integration-test-editor.js index dc83c1bfbe6bd2..1317dec7b9226d 100644 --- a/test/integration/helpers/integration-test-editor.js +++ b/test/integration/helpers/integration-test-editor.js @@ -10,7 +10,6 @@ import userEvent from '@testing-library/user-event'; import { useState, useEffect } from '@wordpress/element'; import { BlockEditorProvider, - BlockTools, BlockInspector, privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; @@ -76,9 +75,7 @@ export function Editor( { testBlocks, settings = {} } ) { settings={ settings } > - - - + ); } From ef65c8b9fceadec403a4cd1b473cc2b45d356c52 Mon Sep 17 00:00:00 2001 From: Nik Tsekouras Date: Wed, 13 Dec 2023 11:34:10 +0200 Subject: [PATCH 16/17] Performance: Improve opening inserter in post editor (#57006) * Performance: Improve opening inserter in post editor * make selector private --- .../src/components/inserter/menu.js | 10 +-- .../src/store/private-selectors.js | 45 +++++++++++ packages/block-editor/src/store/selectors.js | 74 ++----------------- packages/block-editor/src/store/utils.js | 74 +++++++++++++++++++ 4 files changed, 129 insertions(+), 74 deletions(-) create mode 100644 packages/block-editor/src/store/utils.js diff --git a/packages/block-editor/src/components/inserter/menu.js b/packages/block-editor/src/components/inserter/menu.js index a6d752848538e7..4f028eb69c6662 100644 --- a/packages/block-editor/src/components/inserter/menu.js +++ b/packages/block-editor/src/components/inserter/menu.js @@ -22,6 +22,7 @@ import { useDebouncedInput } from '@wordpress/compose'; /** * Internal dependencies */ +import { unlock } from '../../lock-unlock'; import Tips from './tips'; import InserterPreviewPanel from './preview-panel'; import BlockTypesTab from './block-types-tab'; @@ -68,12 +69,11 @@ function InserterMenu( } ); const { showPatterns, inserterItems } = useSelect( ( select ) => { - const { __experimentalGetAllowedPatterns, getInserterItems } = - select( blockEditorStore ); + const { hasAllowedPatterns, getInserterItems } = unlock( + select( blockEditorStore ) + ); return { - showPatterns: !! __experimentalGetAllowedPatterns( - destinationRootClientId - ).length, + showPatterns: hasAllowedPatterns( destinationRootClientId ), inserterItems: getInserterItems( destinationRootClientId ), }; }, diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index c4220e6e7e516c..98a75122f47245 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -10,7 +10,12 @@ import { getBlockOrder, getBlockParents, getBlockEditingMode, + getSettings, + __experimentalGetParsedPattern, + canInsertBlockType, + __experimentalGetAllowedPatterns, } from './selectors'; +import { getUserPatterns, checkAllowListRecursive } from './utils'; /** * Returns true if the block interface is hidden, or false otherwise. @@ -236,3 +241,43 @@ export const getInserterMediaCategories = createSelector( state.registeredInserterMediaCategories, ] ); + +/** + * Returns whether there is at least one allowed pattern for inner blocks children. + * This is useful for deferring the parsing of all patterns until needed. + * + * @param {Object} state Editor state. + * @param {string} [rootClientId=null] Target root client ID. + * + * @return {boolean} If there is at least one allowed pattern. + */ +export const hasAllowedPatterns = createSelector( + ( state, rootClientId = null ) => { + const patterns = state.settings.__experimentalBlockPatterns; + const userPatterns = getUserPatterns( state ); + const { allowedBlockTypes } = getSettings( state ); + return [ ...userPatterns, ...patterns ].some( + ( { name, inserter = true } ) => { + if ( ! inserter ) { + return false; + } + const { blocks } = __experimentalGetParsedPattern( + state, + name + ); + return ( + checkAllowListRecursive( blocks, allowedBlockTypes ) && + blocks.every( ( { name: blockName } ) => + canInsertBlockType( state, blockName, rootClientId ) + ) + ); + } + ); + }, + ( state, rootClientId ) => [ + ...__experimentalGetAllowedPatterns.getDependants( + state, + rootClientId + ), + ] +); diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index c0441cd3b3755e..b6d455333c7a52 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -26,8 +26,12 @@ import { createRegistrySelector } from '@wordpress/data'; /** * Internal dependencies */ +import { + getUserPatterns, + checkAllowListRecursive, + checkAllowList, +} from './utils'; import { orderBy } from '../utils/sorting'; -import { PATTERN_TYPES } from '../components/inserter/block-patterns-tab/utils'; /** * A block selection object. @@ -1481,22 +1485,6 @@ export function getTemplateLock( state, rootClientId ) { return getBlockListSettings( state, rootClientId )?.templateLock ?? false; } -const checkAllowList = ( list, item, defaultResult = null ) => { - if ( typeof list === 'boolean' ) { - return list; - } - if ( Array.isArray( list ) ) { - // TODO: when there is a canonical way to detect that we are editing a post - // the following check should be changed to something like: - // if ( list.includes( 'core/post-content' ) && getEditorMode() === 'post-content' && item === null ) - if ( list.includes( 'core/post-content' ) && item === null ) { - return true; - } - return list.includes( item ); - } - return defaultResult; -}; - /** * Determines if the given block type is allowed to be inserted into the block list. * This function is not exported and not memoized because using a memoized selector @@ -2249,58 +2237,6 @@ export const __experimentalGetDirectInsertBlock = createSelector( ] ); -const checkAllowListRecursive = ( blocks, allowedBlockTypes ) => { - if ( typeof allowedBlockTypes === 'boolean' ) { - return allowedBlockTypes; - } - - const blocksQueue = [ ...blocks ]; - while ( blocksQueue.length > 0 ) { - const block = blocksQueue.shift(); - - const isAllowed = checkAllowList( - allowedBlockTypes, - block.name || block.blockName, - true - ); - if ( ! isAllowed ) { - return false; - } - - block.innerBlocks?.forEach( ( innerBlock ) => { - blocksQueue.push( innerBlock ); - } ); - } - - return true; -}; - -function getUserPatterns( state ) { - const userPatterns = - state?.settings?.__experimentalReusableBlocks ?? EMPTY_ARRAY; - const userPatternCategories = - state?.settings?.__experimentalUserPatternCategories ?? []; - const categories = new Map(); - userPatternCategories.forEach( ( userCategory ) => - categories.set( userCategory.id, userCategory ) - ); - return userPatterns.map( ( userPattern ) => { - return { - name: `core/block/${ userPattern.id }`, - id: userPattern.id, - type: PATTERN_TYPES.user, - title: userPattern.title.raw, - categories: userPattern.wp_pattern_category.map( ( catId ) => - categories && categories.get( catId ) - ? categories.get( catId ).slug - : catId - ), - content: userPattern.content.raw, - syncStatus: userPattern.wp_pattern_sync_status, - }; - } ); -} - export const __experimentalUserPatternCategories = createSelector( ( state ) => { return state?.settings?.__experimentalUserPatternCategories; diff --git a/packages/block-editor/src/store/utils.js b/packages/block-editor/src/store/utils.js new file mode 100644 index 00000000000000..0103b5192154c4 --- /dev/null +++ b/packages/block-editor/src/store/utils.js @@ -0,0 +1,74 @@ +/** + * Internal dependencies + */ +import { PATTERN_TYPES } from '../components/inserter/block-patterns-tab/utils'; + +const EMPTY_ARRAY = []; + +export function getUserPatterns( state ) { + const userPatterns = + state?.settings?.__experimentalReusableBlocks ?? EMPTY_ARRAY; + const userPatternCategories = + state?.settings?.__experimentalUserPatternCategories ?? []; + const categories = new Map(); + userPatternCategories.forEach( ( userCategory ) => + categories.set( userCategory.id, userCategory ) + ); + return userPatterns.map( ( userPattern ) => { + return { + name: `core/block/${ userPattern.id }`, + id: userPattern.id, + type: PATTERN_TYPES.user, + title: userPattern.title.raw, + categories: userPattern.wp_pattern_category.map( ( catId ) => + categories && categories.get( catId ) + ? categories.get( catId ).slug + : catId + ), + content: userPattern.content.raw, + syncStatus: userPattern.wp_pattern_sync_status, + }; + } ); +} + +export const checkAllowList = ( list, item, defaultResult = null ) => { + if ( typeof list === 'boolean' ) { + return list; + } + if ( Array.isArray( list ) ) { + // TODO: when there is a canonical way to detect that we are editing a post + // the following check should be changed to something like: + // if ( list.includes( 'core/post-content' ) && getEditorMode() === 'post-content' && item === null ) + if ( list.includes( 'core/post-content' ) && item === null ) { + return true; + } + return list.includes( item ); + } + return defaultResult; +}; + +export const checkAllowListRecursive = ( blocks, allowedBlockTypes ) => { + if ( typeof allowedBlockTypes === 'boolean' ) { + return allowedBlockTypes; + } + + const blocksQueue = [ ...blocks ]; + while ( blocksQueue.length > 0 ) { + const block = blocksQueue.shift(); + + const isAllowed = checkAllowList( + allowedBlockTypes, + block.name || block.blockName, + true + ); + if ( ! isAllowed ) { + return false; + } + + block.innerBlocks?.forEach( ( innerBlock ) => { + blocksQueue.push( innerBlock ); + } ); + } + + return true; +}; From d8ae7b3c6da5e79041be12e3cd07744673dd9283 Mon Sep 17 00:00:00 2001 From: Gutenberg Repository Automation Date: Wed, 13 Dec 2023 09:50:43 +0000 Subject: [PATCH 17/17] Bump plugin version to 17.3.0-rc.1 --- gutenberg.php | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index f39a0cb519c8ae..20c51fdb6ead2a 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -5,7 +5,7 @@ * Description: Printing since 1440. This is the development plugin for the block editor, site editor, and other future WordPress core functionality. * Requires at least: 6.3 * Requires PHP: 7.0 - * Version: 17.2.1 + * Version: 17.3.0-rc.1 * Author: Gutenberg Team * Text Domain: gutenberg * diff --git a/package-lock.json b/package-lock.json index 6d9382ec9fb2f2..e0c9500416a962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gutenberg", - "version": "17.2.1", + "version": "17.3.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "gutenberg", - "version": "17.2.1", + "version": "17.3.0-rc.1", "hasInstallScript": true, "license": "GPL-2.0-or-later", "dependencies": { diff --git a/package.json b/package.json index 580c100354a966..cab3288450cd75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gutenberg", - "version": "17.2.1", + "version": "17.3.0-rc.1", "private": true, "description": "A new WordPress editor experience.", "author": "The WordPress Contributors",