From 9d450540489a809ee4d40037ace9b6305b3ed8e3 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Fri, 12 Apr 2024 15:40:34 +0800 Subject: [PATCH 01/18] Add __default binding for pattern overrides --- .../src/hooks/use-bindings-attributes.js | 33 +++++++++++++---- packages/block-library/src/block/edit.js | 3 +- packages/block-library/src/block/index.php | 37 ++++++++++++++++++- .../editor/src/bindings/pattern-overrides.js | 11 +++++- .../components/pattern-overrides-controls.js | 31 ++++++++++++---- .../editor/various/pattern-overrides.spec.js | 16 ++++---- 6 files changed, 103 insertions(+), 28 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index b7a4ca0379dd1b..3e0f7016a3b6de 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -29,6 +29,8 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], }; +const DEFAULT_ATTRIBUTES = '__default'; + /** * Based on the given block name, * check if it is possible to bind the block. @@ -68,11 +70,20 @@ export const withBlockBindingSupport = createHigherOrderComponent( return; } + const bindingsEntries = Object.entries( bindings ); const attributes = {}; - for ( const [ attributeName, boundAttribute ] of Object.entries( - bindings - ) ) { + if ( bindings[ DEFAULT_ATTRIBUTES ] !== undefined ) { + const defaultAttributes = BLOCK_BINDINGS_ALLOWED_BLOCKS[ name ]; + bindingsEntries.push( + ...defaultAttributes.map( ( attributeName ) => [ + attributeName, + bindings[ DEFAULT_ATTRIBUTES ], + ] ) + ); + } + + for ( const [ attributeName, boundAttribute ] of bindingsEntries ) { const source = sources[ boundAttribute.source ]; if ( ! source?.getValue || @@ -122,14 +133,17 @@ export const withBlockBindingSupport = createHigherOrderComponent( keptAttributes ) ) { if ( - ! bindings[ attributeName ] || - ! canBindAttribute( name, attributeName ) + ( ! bindings[ attributeName ] || + ! canBindAttribute( name, attributeName ) ) && + ! bindings[ DEFAULT_ATTRIBUTES ] ) { continue; } - const source = - sources[ bindings[ attributeName ].source ]; + const binding = + bindings[ attributeName ] ?? + bindings[ DEFAULT_ATTRIBUTES ]; + const source = sources[ binding?.source ]; if ( ! source?.setValue && ! source?.setValues ) { continue; } @@ -157,12 +171,15 @@ export const withBlockBindingSupport = createHigherOrderComponent( attributeName, value, ] of Object.entries( attributes ) ) { + const binding = + bindings[ attributeName ] ?? + bindings[ DEFAULT_ATTRIBUTES ]; source.setValue( { registry, context, clientId, attributeName, - args: bindings[ attributeName ].args, + args: binding.args, value, } ); } diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index a4f054db46665a..56596da7c3de93 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -40,7 +40,8 @@ import { name as patternBlockName } from './index'; import { unlock } from '../lock-unlock'; const { useLayoutClasses } = unlock( blockEditorPrivateApis ); -const { isOverridableBlock } = unlock( patternsPrivateApis ); +const { isOverridableBlock, PARTIAL_SYNCING_SUPPORTED_BLOCKS } = + unlock( patternsPrivateApis ); const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 9403205c186596..cb1618cb337b65 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -78,13 +78,48 @@ function render_block_core_block( $attributes ) { * filter so that it is available when a pattern's inner blocks are * rendering via do_blocks given it only receives the inner content. */ - $has_pattern_overrides = isset( $attributes['content'] ); + $has_pattern_overrides = isset( $attributes['content'] ) && null !== get_block_bindings_source( 'core/pattern-overrides' ); if ( $has_pattern_overrides ) { $filter_block_context = static function ( $context ) use ( $attributes ) { $context['pattern/overrides'] = $attributes['content']; return $context; }; add_filter( 'render_block_context', $filter_block_context, 1 ); + + // Add bindings for the default pattern overrides source. + $apply_default_pattern_overrides_bindings = static function ( $parsed_block ) { + $supported_block_attrs = array( + 'core/paragraph' => array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'id', 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ), + ); + + if ( + // Return early if the block isn't one of the supported block types, + ! isset( $supported_block_attrs[ $parsed_block['blockName'] ] ) || + // or doesn't have a name, + ! isset( $parsed_block['attrs']['metadata']['name'] ) || + // or doesn't have the default binding. + empty( $parsed_block['attrs']['metadata']['bindings']['__default'] ) || + // or the default binding isn't the pattern overrides source. + 'core/pattern-overrides' !== $parsed_block['attrs']['metadata']['bindings']['__default']['source'] + ) { + return $parsed_block; + } + + $bindings = array(); + foreach ( $supported_block_attrs[ $parsed_block['blockName'] ] as $attribute_name ) { + $bindings[ $attribute_name ] = array( 'source' => 'core/pattern-overrides' ); + } + $parsed_block['attrs']['metadata']['bindings'] = array_merge( + $bindings, + $parsed_block['attrs']['metadata']['bindings'] + ); + + return $parsed_block; + }; + add_filter( 'render_block_data', $apply_default_pattern_overrides_bindings, 10, 1 ); } $content = do_blocks( $content ); diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js index 4065cefe362808..d9d94ba2a39556 100644 --- a/packages/editor/src/bindings/pattern-overrides.js +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -5,6 +5,7 @@ import { _x } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; const CONTENT = 'content'; +const DEFAULT_ATTRIBUTES = '__default'; export default { name: 'core/pattern-overrides', @@ -19,10 +20,16 @@ export default { true ); - const overridableValue = + const overriddenAttributes = getBlockAttributes( patternClientId )?.[ CONTENT ]?.[ currentBlockAttributes?.metadata?.name - ]?.[ attributeName ]; + ]; + const overridableValue = overriddenAttributes?.[ attributeName ]; + const defaultValue = overriddenAttributes[ DEFAULT_ATTRIBUTES ]; + + if ( defaultValue !== undefined ) { + return defaultValue; + } // If there is no pattern client ID, or it is not overwritten, return the default value. if ( ! patternClientId || overridableValue === undefined ) { diff --git a/packages/patterns/src/components/pattern-overrides-controls.js b/packages/patterns/src/components/pattern-overrides-controls.js index 3ece910a0df47c..22f30163c8334c 100644 --- a/packages/patterns/src/components/pattern-overrides-controls.js +++ b/packages/patterns/src/components/pattern-overrides-controls.js @@ -20,6 +20,7 @@ import { function removeBindings( bindings, syncedAttributes ) { let updatedBindings = {}; + // Back-compat: Remove existing duplicate bindings. for ( const attributeName of syncedAttributes ) { // Omit any bindings that's not the same source from the `updatedBindings` object. if ( @@ -37,12 +38,17 @@ function removeBindings( bindings, syncedAttributes ) { } function addBindings( bindings, syncedAttributes ) { - const updatedBindings = { ...bindings }; + const updatedBindings = { + ...bindings, + __default: { source: PATTERN_OVERRIDES_BINDING_SOURCE }, + }; + // Back-compat: Remove existing duplicate bindings. for ( const attributeName of syncedAttributes ) { - if ( ! bindings?.[ attributeName ] ) { - updatedBindings[ attributeName ] = { - source: PATTERN_OVERRIDES_BINDING_SOURCE, - }; + if ( + updatedBindings[ attributeName ]?.source === + PATTERN_OVERRIDES_BINDING_SOURCE + ) { + delete updatedBindings[ attributeName ]; } } return updatedBindings; @@ -60,9 +66,18 @@ function PatternOverridesControls( { attributes, name, setAttributes } ) { ( attributeName ) => attributes.metadata?.bindings?.[ attributeName ]?.source ); - const isConnectedToOtherSources = attributeSources.every( - ( source ) => source && source !== 'core/pattern-overrides' - ); + const defaultBindings = attributes.metadata?.bindings?.__default; + const allowOverrides = + defaultBindings?.source === PATTERN_OVERRIDES_BINDING_SOURCE || + attributeSources.some( + ( source ) => source === PATTERN_OVERRIDES_BINDING_SOURCE + ); + const isConnectedToOtherSources = + ( defaultBindings?.source && + defaultBindings.source !== PATTERN_OVERRIDES_BINDING_SOURCE ) || + attributeSources.every( + ( source ) => source && source !== PATTERN_OVERRIDES_BINDING_SOURCE + ); function updateBindings( isChecked, customName ) { const prevBindings = attributes?.metadata?.bindings; diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 976f1c378daa97..5c86810fb4aa32 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -105,7 +105,7 @@ test.describe( 'Pattern Overrides', () => { metadata: { name: editableParagraphName, bindings: { - content: { + __default: { source: 'core/pattern-overrides', }, }, @@ -234,7 +234,7 @@ test.describe( 'Pattern Overrides', () => { const paragraphName = 'paragraph-name'; const { id } = await requestUtils.createBlock( { title: 'Pattern', - content: ` + content: `

Editable

`, status: 'publish', @@ -324,7 +324,7 @@ test.describe( 'Pattern Overrides', () => { const { id } = await requestUtils.createBlock( { title: 'Button with target', content: ` -
+ `, @@ -434,14 +434,14 @@ test.describe( 'Pattern Overrides', () => { const headingName = 'Editable heading'; const innerPattern = await requestUtils.createBlock( { title: 'Inner Pattern', - content: ` + content: `

Inner paragraph

`, status: 'publish', } ); const outerPattern = await requestUtils.createBlock( { title: 'Outer Pattern', - content: ` + content: `

Outer heading

`, @@ -535,10 +535,10 @@ test.describe( 'Pattern Overrides', () => { const paragraphName = 'Editable paragraph'; const { id } = await requestUtils.createBlock( { title: 'Pattern', - content: ` + content: `

Heading

- +

Paragraph

`, status: 'publish', @@ -694,7 +694,7 @@ test.describe( 'Pattern Overrides', () => { ); const { id } = await requestUtils.createBlock( { title: 'Pattern', - content: ` + content: `
`, status: 'publish', From a099f03e638d93149b7397d77e222c7092f374bb Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 16 Apr 2024 18:11:36 +0800 Subject: [PATCH 02/18] Allow overrides of the default list --- packages/block-library/src/block/edit.js | 11 +--- packages/patterns/src/api/index.js | 45 ++++++++++++++++ packages/patterns/src/api/test/index.js | 69 ++++++++++++++++++++++++ packages/patterns/src/private-apis.js | 8 ++- 4 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 packages/patterns/src/api/test/index.js diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 56596da7c3de93..8d4147cb318555 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -40,7 +40,7 @@ import { name as patternBlockName } from './index'; import { unlock } from '../lock-unlock'; const { useLayoutClasses } = unlock( blockEditorPrivateApis ); -const { isOverridableBlock, PARTIAL_SYNCING_SUPPORTED_BLOCKS } = +const { isOverridableBlock, hasOverridableBlocks } = unlock( patternsPrivateApis ); const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; @@ -74,15 +74,6 @@ const useInferredLayout = ( blocks, parentLayout ) => { }, [ blocks, parentLayout ] ); }; -function hasOverridableBlocks( blocks ) { - return blocks.some( ( block ) => { - if ( isOverridableBlock( block ) ) { - return true; - } - return hasOverridableBlocks( block.innerBlocks ); - } ); -} - function setBlockEditMode( setEditMode, blocks, mode ) { blocks.forEach( ( block ) => { const editMode = diff --git a/packages/patterns/src/api/index.js b/packages/patterns/src/api/index.js index 448001f891fbae..a6a4a08bb324e6 100644 --- a/packages/patterns/src/api/index.js +++ b/packages/patterns/src/api/index.js @@ -22,3 +22,48 @@ export function isOverridableBlock( block ) { ) ); } + +/** + * Determines whether the blocks list has overridable blocks. + * + * @param {WPBlock[]} blocks The blocks list. + * + * @return {boolean} `true` if the list has overridable blocks, `false` otherwise. + */ +export function hasOverridableBlocks( blocks ) { + return blocks.some( ( block ) => { + if ( isOverridableBlock( block ) ) return true; + return hasOverridableBlocks( block.innerBlocks ); + } ); +} + +/** + * Get the overridable attributes for the give block. + * + * @param {WPBlock} block The block to test. + * + * @return {string[]} The attribute names in an array. + */ +export function getOverridableAttributes( block ) { + const set = new Set(); + // get default attributes for the block. + if ( block.attributes.metadata.bindings.__default ) { + PARTIAL_SYNCING_SUPPORTED_BLOCKS[ block.name ].forEach( + ( attribute ) => { + set.add( attribute ); + } + ); + } + // Any additional attributes and overrides. + for ( const [ attributeKey, binding ] of Object.entries( + block.attributes.metadata.bindings + ) ) { + if ( attributeKey === '__default' ) continue; + if ( binding.source === 'core/pattern-overrides' ) { + set.add( attributeKey ); + } else { + set.delete( attributeKey ); + } + } + return Array.from( set ); +} diff --git a/packages/patterns/src/api/test/index.js b/packages/patterns/src/api/test/index.js new file mode 100644 index 00000000000000..81224da8170a1d --- /dev/null +++ b/packages/patterns/src/api/test/index.js @@ -0,0 +1,69 @@ +/** + * Internal dependencies + */ +import { getOverridableAttributes } from '../'; +import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../../constants'; + +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +describe( 'getOverridableAttributes', () => { + beforeAll( () => { + registerCoreBlocks(); + } ); + + it( 'should return an array of overridable attributes', () => { + const block = createBlock( 'core/paragraph', { + metadata: { + bindings: { + content: { source: 'core/pattern-overrides' }, + }, + }, + } ); + + const attributes = getOverridableAttributes( block ); + + expect( attributes ).toEqual( [ 'content' ] ); + } ); + + it( 'should return the default list for core blocks', () => { + const block = createBlock( 'core/image', { + metadata: { + bindings: { + __default: { source: 'core/pattern-overrides' }, + }, + }, + } ); + + const attributes = getOverridableAttributes( block ); + + expect( attributes ).toEqual( + PARTIAL_SYNCING_SUPPORTED_BLOCKS[ 'core/image' ] + ); + } ); + + it( 'should allow overrides of the default list', () => { + const block = createBlock( 'core/image', { + metadata: { + bindings: { + __default: { source: 'core/pattern-overrides' }, + alt: { source: 'core/post-meta' }, + width: { source: 'core/pattern-overrides' }, + }, + }, + } ); + + const attributes = getOverridableAttributes( block ); + + const defaultAttributes = new Set( + PARTIAL_SYNCING_SUPPORTED_BLOCKS[ 'core/image' ] + ); + defaultAttributes.delete( 'alt' ); + defaultAttributes.add( 'width' ); + + expect( attributes ).toEqual( Array.from( defaultAttributes ) ); + } ); +} ); diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index 05417de2b2c669..5d11e6aecdded1 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -11,7 +11,11 @@ import { default as DuplicatePatternModal, useDuplicatePatternProps, } from './components/duplicate-pattern-modal'; -import { isOverridableBlock } from './api'; +import { + isOverridableBlock, + hasOverridableBlocks, + getOverridableAttributes, +} from './api'; import RenamePatternModal from './components/rename-pattern-modal'; import PatternsMenuItems from './components'; import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; @@ -34,6 +38,8 @@ lock( privateApis, { CreatePatternModalContents, DuplicatePatternModal, isOverridableBlock, + hasOverridableBlocks, + getOverridableAttributes, useDuplicatePatternProps, RenamePatternModal, PatternsMenuItems, From bbca6e9700cef0028d8ca10f408f4c7b7f4a559f Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Wed, 17 Apr 2024 12:24:10 +0800 Subject: [PATCH 03/18] Use convert-legacy-block and add e2e test --- .../src/api/parser/convert-legacy-block.js | 22 ++++-- .../components/pattern-overrides-controls.js | 58 +++----------- .../editor/various/pattern-overrides.spec.js | 75 +++++++++++++++++++ 3 files changed, 104 insertions(+), 51 deletions(-) diff --git a/packages/blocks/src/api/parser/convert-legacy-block.js b/packages/blocks/src/api/parser/convert-legacy-block.js index 8396b98109792f..aeb4363c0e24f4 100644 --- a/packages/blocks/src/api/parser/convert-legacy-block.js +++ b/packages/blocks/src/api/parser/convert-legacy-block.js @@ -88,25 +88,37 @@ export function convertLegacyBlockNameAndAttributes( name, attributes ) { ( name === 'core/paragraph' || name === 'core/heading' || name === 'core/image' || - name === 'core/button' ) + name === 'core/button' ) && + newAttributes.metadata.bindings.__default?.source !== + 'core/pattern-overrides' ) { const bindings = [ 'content', 'url', 'title', + 'id', 'alt', 'text', 'linkTarget', ]; + // Delete any existing individual bindings and add a default binding. + // It was only possible to add all the default attributes through the UI, + // So as soon as we find an attribute, we can assume all default attributes are overridable. + let hasPatternOverrides = false; bindings.forEach( ( binding ) => { if ( - newAttributes.metadata.bindings[ binding ]?.source?.name === - 'pattern_attributes' + newAttributes.metadata.bindings[ binding ]?.source === + 'core/pattern-overrides' ) { - newAttributes.metadata.bindings[ binding ].source = - 'core/pattern-overrides'; + hasPatternOverrides = true; + delete newAttributes.metadata.bindings[ binding ]; } } ); + if ( hasPatternOverrides ) { + newAttributes.metadata.bindings.__default = { + source: 'core/pattern-overrides', + }; + } } } return [ name, newAttributes ]; diff --git a/packages/patterns/src/components/pattern-overrides-controls.js b/packages/patterns/src/components/pattern-overrides-controls.js index 22f30163c8334c..b25c39f644fc7c 100644 --- a/packages/patterns/src/components/pattern-overrides-controls.js +++ b/packages/patterns/src/components/pattern-overrides-controls.js @@ -9,81 +9,47 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { - PARTIAL_SYNCING_SUPPORTED_BLOCKS, - PATTERN_OVERRIDES_BINDING_SOURCE, -} from '../constants'; +import { PATTERN_OVERRIDES_BINDING_SOURCE } from '../constants'; import { AllowOverridesModal, DisallowOverridesModal, } from './allow-overrides-modal'; -function removeBindings( bindings, syncedAttributes ) { - let updatedBindings = {}; - // Back-compat: Remove existing duplicate bindings. - for ( const attributeName of syncedAttributes ) { - // Omit any bindings that's not the same source from the `updatedBindings` object. - if ( - bindings?.[ attributeName ]?.source !== - PATTERN_OVERRIDES_BINDING_SOURCE && - bindings?.[ attributeName ]?.source !== undefined - ) { - updatedBindings[ attributeName ] = bindings[ attributeName ]; - } - } +function removeBindings( bindings ) { + let updatedBindings = { ...bindings }; + delete updatedBindings.__default; if ( ! Object.keys( updatedBindings ).length ) { updatedBindings = undefined; } return updatedBindings; } -function addBindings( bindings, syncedAttributes ) { - const updatedBindings = { +function addBindings( bindings ) { + return { ...bindings, __default: { source: PATTERN_OVERRIDES_BINDING_SOURCE }, }; - // Back-compat: Remove existing duplicate bindings. - for ( const attributeName of syncedAttributes ) { - if ( - updatedBindings[ attributeName ]?.source === - PATTERN_OVERRIDES_BINDING_SOURCE - ) { - delete updatedBindings[ attributeName ]; - } - } - return updatedBindings; } -function PatternOverridesControls( { attributes, name, setAttributes } ) { +function PatternOverridesControls( { attributes, setAttributes } ) { const controlId = useId(); const [ showAllowOverridesModal, setShowAllowOverridesModal ] = useState( false ); const [ showDisallowOverridesModal, setShowDisallowOverridesModal ] = useState( false ); - const syncedAttributes = PARTIAL_SYNCING_SUPPORTED_BLOCKS[ name ]; - const attributeSources = syncedAttributes.map( - ( attributeName ) => - attributes.metadata?.bindings?.[ attributeName ]?.source - ); const defaultBindings = attributes.metadata?.bindings?.__default; const allowOverrides = - defaultBindings?.source === PATTERN_OVERRIDES_BINDING_SOURCE || - attributeSources.some( - ( source ) => source === PATTERN_OVERRIDES_BINDING_SOURCE - ); + defaultBindings?.source === PATTERN_OVERRIDES_BINDING_SOURCE; const isConnectedToOtherSources = - ( defaultBindings?.source && - defaultBindings.source !== PATTERN_OVERRIDES_BINDING_SOURCE ) || - attributeSources.every( - ( source ) => source && source !== PATTERN_OVERRIDES_BINDING_SOURCE - ); + defaultBindings?.source && + defaultBindings.source !== PATTERN_OVERRIDES_BINDING_SOURCE; function updateBindings( isChecked, customName ) { const prevBindings = attributes?.metadata?.bindings; const updatedBindings = isChecked - ? addBindings( prevBindings, syncedAttributes ) - : removeBindings( prevBindings, syncedAttributes ); + ? addBindings( prevBindings ) + : removeBindings( prevBindings ); const updatedMetadata = { ...attributes.metadata, diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 5c86810fb4aa32..81d1915aef6fe5 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -878,4 +878,79 @@ test.describe( 'Pattern Overrides', () => { editorSettings.getByRole( 'button', { name: 'Enable overrides' } ) ).toBeHidden(); } ); + + // @see https://github.com/WordPress/gutenberg/pull/60694 + test( 'handles back-compat from individual attributes to __default', async ( { + page, + admin, + requestUtils, + editor, + } ) => { + const imageName = 'Editable image'; + const TEST_IMAGE_FILE_PATH = path.resolve( + __dirname, + '../../../assets/10x10_e2e_test_image_z9T8jK.png' + ); + const { id } = await requestUtils.createBlock( { + title: 'Pattern', + content: ` +
+`, + status: 'publish', + } ); + + await admin.createNewPost(); + + await editor.insertBlock( { + name: 'core/block', + attributes: { ref: id }, + } ); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/block', + attributes: { ref: id }, + innerBlocks: [ + { + name: 'core/image', + attributes: { + metadata: { + name: imageName, + bindings: { + __default: { + source: 'core/pattern-overrides', + }, + }, + }, + }, + }, + ], + }, + ] ); + + const imageBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Image', + } ); + await editor.selectBlocks( imageBlock ); + await imageBlock + .getByTestId( 'form-file-upload-input' ) + .setInputFiles( TEST_IMAGE_FILE_PATH ); + await expect( imageBlock.getByRole( 'img' ) ).toHaveCount( 1 ); + await expect( imageBlock.getByRole( 'img' ) ).toHaveAttribute( + 'src', + /\/wp-content\/uploads\// + ); + await editor.showBlockToolbar(); + await editor.clickBlockToolbarButton( 'Alt' ); + await page + .getByRole( 'textbox', { name: 'alternative text' } ) + .fill( 'Test Image' ); + + const postId = await editor.publishPost(); + + await page.goto( `/?p=${ postId }` ); + await expect( + page.getByRole( 'img', { name: 'Test Image' } ) + ).toHaveAttribute( 'src', /\/wp-content\/uploads\// ); + } ); } ); From e89587a2cfd6f3320cdaa68d2f712afa0aa761fd Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Thu, 18 Apr 2024 10:18:44 +0800 Subject: [PATCH 04/18] Fix allow overrides check --- .../src/components/pattern-overrides-controls.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/patterns/src/components/pattern-overrides-controls.js b/packages/patterns/src/components/pattern-overrides-controls.js index b25c39f644fc7c..9869c5b072c856 100644 --- a/packages/patterns/src/components/pattern-overrides-controls.js +++ b/packages/patterns/src/components/pattern-overrides-controls.js @@ -38,9 +38,10 @@ function PatternOverridesControls( { attributes, setAttributes } ) { const [ showDisallowOverridesModal, setShowDisallowOverridesModal ] = useState( false ); + const hasName = !! attributes.metadata?.name; const defaultBindings = attributes.metadata?.bindings?.__default; const allowOverrides = - defaultBindings?.source === PATTERN_OVERRIDES_BINDING_SOURCE; + hasName && defaultBindings?.source === PATTERN_OVERRIDES_BINDING_SOURCE; const isConnectedToOtherSources = defaultBindings?.source && defaultBindings.source !== PATTERN_OVERRIDES_BINDING_SOURCE; @@ -70,13 +71,6 @@ function PatternOverridesControls( { attributes, setAttributes } ) { return null; } - const hasName = !! attributes.metadata?.name; - const allowOverrides = - hasName && - attributeSources.some( - ( source ) => source === PATTERN_OVERRIDES_BINDING_SOURCE - ); - return ( <> From 8acdc99ddbef0ed833037120c5b9ba22832a0ed8 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 23 Apr 2024 13:42:35 +0800 Subject: [PATCH 05/18] Copy the metadata to avoid mutating original attributes --- packages/blocks/src/api/parser/convert-legacy-block.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/blocks/src/api/parser/convert-legacy-block.js b/packages/blocks/src/api/parser/convert-legacy-block.js index aeb4363c0e24f4..055679302efd64 100644 --- a/packages/blocks/src/api/parser/convert-legacy-block.js +++ b/packages/blocks/src/api/parser/convert-legacy-block.js @@ -111,6 +111,10 @@ export function convertLegacyBlockNameAndAttributes( name, attributes ) { 'core/pattern-overrides' ) { hasPatternOverrides = true; + newAttributes.metadata = { + ...newAttributes.metadata, + bindings: { ...newAttributes.metadata.bindings }, + }; delete newAttributes.metadata.bindings[ binding ]; } } ); From f897e0ffb923803bc7411354ee3085cf1563c0df Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 21 May 2024 16:07:47 +0900 Subject: [PATCH 06/18] Fix errors --- .../editor/src/bindings/pattern-overrides.js | 2 +- .../editor/various/pattern-overrides.spec.js | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js index d9d94ba2a39556..827c3743a3a5c1 100644 --- a/packages/editor/src/bindings/pattern-overrides.js +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -25,7 +25,7 @@ export default { currentBlockAttributes?.metadata?.name ]; const overridableValue = overriddenAttributes?.[ attributeName ]; - const defaultValue = overriddenAttributes[ DEFAULT_ATTRIBUTES ]; + const defaultValue = overriddenAttributes?.[ DEFAULT_ATTRIBUTES ]; if ( defaultValue !== undefined ) { return defaultValue; diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 81d1915aef6fe5..f4648a03efe956 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -906,25 +906,28 @@ test.describe( 'Pattern Overrides', () => { attributes: { ref: id }, } ); - await expect.poll( editor.getBlocks ).toMatchObject( [ + const blocks = await editor.getBlocks( { full: true } ); + expect( blocks ).toMatchObject( [ { name: 'core/block', attributes: { ref: id }, - innerBlocks: [ - { - name: 'core/image', - attributes: { - metadata: { - name: imageName, - bindings: { - __default: { - source: 'core/pattern-overrides', - }, - }, + }, + ] ); + expect( + await editor.getBlocks( { clientId: blocks[ 0 ].clientId } ) + ).toMatchObject( [ + { + name: 'core/image', + attributes: { + metadata: { + name: imageName, + bindings: { + __default: { + source: 'core/pattern-overrides', }, }, }, - ], + }, }, ] ); From 77d7b207e8c13f5f8a7f87a0d3eec9aeb5efdbaa Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 21 May 2024 16:15:14 +0900 Subject: [PATCH 07/18] Cleanup --- packages/patterns/src/api/index.js | 35 ++----------- packages/patterns/src/api/test/index.js | 69 ------------------------- packages/patterns/src/private-apis.js | 7 +-- 3 files changed, 4 insertions(+), 107 deletions(-) delete mode 100644 packages/patterns/src/api/test/index.js diff --git a/packages/patterns/src/api/index.js b/packages/patterns/src/api/index.js index a6a4a08bb324e6..4321dc4262145a 100644 --- a/packages/patterns/src/api/index.js +++ b/packages/patterns/src/api/index.js @@ -32,38 +32,9 @@ export function isOverridableBlock( block ) { */ export function hasOverridableBlocks( blocks ) { return blocks.some( ( block ) => { - if ( isOverridableBlock( block ) ) return true; + if ( isOverridableBlock( block ) ) { + return true; + } return hasOverridableBlocks( block.innerBlocks ); } ); } - -/** - * Get the overridable attributes for the give block. - * - * @param {WPBlock} block The block to test. - * - * @return {string[]} The attribute names in an array. - */ -export function getOverridableAttributes( block ) { - const set = new Set(); - // get default attributes for the block. - if ( block.attributes.metadata.bindings.__default ) { - PARTIAL_SYNCING_SUPPORTED_BLOCKS[ block.name ].forEach( - ( attribute ) => { - set.add( attribute ); - } - ); - } - // Any additional attributes and overrides. - for ( const [ attributeKey, binding ] of Object.entries( - block.attributes.metadata.bindings - ) ) { - if ( attributeKey === '__default' ) continue; - if ( binding.source === 'core/pattern-overrides' ) { - set.add( attributeKey ); - } else { - set.delete( attributeKey ); - } - } - return Array.from( set ); -} diff --git a/packages/patterns/src/api/test/index.js b/packages/patterns/src/api/test/index.js deleted file mode 100644 index 81224da8170a1d..00000000000000 --- a/packages/patterns/src/api/test/index.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Internal dependencies - */ -import { getOverridableAttributes } from '../'; -import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../../constants'; - -/** - * WordPress dependencies - */ -import { createBlock } from '@wordpress/blocks'; -import { registerCoreBlocks } from '@wordpress/block-library'; - -describe( 'getOverridableAttributes', () => { - beforeAll( () => { - registerCoreBlocks(); - } ); - - it( 'should return an array of overridable attributes', () => { - const block = createBlock( 'core/paragraph', { - metadata: { - bindings: { - content: { source: 'core/pattern-overrides' }, - }, - }, - } ); - - const attributes = getOverridableAttributes( block ); - - expect( attributes ).toEqual( [ 'content' ] ); - } ); - - it( 'should return the default list for core blocks', () => { - const block = createBlock( 'core/image', { - metadata: { - bindings: { - __default: { source: 'core/pattern-overrides' }, - }, - }, - } ); - - const attributes = getOverridableAttributes( block ); - - expect( attributes ).toEqual( - PARTIAL_SYNCING_SUPPORTED_BLOCKS[ 'core/image' ] - ); - } ); - - it( 'should allow overrides of the default list', () => { - const block = createBlock( 'core/image', { - metadata: { - bindings: { - __default: { source: 'core/pattern-overrides' }, - alt: { source: 'core/post-meta' }, - width: { source: 'core/pattern-overrides' }, - }, - }, - } ); - - const attributes = getOverridableAttributes( block ); - - const defaultAttributes = new Set( - PARTIAL_SYNCING_SUPPORTED_BLOCKS[ 'core/image' ] - ); - defaultAttributes.delete( 'alt' ); - defaultAttributes.add( 'width' ); - - expect( attributes ).toEqual( Array.from( defaultAttributes ) ); - } ); -} ); diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index 5d11e6aecdded1..0553378cb56043 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -11,11 +11,7 @@ import { default as DuplicatePatternModal, useDuplicatePatternProps, } from './components/duplicate-pattern-modal'; -import { - isOverridableBlock, - hasOverridableBlocks, - getOverridableAttributes, -} from './api'; +import { isOverridableBlock, hasOverridableBlocks } from './api'; import RenamePatternModal from './components/rename-pattern-modal'; import PatternsMenuItems from './components'; import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; @@ -39,7 +35,6 @@ lock( privateApis, { DuplicatePatternModal, isOverridableBlock, hasOverridableBlocks, - getOverridableAttributes, useDuplicatePatternProps, RenamePatternModal, PatternsMenuItems, From afee53fdc25ba2b51fc339bb7171555906823dc7 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 28 May 2024 15:30:57 +0800 Subject: [PATCH 08/18] Extract application of default attributes into an easily replaced function and limit to only pattern overrides --- .../src/hooks/use-bindings-attributes.js | 81 +++++++++++++------ 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 3e0f7016a3b6de..bc0575f26c40e8 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -4,7 +4,7 @@ import { store as blocksStore } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useRegistry, useSelect } from '@wordpress/data'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useMemo } from '@wordpress/element'; import { addFilter } from '@wordpress/hooks'; /** @@ -29,7 +29,38 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], }; -const DEFAULT_ATTRIBUTES = '__default'; +const DEFAULT_ATTRIBUTE = '__default'; + +/** + * Returns the bindings with the `__default` binding for pattern overrides + * replaced with the full-set of supported attributes. e.g.: + * + * bindings passed in: `{ bindings: { __default: 'core/pattern-overrides' } }` + * bindings returned: `{ bindings: { content: 'core/pattern-overrides' } }` + * + * @param {string} blockName The block name (e.g. 'core/paragraph'). + * @param {Object} bindings A block's bindings from the metadata attribute. + * + * @return {Object} The bindings with default replaced for pattern overrides. + */ +function replacePatternOverrideDefaultBindings( blockName, bindings ) { + // The `__default` binding currently only works for pattern overrides. + if ( bindings?.[ DEFAULT_ATTRIBUTE ] === 'core/pattern-overrides' ) { + const supportedAttributes = BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; + const bindingsWithDefaults = {}; + for ( const attributeName of supportedAttributes ) { + // If the block has mixed binding sources, retain any non pattern override bindings. + const bindingSourceName = bindings[ attributeName ] + ? bindings[ attributeName ] + : 'core/pattern-overrides'; + bindingsWithDefaults[ attributeName ] = bindingSourceName; + } + + return bindingsWithDefaults; + } + + return bindings; +} /** * Based on the given block name, @@ -64,26 +95,21 @@ export const withBlockBindingSupport = createHigherOrderComponent( unlock( select( blocksStore ) ).getAllBlockBindingsSources() ); const bindings = props.attributes.metadata?.bindings; + const bindingsWithDefaults = useMemo( + () => replacePatternOverrideDefaultBindings( name, bindings ), + [ bindings, name ] + ); const { name, clientId, context } = props; const boundAttributes = useSelect( () => { - if ( ! bindings ) { + if ( ! bindingsWithDefaults ) { return; } - const bindingsEntries = Object.entries( bindings ); const attributes = {}; - if ( bindings[ DEFAULT_ATTRIBUTES ] !== undefined ) { - const defaultAttributes = BLOCK_BINDINGS_ALLOWED_BLOCKS[ name ]; - bindingsEntries.push( - ...defaultAttributes.map( ( attributeName ) => [ - attributeName, - bindings[ DEFAULT_ATTRIBUTES ], - ] ) - ); - } - - for ( const [ attributeName, boundAttribute ] of bindingsEntries ) { + for ( const [ attributeName, boundAttribute ] of Object.entries( + bindingsWithDefaults + ) ) { const source = sources[ boundAttribute.source ]; if ( ! source?.getValue || @@ -113,14 +139,21 @@ export const withBlockBindingSupport = createHigherOrderComponent( } return attributes; - }, [ bindings, name, clientId, context, registry, sources ] ); + }, [ + bindingsWithDefaults, + name, + clientId, + context, + registry, + sources, + ] ); const { setAttributes } = props; const _setAttributes = useCallback( ( nextAttributes ) => { registry.batch( () => { - if ( ! bindings ) { + if ( ! bindingsWithDefaults ) { setAttributes( nextAttributes ); return; } @@ -133,16 +166,13 @@ export const withBlockBindingSupport = createHigherOrderComponent( keptAttributes ) ) { if ( - ( ! bindings[ attributeName ] || - ! canBindAttribute( name, attributeName ) ) && - ! bindings[ DEFAULT_ATTRIBUTES ] + ! bindingsWithDefaults[ attributeName ] || + ! canBindAttribute( name, attributeName ) ) { continue; } - const binding = - bindings[ attributeName ] ?? - bindings[ DEFAULT_ATTRIBUTES ]; + const binding = bindingsWithDefaults[ attributeName ]; const source = sources[ binding?.source ]; if ( ! source?.setValue && ! source?.setValues ) { continue; @@ -172,8 +202,7 @@ export const withBlockBindingSupport = createHigherOrderComponent( value, ] of Object.entries( attributes ) ) { const binding = - bindings[ attributeName ] ?? - bindings[ DEFAULT_ATTRIBUTES ]; + bindingsWithDefaults[ attributeName ]; source.setValue( { registry, context, @@ -194,7 +223,7 @@ export const withBlockBindingSupport = createHigherOrderComponent( }, [ registry, - bindings, + bindingsWithDefaults, name, clientId, context, From c6b1c4fb7bda80f26d41cf4a4f0d7a88ff76b0b5 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 28 May 2024 15:42:00 +0800 Subject: [PATCH 09/18] Fix issue with `name` not declared in time --- packages/block-editor/src/hooks/use-bindings-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index bc0575f26c40e8..f24b8d94f10476 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -95,11 +95,11 @@ export const withBlockBindingSupport = createHigherOrderComponent( unlock( select( blocksStore ) ).getAllBlockBindingsSources() ); const bindings = props.attributes.metadata?.bindings; + const { name, clientId, context } = props; const bindingsWithDefaults = useMemo( () => replacePatternOverrideDefaultBindings( name, bindings ), [ bindings, name ] ); - const { name, clientId, context } = props; const boundAttributes = useSelect( () => { if ( ! bindingsWithDefaults ) { return; From e5b834f6d02d0aa6e2797e5dc193e52652430e90 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 28 May 2024 15:47:35 +0800 Subject: [PATCH 10/18] Fix missing `source` property --- .../block-editor/src/hooks/use-bindings-attributes.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index f24b8d94f10476..7a39fd7f4f76cc 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -35,8 +35,8 @@ const DEFAULT_ATTRIBUTE = '__default'; * Returns the bindings with the `__default` binding for pattern overrides * replaced with the full-set of supported attributes. e.g.: * - * bindings passed in: `{ bindings: { __default: 'core/pattern-overrides' } }` - * bindings returned: `{ bindings: { content: 'core/pattern-overrides' } }` + * bindings passed in: `{ __default: { source: 'core/pattern-overrides' } }` + * bindings returned: `{ content: { source: 'core/pattern-overrides' } }` * * @param {string} blockName The block name (e.g. 'core/paragraph'). * @param {Object} bindings A block's bindings from the metadata attribute. @@ -45,14 +45,16 @@ const DEFAULT_ATTRIBUTE = '__default'; */ function replacePatternOverrideDefaultBindings( blockName, bindings ) { // The `__default` binding currently only works for pattern overrides. - if ( bindings?.[ DEFAULT_ATTRIBUTE ] === 'core/pattern-overrides' ) { + if ( + bindings?.[ DEFAULT_ATTRIBUTE ]?.source === 'core/pattern-overrides' + ) { const supportedAttributes = BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; const bindingsWithDefaults = {}; for ( const attributeName of supportedAttributes ) { // If the block has mixed binding sources, retain any non pattern override bindings. const bindingSourceName = bindings[ attributeName ] ? bindings[ attributeName ] - : 'core/pattern-overrides'; + : { source: 'core/pattern-overrides' }; bindingsWithDefaults[ attributeName ] = bindingSourceName; } From 9b2dce8061f36b9db1ef504e12467a0c3551f579 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Tue, 28 May 2024 15:51:47 +0800 Subject: [PATCH 11/18] Remove handling of default value in pattern overrides source `getValue` function. Default should never be passed in to this function. --- packages/editor/src/bindings/pattern-overrides.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js index 827c3743a3a5c1..4065cefe362808 100644 --- a/packages/editor/src/bindings/pattern-overrides.js +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -5,7 +5,6 @@ import { _x } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; const CONTENT = 'content'; -const DEFAULT_ATTRIBUTES = '__default'; export default { name: 'core/pattern-overrides', @@ -20,16 +19,10 @@ export default { true ); - const overriddenAttributes = + const overridableValue = getBlockAttributes( patternClientId )?.[ CONTENT ]?.[ currentBlockAttributes?.metadata?.name - ]; - const overridableValue = overriddenAttributes?.[ attributeName ]; - const defaultValue = overriddenAttributes?.[ DEFAULT_ATTRIBUTES ]; - - if ( defaultValue !== undefined ) { - return defaultValue; - } + ]?.[ attributeName ]; // If there is no pattern client ID, or it is not overwritten, return the default value. if ( ! patternClientId || overridableValue === undefined ) { From bb2280e89470612e756ed8438aed6a3ee2490984 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 31 May 2024 15:07:27 +0800 Subject: [PATCH 12/18] Rename variable to `bindingSource` --- packages/block-editor/src/hooks/use-bindings-attributes.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 7a39fd7f4f76cc..9e76e694cdba55 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -52,10 +52,10 @@ function replacePatternOverrideDefaultBindings( blockName, bindings ) { const bindingsWithDefaults = {}; for ( const attributeName of supportedAttributes ) { // If the block has mixed binding sources, retain any non pattern override bindings. - const bindingSourceName = bindings[ attributeName ] + const bindingSource = bindings[ attributeName ] ? bindings[ attributeName ] : { source: 'core/pattern-overrides' }; - bindingsWithDefaults[ attributeName ] = bindingSourceName; + bindingsWithDefaults[ attributeName ] = bindingSource; } return bindingsWithDefaults; From 50822b796de1d1e07204fae67623634a50b854a5 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 31 May 2024 15:12:02 +0800 Subject: [PATCH 13/18] Simplify variable name to just bindings --- .../src/hooks/use-bindings-attributes.js | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 9e76e694cdba55..334c751bc01b0b 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -96,21 +96,24 @@ export const withBlockBindingSupport = createHigherOrderComponent( const sources = useSelect( ( select ) => unlock( select( blocksStore ) ).getAllBlockBindingsSources() ); - const bindings = props.attributes.metadata?.bindings; const { name, clientId, context } = props; - const bindingsWithDefaults = useMemo( - () => replacePatternOverrideDefaultBindings( name, bindings ), - [ bindings, name ] + const bindings = useMemo( + () => + replacePatternOverrideDefaultBindings( + name, + props.attributes.metadata?.bindings + ), + [ props.attributes.metadata?.bindings, name ] ); const boundAttributes = useSelect( () => { - if ( ! bindingsWithDefaults ) { + if ( ! bindings ) { return; } const attributes = {}; for ( const [ attributeName, boundAttribute ] of Object.entries( - bindingsWithDefaults + bindings ) ) { const source = sources[ boundAttribute.source ]; if ( @@ -141,21 +144,14 @@ export const withBlockBindingSupport = createHigherOrderComponent( } return attributes; - }, [ - bindingsWithDefaults, - name, - clientId, - context, - registry, - sources, - ] ); + }, [ bindings, name, clientId, context, registry, sources ] ); const { setAttributes } = props; const _setAttributes = useCallback( ( nextAttributes ) => { registry.batch( () => { - if ( ! bindingsWithDefaults ) { + if ( ! bindings ) { setAttributes( nextAttributes ); return; } @@ -168,13 +164,13 @@ export const withBlockBindingSupport = createHigherOrderComponent( keptAttributes ) ) { if ( - ! bindingsWithDefaults[ attributeName ] || + ! bindings[ attributeName ] || ! canBindAttribute( name, attributeName ) ) { continue; } - const binding = bindingsWithDefaults[ attributeName ]; + const binding = bindings[ attributeName ]; const source = sources[ binding?.source ]; if ( ! source?.setValue && ! source?.setValues ) { continue; @@ -203,8 +199,7 @@ export const withBlockBindingSupport = createHigherOrderComponent( attributeName, value, ] of Object.entries( attributes ) ) { - const binding = - bindingsWithDefaults[ attributeName ]; + const binding = bindings[ attributeName ]; source.setValue( { registry, context, @@ -225,7 +220,7 @@ export const withBlockBindingSupport = createHigherOrderComponent( }, [ registry, - bindingsWithDefaults, + bindings, name, clientId, context, From 29a65a4bff04463904d38a3e80dc5d363e31cbfe Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 31 May 2024 15:20:11 +0800 Subject: [PATCH 14/18] Remove filter after do_blocks --- packages/block-library/src/block/index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index cb1618cb337b65..786615f58583db 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -119,7 +119,7 @@ function render_block_core_block( $attributes ) { return $parsed_block; }; - add_filter( 'render_block_data', $apply_default_pattern_overrides_bindings, 10, 1 ); + add_filter( 'render_block_data', $apply_default_pattern_overrides_bindings ); } $content = do_blocks( $content ); @@ -127,6 +127,7 @@ function render_block_core_block( $attributes ) { if ( $has_pattern_overrides ) { remove_filter( 'render_block_context', $filter_block_context, 1 ); + remove_filter( 'render_block_data', $apply_default_pattern_overrides_bindings ); } return $content; From 869d53de95ed87da86fc61c04a6f4f63dfa93819 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 31 May 2024 16:08:02 +0800 Subject: [PATCH 15/18] Make the PHP implementation of the replacement of default attributes more generic (not implemented in the pattern block) --- lib/compat/wordpress-6.6/blocks.php | 43 ++++++++++++++++++++++ lib/load.php | 1 + packages/block-library/src/block/index.php | 36 ------------------ 3 files changed, 44 insertions(+), 36 deletions(-) create mode 100644 lib/compat/wordpress-6.6/blocks.php diff --git a/lib/compat/wordpress-6.6/blocks.php b/lib/compat/wordpress-6.6/blocks.php new file mode 100644 index 00000000000000..04582452cb0dca --- /dev/null +++ b/lib/compat/wordpress-6.6/blocks.php @@ -0,0 +1,43 @@ + array( 'content' ), + 'core/heading' => array( 'content' ), + 'core/image' => array( 'id', 'url', 'title', 'alt' ), + 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ), + ); + + $bindings = $parsed_block['attrs']['metadata']['bindings'] ?? array(); + if ( + isset( $bindings[ '__default' ][ 'source' ] ) && + $bindings[ '__default' ][ 'source' ] === 'core/pattern-overrides' + ) { + // Build an binding array of all supported attributes. + foreach ( $supported_block_attrs[ $parsed_block['blockName'] ] as $attribute_name ) { + $bindings[ $attribute_name ] = array( 'source' => 'core/pattern-overrides' ); + } + // Merge this into the parsed block's bindings & avoid overwriting existing bindings. + $parsed_block['attrs']['metadata']['bindings'] = array_merge( + $bindings, + $parsed_block['attrs']['metadata']['bindings'] + ); + } + + return $parsed_block; +} + +add_filter( 'render_block_data', 'gutenberg_replace_pattern_override_default_binding', 10, 1 ); diff --git a/lib/load.php b/lib/load.php index 6179ade9a2288e..23985f9c8a92e9 100644 --- a/lib/load.php +++ b/lib/load.php @@ -131,6 +131,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.6 compat. require __DIR__ . '/compat/wordpress-6.6/admin-bar.php'; +require __DIR__ . '/compat/wordpress-6.6/blocks.php'; require __DIR__ . '/compat/wordpress-6.6/compat.php'; require __DIR__ . '/compat/wordpress-6.6/resolve-patterns.php'; require __DIR__ . '/compat/wordpress-6.6/block-bindings/pattern-overrides.php'; diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 786615f58583db..8beef975fad6f3 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -85,41 +85,6 @@ function render_block_core_block( $attributes ) { return $context; }; add_filter( 'render_block_context', $filter_block_context, 1 ); - - // Add bindings for the default pattern overrides source. - $apply_default_pattern_overrides_bindings = static function ( $parsed_block ) { - $supported_block_attrs = array( - 'core/paragraph' => array( 'content' ), - 'core/heading' => array( 'content' ), - 'core/image' => array( 'id', 'url', 'title', 'alt' ), - 'core/button' => array( 'url', 'text', 'linkTarget', 'rel' ), - ); - - if ( - // Return early if the block isn't one of the supported block types, - ! isset( $supported_block_attrs[ $parsed_block['blockName'] ] ) || - // or doesn't have a name, - ! isset( $parsed_block['attrs']['metadata']['name'] ) || - // or doesn't have the default binding. - empty( $parsed_block['attrs']['metadata']['bindings']['__default'] ) || - // or the default binding isn't the pattern overrides source. - 'core/pattern-overrides' !== $parsed_block['attrs']['metadata']['bindings']['__default']['source'] - ) { - return $parsed_block; - } - - $bindings = array(); - foreach ( $supported_block_attrs[ $parsed_block['blockName'] ] as $attribute_name ) { - $bindings[ $attribute_name ] = array( 'source' => 'core/pattern-overrides' ); - } - $parsed_block['attrs']['metadata']['bindings'] = array_merge( - $bindings, - $parsed_block['attrs']['metadata']['bindings'] - ); - - return $parsed_block; - }; - add_filter( 'render_block_data', $apply_default_pattern_overrides_bindings ); } $content = do_blocks( $content ); @@ -127,7 +92,6 @@ function render_block_core_block( $attributes ) { if ( $has_pattern_overrides ) { remove_filter( 'render_block_context', $filter_block_context, 1 ); - remove_filter( 'render_block_data', $apply_default_pattern_overrides_bindings ); } return $content; From b07b34626238f2957ceb8fd8f2eaa6800516f446 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 31 May 2024 16:24:31 +0800 Subject: [PATCH 16/18] Fix linting issues --- lib/compat/wordpress-6.6/blocks.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compat/wordpress-6.6/blocks.php b/lib/compat/wordpress-6.6/blocks.php index 04582452cb0dca..fff9ead207a507 100644 --- a/lib/compat/wordpress-6.6/blocks.php +++ b/lib/compat/wordpress-6.6/blocks.php @@ -23,8 +23,8 @@ function gutenberg_replace_pattern_override_default_binding( $parsed_block ) { $bindings = $parsed_block['attrs']['metadata']['bindings'] ?? array(); if ( - isset( $bindings[ '__default' ][ 'source' ] ) && - $bindings[ '__default' ][ 'source' ] === 'core/pattern-overrides' + isset( $bindings['__default']['source'] ) && + 'core/pattern-overrides' === $bindings['__default']['source'] ) { // Build an binding array of all supported attributes. foreach ( $supported_block_attrs[ $parsed_block['blockName'] ] as $attribute_name ) { From 1cd20dd6b68e6e9d40a77dee5378a9b9e6208534 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 31 May 2024 17:18:46 +0800 Subject: [PATCH 17/18] Avoid mutating parsed block directly and omit the __default attribute in the PHP code --- lib/compat/wordpress-6.6/blocks.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.6/blocks.php b/lib/compat/wordpress-6.6/blocks.php index fff9ead207a507..0d8805a489d9cb 100644 --- a/lib/compat/wordpress-6.6/blocks.php +++ b/lib/compat/wordpress-6.6/blocks.php @@ -26,15 +26,18 @@ function gutenberg_replace_pattern_override_default_binding( $parsed_block ) { isset( $bindings['__default']['source'] ) && 'core/pattern-overrides' === $bindings['__default']['source'] ) { + $updated_bindings = array(); + // Build an binding array of all supported attributes. + // Note that this also omits the `__default` attribute from the + // resulting array. foreach ( $supported_block_attrs[ $parsed_block['blockName'] ] as $attribute_name ) { - $bindings[ $attribute_name ] = array( 'source' => 'core/pattern-overrides' ); + // Retain any non-pattern override bindings that might be present. + $updated_bindings[ $attribute_name ] = isset( $bindings[ $attribute_name ] ) + ? $bindings[ $attribute_name ] + : array( 'source' => 'core/pattern-overrides' ); } - // Merge this into the parsed block's bindings & avoid overwriting existing bindings. - $parsed_block['attrs']['metadata']['bindings'] = array_merge( - $bindings, - $parsed_block['attrs']['metadata']['bindings'] - ); + $parsed_block['attrs']['metadata']['bindings'] = $updated_bindings; } return $parsed_block; From 5638bcd65fcccd648966cfc1fb0ebb6a737532b6 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 31 May 2024 17:20:14 +0800 Subject: [PATCH 18/18] Add backport changelog --- backport-changelog/6.6/6694.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/6.6/6694.md diff --git a/backport-changelog/6.6/6694.md b/backport-changelog/6.6/6694.md new file mode 100644 index 00000000000000..a9eb5a7f37ef5b --- /dev/null +++ b/backport-changelog/6.6/6694.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6694 + +* https://github.com/WordPress/gutenberg/pull/60694