diff --git a/packages/blocks/src/api/parser/convert-legacy-block.js b/packages/blocks/src/api/parser/convert-legacy-block.js index c828a3f5db6c49..ace95c8df92a58 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 df72aed1032680..2ba2a43b8557d6 100644 --- a/packages/patterns/src/components/pattern-overrides-controls.js +++ b/packages/patterns/src/components/pattern-overrides-controls.js @@ -9,77 +9,43 @@ 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 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 toggleRef = useRef(); const [ showAllowOverridesModal, setShowAllowOverridesModal ] = 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 d1fdee7097a923..794dae2e65ddf5 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -644,4 +644,79 @@ test.describe( 'Pattern 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\// ); + } ); } );