From fa1998f341d2923ad9d547a60a3243823d74023f Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Thu, 1 Jun 2023 12:09:53 +0200 Subject: [PATCH 01/27] Revise CSS to contain images within overlay properly --- packages/block-library/src/image/style.scss | 22 +++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index e6ad33308bc94..c6d52f7c47acb 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -195,14 +195,28 @@ } .wp-block-image { - display: flex; - justify-content: center; - align-items: center; width: 100%; height: 100%; - z-index: 3000000; position: absolute; + z-index: 3000000; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; flex-direction: column; + padding: 20px; + + figcaption { + margin: 10px 0 0 0; + } + + img { + max-width: 100%; + max-height: 100%; + width: auto; + min-height: 0; + min-width: 0; + } } button { From cbaa3cbc74b7c20998fca50a2ca84ab7fc8ceba4 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Thu, 1 Jun 2023 17:59:52 +0200 Subject: [PATCH 02/27] Set horizontal images to occupy space of container --- packages/block-library/src/image/index.php | 8 +++++++- packages/block-library/src/image/interactivity.js | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 3c90f0fbc21cf..8ff3cee421506 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -75,6 +75,12 @@ function render_block_core_image( $attributes, $content ) { ''; $body_content = preg_replace( '/]+>/', $button, $body_content ); + // Add directive to expand modal image if appropriate. + $m = new WP_HTML_Tag_Processor( $content ); + $m->next_tag( 'img' ); + $m->set_attribute( 'data-wp-bind--style', 'selectors.core.image.styleWidth' ); + $modal_content = $m->get_updated_html(); + $background_color = esc_attr( wp_get_global_styles( array( 'color', 'background' ) ) ); $close_button_icon = ''; @@ -97,7 +103,7 @@ function render_block_core_image( $attributes, $content ) { - $content + $modal_content
HTML; diff --git a/packages/block-library/src/image/interactivity.js b/packages/block-library/src/image/interactivity.js index 6b6f246b83025..68bf0ffc3b6ef 100644 --- a/packages/block-library/src/image/interactivity.js +++ b/packages/block-library/src/image/interactivity.js @@ -89,6 +89,14 @@ store( { roleAttribute: ( { context } ) => { return context.core.image.lightboxEnabled ? 'dialog' : ''; }, + styleWidth: ( { context } ) => { + if ( context.core.image.imageRef ) { + return context.core.image.imageRef.offsetWidth >= + context.core.image.imageRef.offsetHeight + ? 'width: 100%;' + : 'width: auto;'; + } + }, }, }, }, @@ -96,6 +104,7 @@ store( { core: { image: { initLightbox: async ( { context, ref } ) => { + context.core.image.imageRef = ref.querySelector( 'img' ); if ( context.core.image.lightboxEnabled ) { const focusableElements = ref.querySelectorAll( focusableSelectors ); From e243b3270d19e2f370e6a62428f93f387c39a9fc Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Thu, 1 Jun 2023 18:08:06 +0200 Subject: [PATCH 03/27] Make square images fit by height rather than expand to full width --- packages/block-library/src/image/interactivity.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/image/interactivity.js b/packages/block-library/src/image/interactivity.js index 68bf0ffc3b6ef..0e4c0e2198b5b 100644 --- a/packages/block-library/src/image/interactivity.js +++ b/packages/block-library/src/image/interactivity.js @@ -91,7 +91,7 @@ store( { }, styleWidth: ( { context } ) => { if ( context.core.image.imageRef ) { - return context.core.image.imageRef.offsetWidth >= + return context.core.image.imageRef.offsetWidth > context.core.image.imageRef.offsetHeight ? 'width: 100%;' : 'width: auto;'; From 2b977c280229ec63a530a3611d993b82740279c7 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 2 Jun 2023 14:56:13 +0200 Subject: [PATCH 04/27] Revise so lightbox loads original image; remove padding and hide caption The lightbox will now load the original image when opened; the src attribute is set dynamically so it will not be unnecessarily loaded by the browser. In addition, removed the padding and hid the caption. --- packages/block-library/src/image/index.php | 3 ++- packages/block-library/src/image/interactivity.js | 11 ++++------- packages/block-library/src/image/style.scss | 4 +--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 8ff3cee421506..77ff82d1c462b 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -78,7 +78,8 @@ function render_block_core_image( $attributes, $content ) { // Add directive to expand modal image if appropriate. $m = new WP_HTML_Tag_Processor( $content ); $m->next_tag( 'img' ); - $m->set_attribute( 'data-wp-bind--style', 'selectors.core.image.styleWidth' ); + $m->set_attribute( 'data-wp-context', '{ "core": { "image": { "imageSrc": "' . wp_get_attachment_url($attributes['id']) . '"} } }' ); + $m->set_attribute( 'data-wp-bind--src', 'selectors.core.image.imageSrc' ); $modal_content = $m->get_updated_html(); $background_color = esc_attr( wp_get_global_styles( array( 'color', 'background' ) ) ); diff --git a/packages/block-library/src/image/interactivity.js b/packages/block-library/src/image/interactivity.js index 0e4c0e2198b5b..a4eb79cf50974 100644 --- a/packages/block-library/src/image/interactivity.js +++ b/packages/block-library/src/image/interactivity.js @@ -89,13 +89,10 @@ store( { roleAttribute: ( { context } ) => { return context.core.image.lightboxEnabled ? 'dialog' : ''; }, - styleWidth: ( { context } ) => { - if ( context.core.image.imageRef ) { - return context.core.image.imageRef.offsetWidth > - context.core.image.imageRef.offsetHeight - ? 'width: 100%;' - : 'width: auto;'; - } + imageSrc: ( { context } ) => { + return context.core.image.initialized + ? context.core.image.imageSrc + : ''; }, }, }, diff --git a/packages/block-library/src/image/style.scss b/packages/block-library/src/image/style.scss index c6d52f7c47acb..5628c0cc073b1 100644 --- a/packages/block-library/src/image/style.scss +++ b/packages/block-library/src/image/style.scss @@ -204,18 +204,16 @@ justify-content: center; align-items: center; flex-direction: column; - padding: 20px; figcaption { margin: 10px 0 0 0; + display: none; } img { max-width: 100%; max-height: 100%; width: auto; - min-height: 0; - min-width: 0; } } From 0eb4712f00b1b9aace361001e939965cc6133a22 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 2 Jun 2023 15:10:01 +0200 Subject: [PATCH 05/27] Fix PHP spacing for linter --- packages/block-library/src/image/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 77ff82d1c462b..7ff47a88183f4 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -78,7 +78,7 @@ function render_block_core_image( $attributes, $content ) { // Add directive to expand modal image if appropriate. $m = new WP_HTML_Tag_Processor( $content ); $m->next_tag( 'img' ); - $m->set_attribute( 'data-wp-context', '{ "core": { "image": { "imageSrc": "' . wp_get_attachment_url($attributes['id']) . '"} } }' ); + $m->set_attribute( 'data-wp-context', '{ "core": { "image": { "imageSrc": "' . wp_get_attachment_url( $attributes['id'] ) . '"} } }' ); $m->set_attribute( 'data-wp-bind--src', 'selectors.core.image.imageSrc' ); $modal_content = $m->get_updated_html(); From da2e35f87fce78cfc4387c4d993936e30520a1e1 Mon Sep 17 00:00:00 2001 From: Ricardo Artemio Morales Date: Fri, 2 Jun 2023 17:36:13 +0200 Subject: [PATCH 06/27] Fix test; image src is now only set after lightbox opens --- test/e2e/specs/editor/blocks/image.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/editor/blocks/image.spec.js b/test/e2e/specs/editor/blocks/image.spec.js index 48ece12d8f347..06551c6347889 100644 --- a/test/e2e/specs/editor/blocks/image.spec.js +++ b/test/e2e/specs/editor/blocks/image.spec.js @@ -828,11 +828,11 @@ test.describe( 'Image - interactivity', () => { const lightbox = page.locator( '.wp-lightbox-overlay' ); await expect( lightbox ).toBeHidden(); + await page.getByRole( 'button', { name: 'Enlarge image' } ).click(); + const image = lightbox.locator( 'img' ); await expect( image ).toHaveAttribute( 'src', new RegExp( filename ) ); - await page.getByRole( 'button', { name: 'Enlarge image' } ).click(); - await expect( lightbox ).toBeVisible(); const closeButton = lightbox.getByRole( 'button', { From 3834625c3cb3799bc270f0af1aa279c04447194a Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Thu, 1 Jun 2023 18:22:39 +0200 Subject: [PATCH 07/27] Fix headings in the View component readme. (#51157) --- packages/components/src/view/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/view/README.md b/packages/components/src/view/README.md index 794d7b49a0d38..c1e04ebd8cfff 100644 --- a/packages/components/src/view/README.md +++ b/packages/components/src/view/README.md @@ -27,13 +27,13 @@ function Example() { ## Props -##### as +### as **Type**: `string`,`E` Render the component as another React Component or HTML Element. -##### css +### css **Type**: `InterpolatedCSS` From 1ea4cd44339fa84c7a7b7d4cd49a810e71c93b3e Mon Sep 17 00:00:00 2001 From: Tonya Mork Date: Thu, 1 Jun 2023 16:42:10 -0500 Subject: [PATCH 08/27] [Fonts API] Make local font asset file URL absolute (#51178) Ensures local font asset file URLs also work in iframes. --- lib/experimental/fonts-api/class-wp-fonts-provider-local.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php b/lib/experimental/fonts-api/class-wp-fonts-provider-local.php index a5dbdb707e41f..019861a9ae191 100644 --- a/lib/experimental/fonts-api/class-wp-fonts-provider-local.php +++ b/lib/experimental/fonts-api/class-wp-fonts-provider-local.php @@ -207,11 +207,6 @@ private function compile_src( array $value ) { $src = ''; foreach ( $value as $item ) { - - if ( str_starts_with( $item['url'], get_site_url() ) ) { - $item['url'] = wp_make_link_relative( $item['url'] ); - } - $src .= ( 'data' === $item['format'] ) ? ", url({$item['url']})" : ", url('{$item['url']}') format('{$item['format']}')"; From e0cd2392a2e560bbbb734881f2818718bd8857ad Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 2 Jun 2023 10:04:23 +1000 Subject: [PATCH 09/27] Add inert attribute to disabled blocks that have only disabled descendants (#51079) * Add inert attribute to disabled blocks that have only disabled descendants * Fix typo --------- Co-authored-by: ramon --- .../block-list/use-block-props/index.js | 7 +- .../src/store/private-selectors.js | 33 ++- .../src/store/test/private-selectors.js | 272 +++++++++++++----- 3 files changed, 235 insertions(+), 77 deletions(-) diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index acc2bd7f510ed..ab65e120024d6 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -36,6 +36,7 @@ import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; import { store as blockEditorStore } from '../../../store'; import useBlockOverlayActive from '../../block-content-overlay'; +import { unlock } from '../../../lock-unlock'; /** * If the block count exceeds the threshold, we disable the reordering animation @@ -75,6 +76,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isPartOfSelection, adjustScrolling, enableAnimation, + isSubtreeDisabled, } = useSelect( ( select ) => { const { @@ -88,7 +90,8 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { isBlockMultiSelected, isAncestorMultiSelected, isFirstMultiSelectedBlock, - } = select( blockEditorStore ); + isBlockSubtreeDisabled, + } = unlock( select( blockEditorStore ) ); const { getActiveBlockVariation } = select( blocksStore ); const isSelected = isBlockSelected( clientId ); const isPartOfMultiSelection = @@ -111,6 +114,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { enableAnimation: ! isTyping() && getGlobalBlockCount() <= BLOCK_ANIMATION_THRESHOLD, + isSubtreeDisabled: isBlockSubtreeDisabled( clientId ), }; }, [ clientId ] @@ -158,6 +162,7 @@ export function useBlockProps( props = {}, { __unstableIsHtml } = {} ) { 'data-block': clientId, 'data-type': name, 'data-title': blockTitle, + inert: isSubtreeDisabled ? 'true' : undefined, className: classnames( // The wp-block className is important for editor styles. classnames( 'block-editor-block-list__block', { diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index ce7802036184e..0f549fc5ad055 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -16,6 +16,7 @@ import { getBlockRootClientId, getTemplateLock, getBlockName, + getBlockOrder, } from './selectors'; /** @@ -74,7 +75,7 @@ export function getLastInsertedBlocksClientIds( state ) { export const getBlockEditingMode = createRegistrySelector( ( select ) => ( state, clientId = '' ) => { - const explicitEditingMode = getExplcitBlockEditingMode( + const explicitEditingMode = getExplicitBlockEditingMode( state, clientId ); @@ -101,7 +102,7 @@ export const getBlockEditingMode = createRegistrySelector( } ); -const getExplcitBlockEditingMode = createSelector( +const getExplicitBlockEditingMode = createSelector( ( state, clientId = '' ) => { while ( ! state.blockEditingModes.has( clientId ) && @@ -113,3 +114,31 @@ const getExplcitBlockEditingMode = createSelector( }, ( state ) => [ state.blockEditingModes, state.blocks.parents ] ); + +/** + * Returns true if the block with the given client ID and all of its descendants + * have an editing mode of 'disabled', or false otherwise. + * + * @param {Object} state Global application state. + * @param {string} clientId The block client ID. + * + * @return {boolean} Whether the block and its descendants are disabled. + */ +export const isBlockSubtreeDisabled = createSelector( + ( state, clientId ) => { + const isChildSubtreeDisabled = ( childClientId ) => { + const mode = state.blockEditingModes.get( childClientId ); + return ( + ( mode === undefined || mode === 'disabled' ) && + getBlockOrder( state, childClientId ).every( + isChildSubtreeDisabled + ) + ); + }; + return ( + getExplicitBlockEditingMode( state, clientId ) === 'disabled' && + getBlockOrder( state, clientId ).every( isChildSubtreeDisabled ) + ); + }, + ( state ) => [ state.blockEditingModes, state.blocks.parents ] +); diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index 954c8c94c1379..1c87d0fc18e19 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -5,6 +5,7 @@ import { isBlockInterfaceHidden, getLastInsertedBlocksClientIds, getBlockEditingMode, + isBlockSubtreeDisabled, } from '../private-selectors'; describe( 'private selectors', () => { @@ -51,7 +52,7 @@ describe( 'private selectors', () => { } ); } ); - describe( 'getBlockEditingMode', () => { + describe( 'block editing mode selectors', () => { const baseState = { settings: {}, blocks: { @@ -63,6 +64,27 @@ describe( 'private selectors', () => { [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', {} ], // | | Paragraph [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', {} ], // | | Paragraph ] ), + order: new Map( [ + [ '', [ '6cf70164-9097-4460-bcbf-200560546988' ] ], + [ '6cf70164-9097-4460-bcbf-200560546988', [] ], + [ + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', + [ + 'b26fc763-417d-4f01-b81c-2ec61e14a972', + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', + ], + ], + [ 'b26fc763-417d-4f01-b81c-2ec61e14a972', [] ], + [ + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', + [ + 'b3247f75-fd94-4fef-97f9-5bfd162cc416', + 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', + ], + ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', [] ], + [ 'e178812d-ce5e-48c7-a945-8ae4ffcbbb7c', [] ], + ] ), parents: new Map( [ [ '6cf70164-9097-4460-bcbf-200560546988', '' ], [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', '' ], @@ -91,120 +113,222 @@ describe( 'private selectors', () => { blockEditingModes: new Map( [] ), }; - const __experimentalHasContentRoleAttribute = jest.fn( () => false ); - getBlockEditingMode.registry = { - select: jest.fn( () => ( { - __experimentalHasContentRoleAttribute, - } ) ), - }; + describe( 'getBlockEditingMode', () => { + const __experimentalHasContentRoleAttribute = jest.fn( + () => false + ); + getBlockEditingMode.registry = { + select: jest.fn( () => ( { + __experimentalHasContentRoleAttribute, + } ) ), + }; - it( 'should return default by default', () => { - expect( - getBlockEditingMode( - baseState, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'default' ); - } ); + it( 'should return default by default', () => { + expect( + getBlockEditingMode( + baseState, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'default' ); + } ); + + [ 'disabled', 'contentOnly' ].forEach( ( mode ) => { + it( `should return ${ mode } if explicitly set`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', mode ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); + + it( `should return ${ mode } if explicitly set on a parent`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', mode ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); - [ 'disabled', 'contentOnly' ].forEach( ( mode ) => { - it( `should return ${ mode } if explicitly set`, () => { + it( `should return ${ mode } if overridden by a parent`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ '', mode ], + [ + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', + 'default', + ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', mode ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); + + it( `should return ${ mode } if explicitly set on root`, () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ [ '', mode ] ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( mode ); + } ); + } ); + + it( 'should return disabled if parent is locked and the block has no content role', () => { const state = { ...baseState, - blockEditingModes: new Map( [ - [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', mode ], - ] ), + blockListSettings: { + ...baseState.blockListSettings, + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { + templateLock: 'contentOnly', + }, + }, }; + __experimentalHasContentRoleAttribute.mockReturnValueOnce( + false + ); expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) - ).toBe( mode ); + ).toBe( 'disabled' ); } ); - it( `should return ${ mode } if explicitly set on a parent`, () => { + it( 'should return contentOnly if parent is locked and the block has a content role', () => { const state = { ...baseState, - blockEditingModes: new Map( [ - [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', mode ], - ] ), + blockListSettings: { + ...baseState.blockListSettings, + '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { + templateLock: 'contentOnly', + }, + }, }; + __experimentalHasContentRoleAttribute.mockReturnValueOnce( + true + ); expect( getBlockEditingMode( state, 'b3247f75-fd94-4fef-97f9-5bfd162cc416' ) - ).toBe( mode ); + ).toBe( 'contentOnly' ); } ); + } ); - it( `should return ${ mode } if overridden by a parent`, () => { + describe( 'isBlockSubtreeDisabled', () => { + it( 'should return false when top level block is not disabled', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( false ); + } ); + + it( 'should return true when top level block is disabled and there are no editing modes within it', () => { const state = { ...baseState, blockEditingModes: new Map( [ - [ '', mode ], - [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'default' ], - [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', mode ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], ] ), }; expect( - getBlockEditingMode( + isBlockSubtreeDisabled( state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) - ).toBe( mode ); + ).toBe( true ); } ); - it( `should return ${ mode } if explicitly set on root`, () => { + it( 'should return true when top level block is disabled via inheritence and there are no editing modes within it', () => { const state = { ...baseState, - blockEditingModes: new Map( [ [ '', mode ] ] ), + blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), }; expect( - getBlockEditingMode( + isBlockSubtreeDisabled( state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' ) - ).toBe( mode ); + ).toBe( true ); } ); - } ); - it( 'should return disabled if parent is locked and the block has no content role', () => { - const state = { - ...baseState, - blockListSettings: { - ...baseState.blockListSettings, - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { - templateLock: 'contentOnly', - }, - }, - }; - __experimentalHasContentRoleAttribute.mockReturnValueOnce( false ); - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'disabled' ); - } ); + it( 'should return true when top level block is disabled and there are disabled editing modes within it', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + ] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( true ); + } ); - it( 'should return contentOnly if parent is locked and the block has a content role', () => { - const state = { - ...baseState, - blockListSettings: { - ...baseState.blockListSettings, - '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f': { - templateLock: 'contentOnly', - }, - }, - }; - __experimentalHasContentRoleAttribute.mockReturnValueOnce( true ); - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( 'contentOnly' ); + it( 'should return false when top level block is disabled and there are non-disabled editing modes within it', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], + ] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( false ); + } ); + + it( 'should return false when top level block is disabled via inheritence and there are non-disabled editing modes within it', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ '', 'disabled' ], + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'default' ], + ] ), + }; + expect( + isBlockSubtreeDisabled( + state, + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337' + ) + ).toBe( false ); + } ); } ); } ); } ); From 5430ec3b28f23f980d4fb51567ea062b37032087 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 2 Jun 2023 11:45:01 +1000 Subject: [PATCH 10/27] Improvements to how blocks with a 'disabled' editing mode behave (#51148) - Prevent DefaultAppender from appearing in a disabled block. - Disable selection (using user-select: none) in disabled blocks. - Prevent blocks from being inserted into a disabled block via global inserter. - Prevent disabled blocks from being removed via keyboard shortcut. - Prevent disabled blocks from being moved via List View drag and drop. - Prevent block overlay from appearing on a disabled block. --- .../components/block-list-appender/index.js | 5 +- .../src/components/block-list/content.scss | 20 ++-- .../src/store/private-selectors.js | 58 ++++++----- packages/block-editor/src/store/selectors.js | 51 ++++++---- .../src/store/test/private-selectors.js | 17 +++- .../block-editor/src/store/test/selectors.js | 99 +++++++++++++++++++ 6 files changed, 182 insertions(+), 68 deletions(-) diff --git a/packages/block-editor/src/components/block-list-appender/index.js b/packages/block-editor/src/components/block-list-appender/index.js index 56c044b6c5a8a..99d0fc56374e4 100644 --- a/packages/block-editor/src/components/block-list-appender/index.js +++ b/packages/block-editor/src/components/block-list-appender/index.js @@ -15,6 +15,7 @@ import { getDefaultBlockName } from '@wordpress/blocks'; import DefaultBlockAppender from '../default-block-appender'; import ButtonBlockAppender from '../button-block-appender'; import { store as blockEditorStore } from '../../store'; +import { unlock } from '../../lock-unlock'; function DefaultAppender( { rootClientId } ) { const canInsertDefaultBlock = useSelect( ( select ) => @@ -46,13 +47,15 @@ function useAppender( rootClientId, CustomAppender ) { getTemplateLock, getSelectedBlockClientId, __unstableGetEditorMode, - } = select( blockEditorStore ); + getBlockEditingMode, + } = unlock( select( blockEditorStore ) ); const selectedBlockClientId = getSelectedBlockClientId(); return { hideInserter: !! getTemplateLock( rootClientId ) || + getBlockEditingMode( rootClientId ) === 'disabled' || __unstableGetEditorMode() === 'zoom-out', isParentSelected: rootClientId === selectedBlockClientId || diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index dab07ced873e9..a163ddaa78955 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -161,15 +161,6 @@ padding: 0; } -.block-editor-block-list__layout, -.block-editor-block-list__block { - pointer-events: initial; - - &.is-editing-disabled { - pointer-events: none; - } -} - .block-editor-block-list__layout .block-editor-block-list__block { // With `position: static`, Safari marks a full-width selection rectangle, including margins. // With `position: relative`, Safari marks an inline selection rectangle, similar to that of @@ -178,12 +169,17 @@ // We choose relative, as that matches the multi-selection, which is limited to the block footprint. position: relative; - // Re-enable text-selection on editable blocks. - user-select: text; - // Break long strings of text without spaces so they don't overflow the block. overflow-wrap: break-word; + pointer-events: auto; + user-select: text; + + &.is-editing-disabled { + pointer-events: none; + user-select: none; + } + .reusable-block-edit-panel * { z-index: z-index(".block-editor-block-list__block .reusable-block-edit-panel *"); } diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 0f549fc5ad055..5cb748cdd8dfa 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -6,7 +6,7 @@ import createSelector from 'rememo'; /** * WordPress dependencies */ -import { createRegistrySelector } from '@wordpress/data'; +import { select } from '@wordpress/data'; import { store as blocksStore } from '@wordpress/blocks'; /** @@ -72,35 +72,33 @@ export function getLastInsertedBlocksClientIds( state ) { * @return {BlockEditingMode} The block editing mode. One of `'disabled'`, * `'contentOnly'`, or `'default'`. */ -export const getBlockEditingMode = createRegistrySelector( - ( select ) => - ( state, clientId = '' ) => { - const explicitEditingMode = getExplicitBlockEditingMode( - state, - clientId - ); - const rootClientId = getBlockRootClientId( state, clientId ); - const templateLock = getTemplateLock( state, rootClientId ); - const name = getBlockName( state, clientId ); - const isContent = - select( blocksStore ).__experimentalHasContentRoleAttribute( - name - ); - if ( - explicitEditingMode === 'disabled' || - ( templateLock === 'contentOnly' && ! isContent ) - ) { - return 'disabled'; - } - if ( - explicitEditingMode === 'contentOnly' || - ( templateLock === 'contentOnly' && isContent ) - ) { - return 'contentOnly'; - } - return 'default'; - } -); +export const getBlockEditingMode = ( state, clientId = '' ) => { + const explicitEditingMode = getExplicitBlockEditingMode( state, clientId ); + const rootClientId = getBlockRootClientId( state, clientId ); + const templateLock = getTemplateLock( state, rootClientId ); + const name = getBlockName( state, clientId ); + // TODO: Terrible hack! We're calling the global select() function here + // instead of using createRegistrySelector(). The problem with using + // createRegistrySelector() is that then the public block-editor selectors + // (e.g. canInsertBlockTypeUnmemoized) can't call this private block-editor + // selector due to a bug in @wordpress/data. See + // https://github.com/WordPress/gutenberg/pull/50985. + const isContent = + select( blocksStore ).__experimentalHasContentRoleAttribute( name ); + if ( + explicitEditingMode === 'disabled' || + ( templateLock === 'contentOnly' && ! isContent ) + ) { + return 'disabled'; + } + if ( + explicitEditingMode === 'contentOnly' || + ( templateLock === 'contentOnly' && isContent ) + ) { + return 'contentOnly'; + } + return 'default'; +}; const getExplicitBlockEditingMode = createSelector( ( state, clientId = '' ) => { diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 487d13db811d8..5b615f67defbd 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -26,6 +26,7 @@ import deprecated from '@wordpress/deprecated'; */ import { mapRichTextSettings } from './utils'; import { orderBy } from '../utils/sorting'; +import { getBlockEditingMode } from './private-selectors'; /** * A block selection object. @@ -1539,6 +1540,10 @@ const canInsertBlockTypeUnmemoized = ( return false; } + if ( getBlockEditingMode( state, rootClientId ?? '' ) === 'disabled' ) { + return false; + } + const parentBlockListSettings = getBlockListSettings( state, rootClientId ); // The parent block doesn't have settings indicating it doesn't support @@ -1633,6 +1638,7 @@ export const canInsertBlockType = createSelector( state.blocks.byClientId.get( rootClientId ), state.settings.allowedBlockTypes, state.settings.templateLock, + state.blockEditingModes, ] ); @@ -1663,21 +1669,19 @@ export function canInsertBlocks( state, clientIds, rootClientId = null ) { */ export function canRemoveBlock( state, clientId, rootClientId = null ) { const attributes = getBlockAttributes( state, clientId ); - - // attributes can be null if the block is already deleted. if ( attributes === null ) { return true; } - - const { lock } = attributes; - const parentIsLocked = !! getTemplateLock( state, rootClientId ); - // If we don't have a lock on the blockType level, we defer to the parent templateLock. - if ( lock === undefined || lock?.remove === undefined ) { - return ! parentIsLocked; + if ( attributes.lock?.remove ) { + return false; } - - // When remove is true, it means we cannot remove it. - return ! lock?.remove; + if ( getTemplateLock( state, rootClientId ) ) { + return false; + } + if ( getBlockEditingMode( state, rootClientId ) === 'disabled' ) { + return false; + } + return true; } /** @@ -1709,16 +1713,16 @@ export function canMoveBlock( state, clientId, rootClientId = null ) { if ( attributes === null ) { return; } - - const { lock } = attributes; - const parentIsLocked = getTemplateLock( state, rootClientId ) === 'all'; - // If we don't have a lock on the blockType level, we defer to the parent templateLock. - if ( lock === undefined || lock?.move === undefined ) { - return ! parentIsLocked; + if ( attributes.lock?.move ) { + return false; } - - // When move is true, it means we cannot move it. - return ! lock?.move; + if ( getTemplateLock( state, rootClientId ) === 'all' ) { + return false; + } + if ( getBlockEditingMode( state, rootClientId ) === 'disabled' ) { + return false; + } + return true; } /** @@ -2812,6 +2816,13 @@ export function __unstableGetTemporarilyEditingAsBlocks( state ) { } export function __unstableHasActiveBlockOverlayActive( state, clientId ) { + // Prevent overlay on disabled blocks. It's redundant since disabled blocks + // can't be selected, and prevents non-disabled nested blocks from being + // selected. + if ( getBlockEditingMode( state, clientId ) === 'disabled' ) { + return false; + } + // If the block editing is locked, the block overlay is always active. if ( ! canEditBlock( state, clientId ) ) { return true; diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index 1c87d0fc18e19..cbe890a8dc973 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { select } from '@wordpress/data'; + /** * Internal dependencies */ @@ -8,6 +13,10 @@ import { isBlockSubtreeDisabled, } from '../private-selectors'; +jest.mock( '@wordpress/data/src/select', () => ( { + select: jest.fn(), +} ) ); + describe( 'private selectors', () => { describe( 'isBlockInterfaceHidden', () => { it( 'should return the true if toggled true in state', () => { @@ -117,11 +126,9 @@ describe( 'private selectors', () => { const __experimentalHasContentRoleAttribute = jest.fn( () => false ); - getBlockEditingMode.registry = { - select: jest.fn( () => ( { - __experimentalHasContentRoleAttribute, - } ) ), - }; + select.mockReturnValue( { + __experimentalHasContentRoleAttribute, + } ); it( 'should return default by default', () => { expect( diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 60d90d80b9d41..5cc17fc08b314 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -2693,11 +2693,13 @@ describe( 'selectors', () => { blocks: { byClientId: new Map(), attributes: new Map(), + parents: new Map(), }, blockListSettings: {}, settings: { allowedBlockTypes: [ 'core/test-block-a' ], }, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( true @@ -2720,14 +2722,36 @@ describe( 'selectors', () => { ); } ); + it( 'should deny blocks when the editor has a disabled editing mode', () => { + const state = { + blocks: { + byClientId: new Map(), + attributes: new Map(), + parents: new Map(), + }, + blockListSettings: {}, + settings: {}, + blockEditingModes: new Map( + Object.entries( { + '': 'disabled', + } ) + ), + }; + expect( canInsertBlockType( state, 'core/test-block-a' ) ).toBe( + false + ); + } ); + it( 'should deny blocks that restrict parent from being inserted into the root', () => { const state = { blocks: { byClientId: new Map(), attributes: new Map(), + parents: new Map(), }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-c' ) ).toBe( false @@ -2747,9 +2771,11 @@ describe( 'selectors', () => { block1: {}, } ) ), + parents: new Map(), }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) @@ -2769,11 +2795,13 @@ describe( 'selectors', () => { block1: {}, } ) ), + parents: new Map(), }, blockListSettings: { block1: {}, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) @@ -2793,11 +2821,13 @@ describe( 'selectors', () => { block1: {}, } ) ), + parents: new Map(), }, blockListSettings: { block1: {}, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) @@ -2817,6 +2847,7 @@ describe( 'selectors', () => { block1: {}, } ) ), + parents: new Map(), }, blockListSettings: { block1: { @@ -2824,6 +2855,7 @@ describe( 'selectors', () => { }, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) @@ -2843,6 +2875,7 @@ describe( 'selectors', () => { block1: {}, } ) ), + parents: new Map(), }, blockListSettings: { block1: { @@ -2850,12 +2883,41 @@ describe( 'selectors', () => { }, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-b', 'block1' ) ).toBe( true ); } ); + it( 'should deny blocks from being inserted into a block that has a disabled editing mode', () => { + const state = { + blocks: { + byClientId: new Map( + Object.entries( { + block1: { name: 'core/test-block-a' }, + } ) + ), + attributes: new Map( + Object.entries( { + block1: {}, + } ) + ), + parents: new Map(), + }, + blockListSettings: {}, + settings: {}, + blockEditingModes: new Map( + Object.entries( { + block1: 'disabled', + } ) + ), + }; + expect( + canInsertBlockType( state, 'core/test-block-b', 'block1' ) + ).toBe( false ); + } ); + it( 'should prioritise parent over allowedBlocks', () => { const state = { blocks: { @@ -2869,6 +2931,7 @@ describe( 'selectors', () => { block1: {}, } ) ), + parents: new Map(), }, blockListSettings: { block1: { @@ -2876,6 +2939,7 @@ describe( 'selectors', () => { }, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/test-block-c', 'block1' ) @@ -2895,9 +2959,11 @@ describe( 'selectors', () => { block1: {}, } ) ), + parents: new Map(), }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/post-content-child', 'block1' ) @@ -2909,9 +2975,11 @@ describe( 'selectors', () => { blocks: { byClientId: new Map(), attributes: new Map(), + parents: new Map(), }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( state, 'core/post-content-child' ) @@ -2944,6 +3012,7 @@ describe( 'selectors', () => { block2: {}, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( @@ -2984,6 +3053,7 @@ describe( 'selectors', () => { block3: {}, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( @@ -3023,6 +3093,7 @@ describe( 'selectors', () => { block3: {}, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( @@ -3062,6 +3133,7 @@ describe( 'selectors', () => { block3: {}, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( @@ -3100,6 +3172,7 @@ describe( 'selectors', () => { }, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( @@ -3136,6 +3209,7 @@ describe( 'selectors', () => { block2: {}, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlockType( @@ -3165,6 +3239,7 @@ describe( 'selectors', () => { 3: {}, } ) ), + parents: new Map(), }, blockListSettings: { 1: { @@ -3175,6 +3250,7 @@ describe( 'selectors', () => { }, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlocks( state, [ '2', '3' ], '1' ) ).toBe( true ); } ); @@ -3196,6 +3272,7 @@ describe( 'selectors', () => { 3: {}, } ) ), + parents: new Map(), }, blockListSettings: { 1: { @@ -3203,6 +3280,7 @@ describe( 'selectors', () => { }, }, settings: {}, + blockEditingModes: new Map(), }; expect( canInsertBlocks( state, [ '2', '3' ], '1' ) ).toBe( false ); } ); @@ -3241,6 +3319,7 @@ describe( 'selectors', () => { // See: https://github.com/WordPress/gutenberg/issues/14580 preferences: {}, blockListSettings: {}, + blockEditingModes: new Map(), }; const items = getInserterItems( state ); const testBlockAItem = items.find( @@ -3349,6 +3428,7 @@ describe( 'selectors', () => { block3: {}, block4: {}, }, + blockEditingModes: new Map(), }; const stateSecondBlockRestricted = { @@ -3436,6 +3516,7 @@ describe( 'selectors', () => { }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; const items = getInserterItems( state ); const testBlockBItem = items.find( @@ -3460,6 +3541,7 @@ describe( 'selectors', () => { }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; const items = getInserterItems( state ); const reusableBlock2Item = items.find( @@ -3551,6 +3633,7 @@ describe( 'selectors', () => { settings: {}, preferences: {}, blockListSettings: {}, + blockEditingModes: new Map(), }; const blocks = [ { name: 'core/with-tranforms-a' } ]; const items = getBlockTransformItems( state, blocks ); @@ -3591,6 +3674,7 @@ describe( 'selectors', () => { settings: {}, preferences: {}, blockListSettings: {}, + blockEditingModes: new Map(), }; const block = { name: 'core/with-tranforms-a' }; const items = getBlockTransformItems( state, block ); @@ -3629,6 +3713,7 @@ describe( 'selectors', () => { }, block2: {}, }, + blockEditingModes: new Map(), }; const blocks = [ { clientId: 'block2', name: 'core/with-tranforms-a' }, @@ -3676,6 +3761,7 @@ describe( 'selectors', () => { }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; const blocks = [ { name: 'core/with-tranforms-a' } ]; const items = getBlockTransformItems( state, blocks ); @@ -3709,6 +3795,7 @@ describe( 'selectors', () => { }, blockListSettings: {}, settings: {}, + blockEditingModes: new Map(), }; const blocks = [ { name: 'core/with-tranforms-c' } ]; const items = getBlockTransformItems( state, blocks ); @@ -4121,6 +4208,12 @@ describe( 'selectors', () => { block2: {}, } ) ), + parents: new Map( + Object.entries( { + block1: '', + block2: '', + } ) + ), }, blockListSettings: { block1: { @@ -4152,6 +4245,7 @@ describe( 'selectors', () => { }, ], }, + blockEditingModes: new Map(), }; it( 'should return all patterns for root level', () => { @@ -4249,6 +4343,7 @@ describe( 'selectors', () => { block1: { name: 'core/test-block-a' }, } ) ), + parents: new Map(), }, blockListSettings: { block1: { @@ -4279,6 +4374,7 @@ describe( 'selectors', () => { }, ], }, + blockEditingModes: new Map(), }; it( 'should return empty array if no block name is provided', () => { expect( getPatternsByBlockTypes( state ) ).toEqual( [] ); @@ -4329,6 +4425,7 @@ describe( 'selectors', () => { block2: { name: 'core/test-block-b' }, } ) ), + parents: new Map(), controlledInnerBlocks: { 'block2-clientId': true }, }, blockListSettings: { @@ -4371,6 +4468,7 @@ describe( 'selectors', () => { }, ], }, + blockEditingModes: new Map(), }; describe( 'should return empty array', () => { it( 'when no blocks are selected', () => { @@ -4591,6 +4689,7 @@ describe( 'getInserterItems with core blocks prioritization', () => { settings: {}, preferences: {}, blockListSettings: {}, + blockEditingModes: new Map(), }; const items = getInserterItems( state ); const expectedResult = [ From 69b9be1c7d3392a9a316309ed79b4c1ef9f5c115 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 2 Jun 2023 12:52:41 +1000 Subject: [PATCH 11/27] Make getBlockEditingMode() return 'default' when parent is 'contentOnly' (#51185) Change the behaviour of getBlockEditingMode() so that a block's children is considered "content" that is editable when the editing mode is 'contentOnly'. Therefore getBlockEditingMode( foo ) should be 'default' when getBlockEditingMode( getParent( foo ) ) is 'contentOnly'. The benefit of this is that block subtrees can be enabled by simply setting the parent container to 'contentOnly' rather than each individual block to 'default'. This is much more performant as each call to setBlockEditingMode() invalidates the cache. --- .../src/store/private-selectors.js | 69 ++++---- .../src/store/test/private-selectors.js | 164 +++++++++++------- 2 files changed, 136 insertions(+), 97 deletions(-) diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 5cb748cdd8dfa..ff85b4fb3f20b 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -72,45 +72,40 @@ export function getLastInsertedBlocksClientIds( state ) { * @return {BlockEditingMode} The block editing mode. One of `'disabled'`, * `'contentOnly'`, or `'default'`. */ -export const getBlockEditingMode = ( state, clientId = '' ) => { - const explicitEditingMode = getExplicitBlockEditingMode( state, clientId ); - const rootClientId = getBlockRootClientId( state, clientId ); - const templateLock = getTemplateLock( state, rootClientId ); - const name = getBlockName( state, clientId ); - // TODO: Terrible hack! We're calling the global select() function here - // instead of using createRegistrySelector(). The problem with using - // createRegistrySelector() is that then the public block-editor selectors - // (e.g. canInsertBlockTypeUnmemoized) can't call this private block-editor - // selector due to a bug in @wordpress/data. See - // https://github.com/WordPress/gutenberg/pull/50985. - const isContent = - select( blocksStore ).__experimentalHasContentRoleAttribute( name ); - if ( - explicitEditingMode === 'disabled' || - ( templateLock === 'contentOnly' && ! isContent ) - ) { - return 'disabled'; - } - if ( - explicitEditingMode === 'contentOnly' || - ( templateLock === 'contentOnly' && isContent ) - ) { - return 'contentOnly'; - } - return 'default'; -}; - -const getExplicitBlockEditingMode = createSelector( +export const getBlockEditingMode = createSelector( ( state, clientId = '' ) => { - while ( - ! state.blockEditingModes.has( clientId ) && - state.blocks.parents.has( clientId ) - ) { - clientId = state.blocks.parents.get( clientId ); + if ( state.blockEditingModes.has( clientId ) ) { + return state.blockEditingModes.get( clientId ); + } + if ( ! clientId ) { + return 'default'; } - return state.blockEditingModes.get( clientId ) ?? 'default'; + const rootClientId = getBlockRootClientId( state, clientId ); + const templateLock = getTemplateLock( state, rootClientId ); + if ( templateLock === 'contentOnly' ) { + const name = getBlockName( state, clientId ); + // TODO: Terrible hack! We're calling the global select() function + // here instead of using createRegistrySelector(). The problem with + // using createRegistrySelector() is that then the public + // block-editor selectors (e.g. canInsertBlockTypeUnmemoized) can't + // call this private block-editor selector due to a bug in + // @wordpress/data. See + // https://github.com/WordPress/gutenberg/pull/50985. + const isContent = + select( blocksStore ).__experimentalHasContentRoleAttribute( + name + ); + return isContent ? 'contentOnly' : 'disabled'; + } + const parentMode = getBlockEditingMode( state, rootClientId ); + return parentMode === 'contentOnly' ? 'default' : parentMode; }, - ( state ) => [ state.blockEditingModes, state.blocks.parents ] + ( state ) => [ + state.blockEditingModes, + state.blocks.parents, + state.settings.templateLock, + state.blockListSettings, + ] ); /** @@ -134,7 +129,7 @@ export const isBlockSubtreeDisabled = createSelector( ); }; return ( - getExplicitBlockEditingMode( state, clientId ) === 'disabled' && + getBlockEditingMode( state, clientId ) === 'disabled' && getBlockOrder( state, clientId ).every( isChildSubtreeDisabled ) ); }, diff --git a/packages/block-editor/src/store/test/private-selectors.js b/packages/block-editor/src/store/test/private-selectors.js index cbe890a8dc973..ecae342317ce1 100644 --- a/packages/block-editor/src/store/test/private-selectors.js +++ b/packages/block-editor/src/store/test/private-selectors.js @@ -139,69 +139,113 @@ describe( 'private selectors', () => { ).toBe( 'default' ); } ); - [ 'disabled', 'contentOnly' ].forEach( ( mode ) => { - it( `should return ${ mode } if explicitly set`, () => { - const state = { - ...baseState, - blockEditingModes: new Map( [ - [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', mode ], - ] ), - }; - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( mode ); - } ); + it( 'should return disabled if explicitly set', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'b3247f75-fd94-4fef-97f9-5bfd162cc416', 'disabled' ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'disabled' ); + } ); - it( `should return ${ mode } if explicitly set on a parent`, () => { - const state = { - ...baseState, - blockEditingModes: new Map( [ - [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', mode ], - ] ), - }; - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( mode ); - } ); + it( 'should return contentOnly if explicitly set', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ + 'b3247f75-fd94-4fef-97f9-5bfd162cc416', + 'contentOnly', + ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'contentOnly' ); + } ); - it( `should return ${ mode } if overridden by a parent`, () => { - const state = { - ...baseState, - blockEditingModes: new Map( [ - [ '', mode ], - [ - 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', - 'default', - ], - [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', mode ], - ] ), - }; - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( mode ); - } ); + it( 'should return disabled if explicitly set on a parent', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'disabled' ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'disabled' ); + } ); - it( `should return ${ mode } if explicitly set on root`, () => { - const state = { - ...baseState, - blockEditingModes: new Map( [ [ '', mode ] ] ), - }; - expect( - getBlockEditingMode( - state, - 'b3247f75-fd94-4fef-97f9-5bfd162cc416' - ) - ).toBe( mode ); - } ); + it( 'should return default if parent is set to contentOnly', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ + 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', + 'contentOnly', + ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'default' ); + } ); + + it( 'should return disabled if overridden by a parent', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ + [ '', 'disabled' ], + [ 'ef45d5fd-5234-4fd5-ac4f-c3736c7f9337', 'default' ], + [ '9b9c5c3f-2e46-4f02-9e14-9fe9515b958f', 'disabled' ], + ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'disabled' ); + } ); + + it( 'should return disabled if explicitly set on root', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ [ '', 'disabled' ] ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'disabled' ); + } ); + + it( 'should return default if root is contentOnly', () => { + const state = { + ...baseState, + blockEditingModes: new Map( [ [ '', 'contentOnly' ] ] ), + }; + expect( + getBlockEditingMode( + state, + 'b3247f75-fd94-4fef-97f9-5bfd162cc416' + ) + ).toBe( 'default' ); } ); it( 'should return disabled if parent is locked and the block has no content role', () => { From 3c5e91a4fd7b80b29f332573bcf45e07f4a1ad4f Mon Sep 17 00:00:00 2001 From: tellthemachines Date: Fri, 2 Jun 2023 13:58:04 +1000 Subject: [PATCH 12/27] Add block spacing and layout to Post Template (#49050) * Add static column number option * Address feedback and fix Group placeholder * Revert non-responsive option * Try using grid layout in Post Template * Update Post Template to use auto-fill columns * Add block spacing to post template * Show gap control by default * Add unstable columns grid property * Rename layout column attribute. * Add a media query to reproduce current responsiveness * Move layout controls to Post Template block * Reduce max columns to 6. * Remove unstable prefix * fix cols breaking out of container * Try adding deprecation * Fix broken loop * Update fixtures * Code improvements * Add some comments to the CSS. * Add back deleted line * Remove legacy attributes in deprecation * Fix deprecation logic * Update fixtures * Fallback gap for classic themes * fix spacing * match old default value * Update PHP test strings. * Fix tag discrepancy in fixtures --- docs/reference-guides/core-blocks.md | 4 +- lib/block-supports/layout.php | 17 +- lib/class-wp-theme-json-gutenberg.php | 17 +- .../global-styles/use-global-styles-output.js | 6 +- packages/block-editor/src/layouts/grid.js | 32 ++- .../src/post-template/block.json | 12 +- .../block-library/src/post-template/edit.js | 98 ++++++--- .../src/post-template/style.scss | 9 +- packages/block-library/src/query/block.json | 6 - .../block-library/src/query/deprecated.js | 189 ++++++++++++++++-- .../query/edit/inspector-controls/index.js | 6 +- .../src/query/edit/query-content.js | 1 - .../src/query/edit/query-toolbar.js | 25 +-- phpunit/class-wp-theme-json-test.php | 2 +- .../fixtures/blocks/core__query.json | 5 +- .../blocks/core__query.serialized.html | 2 +- .../core__query__deprecated-1.serialized.html | 2 +- ...core__query__deprecated-2-with-colors.json | 11 +- ...__deprecated-2-with-colors.serialized.html | 4 +- .../blocks/core__query__deprecated-2.json | 11 +- .../core__query__deprecated-2.serialized.html | 4 +- .../blocks/core__query__deprecated-3.json | 10 +- .../core__query__deprecated-3.serialized.html | 4 +- .../blocks/core__query__deprecated-4.html | 2 +- .../blocks/core__query__deprecated-4.json | 16 +- .../core__query__deprecated-4.parsed.json | 1 - .../core__query__deprecated-4.serialized.html | 8 +- .../blocks/core__query__deprecated-5.html | 6 + .../blocks/core__query__deprecated-5.json | 50 +++++ .../core__query__deprecated-5.parsed.json | 50 +++++ .../core__query__deprecated-5.serialized.html | 5 + 31 files changed, 468 insertions(+), 147 deletions(-) create mode 100644 test/integration/fixtures/blocks/core__query__deprecated-5.html create mode 100644 test/integration/fixtures/blocks/core__query__deprecated-5.json create mode 100644 test/integration/fixtures/blocks/core__query__deprecated-5.parsed.json create mode 100644 test/integration/fixtures/blocks/core__query__deprecated-5.serialized.html diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index c9fa11d078ef0..bf0d3bf71d06f 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -600,7 +600,7 @@ Contains the block elements used to render a post, like the title, date, feature - **Name:** core/post-template - **Category:** theme - **Parent:** core/query -- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ +- **Supports:** align (full, wide), anchor, color (background, gradients, link, text), spacing (blockGap), typography (fontSize, lineHeight), ~~html~~, ~~reusable~~ - **Attributes:** ## Post Terms @@ -656,7 +656,7 @@ An advanced block that allows displaying post types based on different query par - **Name:** core/query - **Category:** theme - **Supports:** align (full, wide), anchor, ~~html~~ -- **Attributes:** displayLayout, namespace, query, queryId, tagName +- **Attributes:** namespace, query, queryId, tagName ## No results diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php index 5a23573dd1dfa..fb3098d06f743 100644 --- a/lib/block-supports/layout.php +++ b/lib/block-supports/layout.php @@ -280,12 +280,19 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support } } } elseif ( 'grid' === $layout_type ) { - $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem'; + if ( ! empty( $layout['columnCount'] ) ) { + $layout_styles[] = array( + 'selector' => $selector, + 'declarations' => array( 'grid-template-columns' => 'repeat(' . $layout['columnCount'] . ', minmax(0, 1fr))' ), + ); + } else { + $minimum_column_width = ! empty( $layout['minimumColumnWidth'] ) ? $layout['minimumColumnWidth'] : '12rem'; - $layout_styles[] = array( - 'selector' => $selector, - 'declarations' => array( 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))' ), - ); + $layout_styles[] = array( + 'selector' => $selector, + 'declarations' => array( 'grid-template-columns' => 'repeat(auto-fill, minmax(min(' . $minimum_column_width . ', 100%), 1fr))' ), + ); + } if ( $has_block_gap_support && isset( $gap_value ) ) { $combined_gap_value = ''; diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 8fbd3ea6e70df..5cb11c2d7aaf9 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -1073,11 +1073,13 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' } $stylesheet .= $this->get_block_classes( $style_nodes ); } elseif ( in_array( 'base-layout-styles', $types, true ) ) { - $root_selector = static::ROOT_BLOCK_SELECTOR; - $columns_selector = '.wp-block-columns'; + $root_selector = static::ROOT_BLOCK_SELECTOR; + $columns_selector = '.wp-block-columns'; + $post_template_selector = '.wp-block-post-template'; if ( ! empty( $options['scope'] ) ) { - $root_selector = static::scope_selector( $options['scope'], $root_selector ); - $columns_selector = static::scope_selector( $options['scope'], $columns_selector ); + $root_selector = static::scope_selector( $options['scope'], $root_selector ); + $columns_selector = static::scope_selector( $options['scope'], $columns_selector ); + $post_template_selector = static::scope_selector( $options['scope'], $post_template_selector ); } if ( ! empty( $options['root_selector'] ) ) { $root_selector = $options['root_selector']; @@ -1094,6 +1096,11 @@ public function get_stylesheet( $types = array( 'variables', 'styles', 'presets' 'selector' => $columns_selector, 'name' => 'core/columns', ), + array( + 'path' => array( 'styles', 'blocks', 'core/post-template' ), + 'selector' => $post_template_selector, + 'name' => 'core/post-template', + ), ); foreach ( $base_styles_nodes as $base_style_node ) { @@ -1298,7 +1305,7 @@ protected function get_layout_styles( $block_metadata ) { if ( null !== $block_gap_value && false !== $block_gap_value && '' !== $block_gap_value ) { foreach ( $layout_definitions as $layout_definition_key => $layout_definition ) { // Allow outputting fallback gap styles for flex layout type when block gap support isn't available. - if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key ) { + if ( ! $has_block_gap_support && 'flex' !== $layout_definition_key && 'grid' !== $layout_definition_key ) { continue; } diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index 0b82c40f6803f..b20ddd0f02f54 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -460,7 +460,11 @@ export function getLayoutStyles( { Object.values( tree.settings.layout.definitions ).forEach( ( { className, name, spacingStyles } ) => { // Allow outputting fallback gap styles for flex layout type when block gap support isn't available. - if ( ! hasBlockGapSupport && 'flex' !== name ) { + if ( + ! hasBlockGapSupport && + 'flex' !== name && + 'grid' !== name + ) { return; } diff --git a/packages/block-editor/src/layouts/grid.js b/packages/block-editor/src/layouts/grid.js index 69347123fd421..c2cda643baa70 100644 --- a/packages/block-editor/src/layouts/grid.js +++ b/packages/block-editor/src/layouts/grid.js @@ -35,7 +35,9 @@ export default { layout = {}, onChange, } ) { - return ( + return layout?.columnCount ? ( + + ) : ( ); } + +// Enables setting number of grid columns +function GridLayoutColumnsControl( { layout, onChange } ) { + const { columnCount = 3 } = layout; + + return ( + + onChange( { + ...layout, + columnCount: value, + } ) + } + min={ 1 } + max={ 6 } + /> + ); +} diff --git a/packages/block-library/src/post-template/block.json b/packages/block-library/src/post-template/block.json index 6c2056368d644..1a5426253dbf3 100644 --- a/packages/block-library/src/post-template/block.json +++ b/packages/block-library/src/post-template/block.json @@ -20,9 +20,7 @@ "html": false, "align": [ "wide", "full" ], "anchor": true, - "__experimentalLayout": { - "allowEditing": false - }, + "__experimentalLayout": true, "color": { "gradients": true, "link": true, @@ -43,6 +41,14 @@ "__experimentalDefaultControls": { "fontSize": true } + }, + "spacing": { + "blockGap": { + "__experimentalDefault": "1.25em" + }, + "__experimentalDefaultControls": { + "blockGap": true + } } }, "style": "wp-block-post-template", diff --git a/packages/block-library/src/post-template/edit.js b/packages/block-library/src/post-template/edit.js index 1acb3e5719175..c5d59cd0a46db 100644 --- a/packages/block-library/src/post-template/edit.js +++ b/packages/block-library/src/post-template/edit.js @@ -10,14 +10,16 @@ import { memo, useMemo, useState } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { + BlockControls, BlockContextProvider, __experimentalUseBlockPreview as useBlockPreview, useBlockProps, useInnerBlocksProps, store as blockEditorStore, } from '@wordpress/block-editor'; -import { Spinner } from '@wordpress/components'; +import { Spinner, ToolbarGroup } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; +import { list, grid } from '@wordpress/icons'; const TEMPLATE = [ [ 'core/post-title' ], @@ -70,6 +72,7 @@ function PostTemplateBlockPreview( { const MemoizedPostTemplateBlockPreview = memo( PostTemplateBlockPreview ); export default function PostTemplateEdit( { + setAttributes, clientId, context: { query: { @@ -95,11 +98,13 @@ export default function PostTemplateEdit( { } = {}, queryContext = [ { page: 1 } ], templateSlug, - displayLayout: { type: layoutType = 'flex', columns = 1 } = {}, previewPostType, }, + attributes: { layout }, __unstableLayoutClassNames, } ) { + const { type: layoutType, columnCount = 3 } = layout || {}; + const [ { page } ] = queryContext; const [ activeBlockContextId, setActiveBlockContextId ] = useState(); const { posts, blocks } = useSelect( @@ -215,12 +220,9 @@ export default function PostTemplateEdit( { } ) ), [ posts ] ); - const hasLayoutFlex = layoutType === 'flex' && columns > 1; + const blockProps = useBlockProps( { - className: classnames( __unstableLayoutClassNames, { - 'is-flex-container': hasLayoutFlex, - [ `columns-${ columns }` ]: hasLayoutFlex, - } ), + className: classnames( __unstableLayoutClassNames ), } ); if ( ! posts ) { @@ -235,35 +237,67 @@ export default function PostTemplateEdit( { return

{ __( 'No results found.' ) }

; } + const setDisplayLayout = ( newDisplayLayout ) => + setAttributes( { + layout: { ...layout, ...newDisplayLayout }, + } ); + + const displayLayoutControls = [ + { + icon: list, + title: __( 'List view' ), + onClick: () => setDisplayLayout( { type: 'default' } ), + isActive: layoutType === 'default' || layoutType === 'constrained', + }, + { + icon: grid, + title: __( 'Grid view' ), + onClick: () => + setDisplayLayout( { + type: 'grid', + columnCount, + } ), + isActive: layoutType === 'grid', + }, + ]; + // To avoid flicker when switching active block contexts, a preview is rendered // for each block context, but the preview for the active block context is hidden. // This ensures that when it is displayed again, the cached rendering of the // block preview is used, instead of having to re-render the preview from scratch. return ( -
    - { blockContexts && - blockContexts.map( ( blockContext ) => ( - - { blockContext.postId === - ( activeBlockContextId || - blockContexts[ 0 ]?.postId ) ? ( - - ) : null } - - - ) ) } -
+ <> + + + + +
    + { blockContexts && + blockContexts.map( ( blockContext ) => ( + + { blockContext.postId === + ( activeBlockContextId || + blockContexts[ 0 ]?.postId ) ? ( + + ) : null } + + + ) ) } +
+ ); } diff --git a/packages/block-library/src/post-template/style.scss b/packages/block-library/src/post-template/style.scss index b1cdcf385e223..00305a1712336 100644 --- a/packages/block-library/src/post-template/style.scss +++ b/packages/block-library/src/post-template/style.scss @@ -9,7 +9,7 @@ &.wp-block-post-template { background: none; } - + // These rules no longer apply but should be kept for backwards compatibility. &.is-flex-container { flex-direction: row; display: flex; @@ -30,3 +30,10 @@ } } } + +@media ( max-width: $break-small ) { + // Temporary specificity bump until "wp-container" layout specificity is revisited. + .wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid.wp-block-post-template-is-layout-grid { + grid-template-columns: 1fr; + } +} diff --git a/packages/block-library/src/query/block.json b/packages/block-library/src/query/block.json index bcff0e3ac63b1..50733930b78dc 100644 --- a/packages/block-library/src/query/block.json +++ b/packages/block-library/src/query/block.json @@ -32,12 +32,6 @@ "type": "string", "default": "div" }, - "displayLayout": { - "type": "object", - "default": { - "type": "list" - } - }, "namespace": { "type": "string" } diff --git a/packages/block-library/src/query/deprecated.js b/packages/block-library/src/query/deprecated.js index 05ee6217a288d..1b937498d3f22 100644 --- a/packages/block-library/src/query/deprecated.js +++ b/packages/block-library/src/query/deprecated.js @@ -126,6 +126,84 @@ const migrateColors = ( attributes, innerBlocks ) => { const hasSingleInnerGroupBlock = ( innerBlocks = [] ) => innerBlocks.length === 1 && innerBlocks[ 0 ].name === 'core/group'; +const migrateToConstrainedLayout = ( attributes ) => { + const { layout = null } = attributes; + if ( ! layout ) { + return attributes; + } + const { inherit = null, contentSize = null, ...newLayout } = layout; + + if ( inherit || contentSize ) { + return { + ...attributes, + layout: { + ...newLayout, + contentSize, + type: 'constrained', + }, + }; + } +}; + +const findPostTemplateBlock = ( innerBlocks = [] ) => { + let foundBlock = null; + for ( const block of innerBlocks ) { + if ( block.name === 'core/post-template' ) { + foundBlock = block; + break; + } else if ( block.innerBlocks.length ) { + foundBlock = findPostTemplateBlock( block.innerBlocks ); + } + } + return foundBlock; +}; + +const replacePostTemplateBlock = ( innerBlocks = [], replacementBlock ) => { + innerBlocks.forEach( ( block, index ) => { + if ( block.name === 'core/post-template' ) { + innerBlocks.splice( index, 1, replacementBlock ); + } else if ( block.innerBlocks.length ) { + block.innerBlocks = replacePostTemplateBlock( + block.innerBlocks, + replacementBlock + ); + } + } ); + return innerBlocks; +}; + +const migrateDisplayLayout = ( attributes, innerBlocks ) => { + const { displayLayout = null, ...newAttributes } = attributes; + if ( ! displayLayout ) { + return [ attributes, innerBlocks ]; + } + const postTemplateBlock = findPostTemplateBlock( innerBlocks ); + if ( ! postTemplateBlock ) { + return [ attributes, innerBlocks ]; + } + + const { type, columns } = displayLayout; + + // Convert custom displayLayout values to canonical layout types. + const updatedLayoutType = type === 'flex' ? 'grid' : 'default'; + + const newPostTemplateBlock = createBlock( + 'core/post-template', + { + ...postTemplateBlock.attributes, + layout: { + type: updatedLayoutType, + ...( columns && { columnCount: columns } ), + }, + }, + postTemplateBlock.innerBlocks + ); + return [ + newAttributes, + replacePostTemplateBlock( innerBlocks, newPostTemplateBlock ), + ]; +}; + // Version with NO wrapper `div` element. const v1 = { attributes: { @@ -160,13 +238,14 @@ const v1 = { supports: { html: false, }, - migrate( attributes ) { + migrate( attributes, innerBlocks ) { const withTaxQuery = migrateToTaxQuery( attributes ); const { layout, ...restWithTaxQuery } = withTaxQuery; - return { + const newAttributes = { ...restWithTaxQuery, displayLayout: withTaxQuery.layout, }; + return migrateDisplayLayout( newAttributes, innerBlocks ); }, save() { return ; @@ -221,7 +300,16 @@ const v2 = { categoryIds || tagIds, migrate( attributes, innerBlocks ) { const withTaxQuery = migrateToTaxQuery( attributes ); - return migrateColors( withTaxQuery, innerBlocks ); + const [ withColorAttributes, withColorInnerBlocks ] = migrateColors( + withTaxQuery, + innerBlocks + ); + const withConstrainedLayoutAttributes = + migrateToConstrainedLayout( withColorAttributes ); + return migrateDisplayLayout( + withConstrainedLayoutAttributes, + withColorInnerBlocks + ); }, save( { attributes: { tagName: Tag = 'div' } } ) { const blockProps = useBlockProps.save(); @@ -291,7 +379,18 @@ const v3 = { style?.elements?.link ); }, - migrate: migrateColors, + migrate( attributes, innerBlocks ) { + const [ withColorAttributes, withColorInnerBlocks ] = migrateColors( + attributes, + innerBlocks + ); + const withConstrainedLayoutAttributes = + migrateToConstrainedLayout( withColorAttributes ); + return migrateDisplayLayout( + withConstrainedLayoutAttributes, + withColorInnerBlocks + ); + }, save( { attributes: { tagName: Tag = 'div' } } ) { const blockProps = useBlockProps.save(); const innerBlocksProps = useInnerBlocksProps.save( blockProps ); @@ -355,26 +454,72 @@ const v4 = { return ; }, isEligible: ( { layout } ) => - ! layout || - layout.inherit || - ( layout.contentSize && layout.type !== 'constrained' ), - migrate: ( attributes ) => { - const { layout = null } = attributes; - if ( ! layout ) { - return attributes; - } - if ( layout.inherit || layout.contentSize ) { - return { - ...attributes, - layout: { - ...layout, - type: 'constrained', - }, - }; - } + layout?.inherit || + ( layout?.contentSize && layout?.type !== 'constrained' ), + migrate( attributes, innerBlocks ) { + const withConstrainedLayoutAttributes = + migrateToConstrainedLayout( attributes ); + return migrateDisplayLayout( + withConstrainedLayoutAttributes, + innerBlocks + ); + }, +}; + +const v5 = { + attributes: { + queryId: { + type: 'number', + }, + query: { + type: 'object', + default: { + perPage: null, + pages: 0, + offset: 0, + postType: 'post', + order: 'desc', + orderBy: 'date', + author: '', + search: '', + exclude: [], + sticky: '', + inherit: true, + taxQuery: null, + parents: [], + }, + }, + tagName: { + type: 'string', + default: 'div', + }, + displayLayout: { + type: 'object', + default: { + type: 'list', + }, + }, + namespace: { + type: 'string', + }, + }, + supports: { + align: [ 'wide', 'full' ], + anchor: true, + html: false, + __experimentalLayout: true, + }, + save( { attributes: { tagName: Tag = 'div' } } ) { + const blockProps = useBlockProps.save(); + const innerBlocksProps = useInnerBlocksProps.save( blockProps ); + return ; + }, + isEligible: ( { displayLayout } ) => { + return !! displayLayout; }, + migrate: migrateDisplayLayout, }; -const deprecated = [ v4, v3, v2, v1 ]; +const deprecated = [ v5, v4, v3, v2, v1 ]; export default deprecated; diff --git a/packages/block-library/src/query/edit/inspector-controls/index.js b/packages/block-library/src/query/edit/inspector-controls/index.js index 2222f7d2d1eff..0061429f4af3b 100644 --- a/packages/block-library/src/query/edit/inspector-controls/index.js +++ b/packages/block-library/src/query/edit/inspector-controls/index.js @@ -101,7 +101,7 @@ export default function QueryInspectorControls( props ) { const showInheritControl = isControlAllowed( allowedControls, 'inherit' ); const showPostTypeControl = ! inherit && isControlAllowed( allowedControls, 'postType' ); - const showColumnsControl = displayLayout?.type === 'flex'; + const showColumnsControl = false; const showOrderControl = ! inherit && isControlAllowed( allowedControls, 'order' ); const showStickyControl = @@ -169,7 +169,9 @@ export default function QueryInspectorControls( props ) { label={ __( 'Columns' ) } value={ displayLayout.columns } onChange={ ( value ) => - setDisplayLayout( { columns: value } ) + setDisplayLayout( { + columns: value, + } ) } min={ 2 } max={ Math.max( 6, displayLayout.columns ) } diff --git a/packages/block-library/src/query/edit/query-content.js b/packages/block-library/src/query/edit/query-content.js index 9563673917f57..567199f38e1b3 100644 --- a/packages/block-library/src/query/edit/query-content.js +++ b/packages/block-library/src/query/edit/query-content.js @@ -107,7 +107,6 @@ export default function QueryContent( { clientId={ clientId } attributes={ attributes } setQuery={ updateQuery } - setDisplayLayout={ updateDisplayLayout } openPatternSelectionModal={ openPatternSelectionModal } /> diff --git a/packages/block-library/src/query/edit/query-toolbar.js b/packages/block-library/src/query/edit/query-toolbar.js index 1d079eb399fb8..7b02290ae4c76 100644 --- a/packages/block-library/src/query/edit/query-toolbar.js +++ b/packages/block-library/src/query/edit/query-toolbar.js @@ -10,7 +10,7 @@ import { } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; import { __ } from '@wordpress/i18n'; -import { settings, list, grid } from '@wordpress/icons'; +import { settings } from '@wordpress/icons'; /** * Internal dependencies @@ -18,9 +18,8 @@ import { settings, list, grid } from '@wordpress/icons'; import { usePatterns } from '../utils'; export default function QueryToolbar( { - attributes: { query, displayLayout }, + attributes: { query }, setQuery, - setDisplayLayout, openPatternSelectionModal, name, clientId, @@ -30,24 +29,7 @@ export default function QueryToolbar( { QueryToolbar, 'blocks-query-pagination-max-page-input' ); - const displayLayoutControls = [ - { - icon: list, - title: __( 'List view' ), - onClick: () => setDisplayLayout( { type: 'list' } ), - isActive: displayLayout?.type === 'list', - }, - { - icon: grid, - title: __( 'Grid view' ), - onClick: () => - setDisplayLayout( { - type: 'flex', - columns: displayLayout?.columns || 3, - } ), - isActive: displayLayout?.type === 'flex', - }, - ]; + return ( <> { ! query.inherit && ( @@ -144,7 +126,6 @@ export default function QueryToolbar( { ) } - ); } diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index 7d63f62a2c1f0..043cd4916eac8 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -215,7 +215,7 @@ public function test_get_stylesheet_generates_base_fallback_gap_layout_styles( $ // Note the `base-layout-styles` includes a fallback gap for the Columns block for backwards compatibility. $this->assertEquals( - ':where(.is-layout-flex){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}', + ':where(.is-layout-flex){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}', $stylesheet ); } diff --git a/test/integration/fixtures/blocks/core__query.json b/test/integration/fixtures/blocks/core__query.json index 4c7ce920a0450..fb545c16ea695 100644 --- a/test/integration/fixtures/blocks/core__query.json +++ b/test/integration/fixtures/blocks/core__query.json @@ -18,10 +18,7 @@ "taxQuery": null, "parents": [] }, - "tagName": "div", - "displayLayout": { - "type": "list" - } + "tagName": "div" }, "innerBlocks": [] } diff --git a/test/integration/fixtures/blocks/core__query.serialized.html b/test/integration/fixtures/blocks/core__query.serialized.html index 049ea7dd2bb73..3bc4085f4f090 100644 --- a/test/integration/fixtures/blocks/core__query.serialized.html +++ b/test/integration/fixtures/blocks/core__query.serialized.html @@ -1,3 +1,3 @@ - +
diff --git a/test/integration/fixtures/blocks/core__query__deprecated-1.serialized.html b/test/integration/fixtures/blocks/core__query__deprecated-1.serialized.html index 39f889cfae97e..915726d992a8f 100644 --- a/test/integration/fixtures/blocks/core__query__deprecated-1.serialized.html +++ b/test/integration/fixtures/blocks/core__query__deprecated-1.serialized.html @@ -1,3 +1,3 @@ - +
diff --git a/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.json b/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.json index 82bc41a40fb1b..8a048667f55af 100644 --- a/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.json +++ b/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.json @@ -21,10 +21,7 @@ "post_tag": [ 6 ] } }, - "tagName": "div", - "displayLayout": { - "type": "list" - } + "tagName": "div" }, "innerBlocks": [ { @@ -50,7 +47,11 @@ { "name": "core/post-template", "isValid": true, - "attributes": {}, + "attributes": { + "layout": { + "type": "default" + } + }, "innerBlocks": [ { "name": "core/post-title", diff --git a/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.serialized.html b/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.serialized.html index f86b4f26ecc1d..b9e6b50deb067 100644 --- a/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.serialized.html +++ b/test/integration/fixtures/blocks/core__query__deprecated-2-with-colors.serialized.html @@ -1,6 +1,6 @@ - +
- diff --git a/test/integration/fixtures/blocks/core__query__deprecated-2.json b/test/integration/fixtures/blocks/core__query__deprecated-2.json index a63ad1c007b6b..b0a1aea41ea50 100644 --- a/test/integration/fixtures/blocks/core__query__deprecated-2.json +++ b/test/integration/fixtures/blocks/core__query__deprecated-2.json @@ -21,16 +21,17 @@ "post_tag": [ 6 ] } }, - "tagName": "div", - "displayLayout": { - "type": "list" - } + "tagName": "div" }, "innerBlocks": [ { "name": "core/post-template", "isValid": true, - "attributes": {}, + "attributes": { + "layout": { + "type": "default" + } + }, "innerBlocks": [ { "name": "core/post-title", diff --git a/test/integration/fixtures/blocks/core__query__deprecated-2.serialized.html b/test/integration/fixtures/blocks/core__query__deprecated-2.serialized.html index 5804c54e577f1..2016bea963592 100644 --- a/test/integration/fixtures/blocks/core__query__deprecated-2.serialized.html +++ b/test/integration/fixtures/blocks/core__query__deprecated-2.serialized.html @@ -1,5 +1,5 @@ - -
+ +
diff --git a/test/integration/fixtures/blocks/core__query__deprecated-3.json b/test/integration/fixtures/blocks/core__query__deprecated-3.json index 6b3327eacdc3f..bb9a9de34a4b7 100644 --- a/test/integration/fixtures/blocks/core__query__deprecated-3.json +++ b/test/integration/fixtures/blocks/core__query__deprecated-3.json @@ -18,10 +18,6 @@ "inherit": false }, "tagName": "div", - "displayLayout": { - "type": "flex", - "columns": 3 - }, "align": "wide" }, "innerBlocks": [ @@ -49,7 +45,11 @@ "name": "core/post-template", "isValid": true, "attributes": { - "fontSize": "large" + "fontSize": "large", + "layout": { + "type": "grid", + "columnCount": 3 + } }, "innerBlocks": [ { diff --git a/test/integration/fixtures/blocks/core__query__deprecated-3.serialized.html b/test/integration/fixtures/blocks/core__query__deprecated-3.serialized.html index edbf5b1a0557b..86c87dde71c3b 100644 --- a/test/integration/fixtures/blocks/core__query__deprecated-3.serialized.html +++ b/test/integration/fixtures/blocks/core__query__deprecated-3.serialized.html @@ -1,6 +1,6 @@ - +
-