diff --git a/lib/compat/wordpress-6.5/block-bindings/pattern-overrides.php b/lib/compat/wordpress-6.5/block-bindings/pattern-overrides.php index 76c3d49ca8085..e5f9891f04c47 100644 --- a/lib/compat/wordpress-6.5/block-bindings/pattern-overrides.php +++ b/lib/compat/wordpress-6.5/block-bindings/pattern-overrides.php @@ -15,11 +15,30 @@ * @return mixed The value computed for the source. */ function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs, $block_instance, $attribute_name ) { - if ( empty( $block_instance->attributes['metadata']['id'] ) ) { + if ( ! isset( $block_instance->context['pattern/overrides'] ) ) { return null; } - $block_id = $block_instance->attributes['metadata']['id']; - return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, 'values', $attribute_name ), null ); + + $override_content = $block_instance->context['pattern/overrides']; + + // Back compat. Pattern overrides previously used a metadata `id` instead of `name`. + // We check first for the name, and if it exists, use that value. + if ( isset( $block_instance->attributes['metadata']['name'] ) ) { + $metadata_name = $block_instance->attributes['metadata']['name']; + if ( array_key_exists( $metadata_name, $override_content ) ) { + return _wp_array_get( $override_content, array( $metadata_name, $attribute_name ), null ); + } + } + + // Next check for the `id`. + if ( isset( $block_instance->attributes['metadata']['id'] ) ) { + $metadata_id = $block_instance->attributes['metadata']['id']; + if ( array_key_exists( $metadata_id, $override_content ) ) { + return _wp_array_get( $override_content, array( $metadata_id, $attribute_name ), null ); + } + } + + return null; } /** diff --git a/package-lock.json b/package-lock.json index 9c03b2e0e1faf..6dca2aa7912ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55967,8 +55967,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", "@wordpress/private-apis": "file:../private-apis", - "@wordpress/url": "file:../url", - "nanoid": "^3.3.4" + "@wordpress/url": "file:../url" }, "engines": { "node": ">=16.0.0" @@ -70828,8 +70827,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", "@wordpress/private-apis": "file:../private-apis", - "@wordpress/url": "file:../url", - "nanoid": "^3.3.4" + "@wordpress/url": "file:../url" } }, "@wordpress/plugins": { diff --git a/packages/block-library/src/block/deprecated.js b/packages/block-library/src/block/deprecated.js index 7bc243bbf4ce9..f820867fff627 100644 --- a/packages/block-library/src/block/deprecated.js +++ b/packages/block-library/src/block/deprecated.js @@ -1,4 +1,75 @@ -// v1: Migrate and rename the `overrides` attribute to the `content` attribute. +const isObject = ( obj ) => + typeof obj === 'object' && ! Array.isArray( obj ) && obj !== null; + +// v2: Migrate to a more condensed version of the 'content' attribute attribute. +const v2 = { + attributes: { + ref: { + type: 'number', + }, + content: { + type: 'object', + }, + }, + supports: { + customClassName: false, + html: false, + inserter: false, + renaming: false, + }, + // Force this deprecation to run whenever there's a values sub-property that's an object. + // + // This could fail in the future if a block ever has binding to a `values` attribute. + // Some extra protection is added to ensure `values` is an object, but this only reduces + // the likelihood, it doesn't solve it completely. + isEligible( { content } ) { + return ( + !! content && + Object.keys( content ).every( + ( contentKey ) => + content[ contentKey ].values && + isObject( content[ contentKey ].values ) + ) + ); + }, + /* + * Old attribute format: + * content: { + * "V98q_x": { + * // The attribute values are now stored as a 'values' sub-property. + * values: { content: 'My content value' }, + * // ... additional metadata, like the block name can be stored here. + * } + * } + * + * New attribute format: + * content: { + * "V98q_x": { + * content: 'My content value', + * } + * } + */ + migrate( attributes ) { + const { content, ...retainedAttributes } = attributes; + + if ( content && Object.keys( content ).length ) { + const updatedContent = { ...content }; + + for ( const contentKey in content ) { + updatedContent[ contentKey ] = content[ contentKey ].values; + } + + return { + ...retainedAttributes, + content: updatedContent, + }; + } + + return attributes; + }, +}; + +// v1: Rename the `overrides` attribute to the `content` attribute. const v1 = { attributes: { ref: { @@ -23,16 +94,12 @@ const v1 = { * overrides: { * // An key is an id that represents a block. * // The values are the attribute values of the block. - * "V98q_x": { content: 'dwefwefwefwe' } + * "V98q_x": { content: 'My content value' } * } * * New attribute format: * content: { - * "V98q_x": { - * // The attribute values are now stored as a 'values' sub-property. - * values: { content: 'dwefwefwefwe' }, - * // ... additional metadata, like the block name can be stored here. - * } + * "V98q_x": { content: 'My content value' } * } * */ @@ -42,9 +109,7 @@ const v1 = { const content = {}; Object.keys( overrides ).forEach( ( id ) => { - content[ id ] = { - values: overrides[ id ], - }; + content[ id ] = overrides[ id ]; } ); return { @@ -54,4 +119,4 @@ const v1 = { }, }; -export default [ v1 ]; +export default [ v2, v1 ]; diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 5efe245c935fc..ddacc47dbd039 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -42,6 +42,25 @@ const { PARTIAL_SYNCING_SUPPORTED_BLOCKS } = unlock( patternsPrivateApis ); const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; +function getLegacyIdMap( blocks, content, nameCount = {} ) { + let idToClientIdMap = {}; + for ( const block of blocks ) { + if ( block?.innerBlocks?.length ) { + idToClientIdMap = { + ...idToClientIdMap, + ...getLegacyIdMap( block.innerBlocks, content, nameCount ), + }; + } + + const id = block.attributes.metadata?.id; + const clientId = block.clientId; + if ( id && content?.[ id ] ) { + idToClientIdMap[ clientId ] = id; + } + } + return idToClientIdMap; +} + const useInferredLayout = ( blocks, parentLayout ) => { const initialInferredAlignmentRef = useRef(); @@ -101,25 +120,31 @@ function getOverridableAttributes( block ) { function applyInitialContentValuesToInnerBlocks( blocks, content = {}, - defaultValues + defaultValues, + legacyIdMap ) { return blocks.map( ( block ) => { const innerBlocks = applyInitialContentValuesToInnerBlocks( block.innerBlocks, content, - defaultValues + defaultValues, + legacyIdMap ); - const blockId = block.attributes.metadata?.id; - if ( ! hasOverridableAttributes( block ) || ! blockId ) + const metadataName = + legacyIdMap?.[ block.clientId ] ?? block.attributes.metadata?.name; + + if ( ! metadataName || ! hasOverridableAttributes( block ) ) { return { ...block, innerBlocks }; + } + const attributes = getOverridableAttributes( block ); const newAttributes = { ...block.attributes }; for ( const attributeKey of attributes ) { - defaultValues[ blockId ] ??= {}; - defaultValues[ blockId ][ attributeKey ] = + defaultValues[ metadataName ] ??= {}; + defaultValues[ metadataName ][ attributeKey ] = block.attributes[ attributeKey ]; - const contentValues = content[ blockId ]?.values; + const contentValues = content[ metadataName ]; if ( contentValues?.[ attributeKey ] !== undefined ) { newAttributes[ attributeKey ] = contentValues[ attributeKey ]; } @@ -142,29 +167,40 @@ function isAttributeEqual( attribute1, attribute2 ) { return attribute1 === attribute2; } -function getContentValuesFromInnerBlocks( blocks, defaultValues ) { +function getContentValuesFromInnerBlocks( blocks, defaultValues, legacyIdMap ) { /** @type {Record}>} */ const content = {}; for ( const block of blocks ) { if ( block.name === patternBlockName ) continue; - Object.assign( - content, - getContentValuesFromInnerBlocks( block.innerBlocks, defaultValues ) - ); - const blockId = block.attributes.metadata?.id; - if ( ! hasOverridableAttributes( block ) || ! blockId ) continue; + if ( block.innerBlocks.length ) { + Object.assign( + content, + getContentValuesFromInnerBlocks( + block.innerBlocks, + defaultValues, + legacyIdMap + ) + ); + } + const metadataName = + legacyIdMap?.[ block.clientId ] ?? block.attributes.metadata?.name; + if ( ! metadataName || ! hasOverridableAttributes( block ) ) { + continue; + } + const attributes = getOverridableAttributes( block ); + for ( const attributeKey of attributes ) { if ( ! isAttributeEqual( block.attributes[ attributeKey ], - defaultValues[ blockId ][ attributeKey ] + defaultValues?.[ metadataName ]?.[ attributeKey ] ) ) { - content[ blockId ] ??= { values: {}, blockName: block.name }; + content[ metadataName ] ??= {}; // TODO: We need a way to represent `undefined` in the serialized overrides. // Also see: https://github.com/WordPress/gutenberg/pull/57249#discussion_r1452987871 - content[ blockId ].values[ attributeKey ] = + content[ metadataName ][ attributeKey ] = block.attributes[ attributeKey ] === undefined ? // TODO: We use an empty string to represent undefined for now until // we support a richer format for overrides and the block binding API. @@ -278,8 +314,15 @@ export default function ReusableBlockEdit( { [ editedRecord.blocks, editedRecord.content ] ); + const legacyIdMap = useRef( {} ); + // Apply the initial overrides from the pattern block to the inner blocks. useEffect( () => { + // Build a map of clientIds to the old nano id system to provide back compat. + legacyIdMap.current = getLegacyIdMap( + initialBlocks, + initialContent.current + ); defaultContent.current = {}; const originalEditingMode = getBlockEditingMode( patternClientId ); // Replace the contents of the blocks with the overrides. @@ -291,7 +334,8 @@ export default function ReusableBlockEdit( { applyInitialContentValuesToInnerBlocks( initialBlocks, initialContent.current, - defaultContent.current + defaultContent.current, + legacyIdMap.current ) ); } ); @@ -343,7 +387,8 @@ export default function ReusableBlockEdit( { setAttributes( { content: getContentValuesFromInnerBlocks( blocks, - defaultContent.current + defaultContent.current, + legacyIdMap.current ), } ); } ); diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 4aabe98ffa465..49b5786eacc79 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -48,26 +48,35 @@ function render_block_core_block( $attributes ) { $content = $wp_embed->run_shortcode( $reusable_block->post_content ); $content = $wp_embed->autoembed( $content ); - // Back compat, the content attribute was previously named overrides and - // had a slightly different format. For blocks that have not been migrated, - // also convert the format here so that the provided `pattern/overrides` - // context is correct. - if ( isset( $attributes['overrides'] ) && ! isset( $attributes['content'] ) ) { - $migrated_content = array(); - foreach ( $attributes['overrides'] as $id => $values ) { - $migrated_content[ $id ] = array( - 'values' => $values, - ); + // Back compat. + // For blocks that have not been migrated in the editor, add some back compat + // so that front-end rendering continues to work. + + // This matches the `v2` deprecation. Removes the inner `values` property + // from every item. + if ( isset( $attributes['content'] ) ) { + foreach ( $attributes['content'] as &$content_data ) { + if ( isset( $content_data['values'] ) ) { + $is_assoc_array = is_array( $content_data['values'] ) && ! wp_is_numeric_array( $content_data['values'] ); + + if ( $is_assoc_array ) { + $content_data = $content_data['values']; + } + } } - $attributes['content'] = $migrated_content; } - $has_pattern_overrides = isset( $attributes['content'] ); + + // This matches the `v1` deprecation. Rename `overrides` to `content`. + if ( isset( $attributes['overrides'] ) && ! isset( $attributes['content'] ) ) { + $attributes['content'] = $attributes['overrides']; + } /** * We set the `pattern/overrides` context through the `render_block_context` * 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'] ); if ( $has_pattern_overrides ) { $filter_block_context = static function ( $context ) use ( $attributes ) { $context['pattern/overrides'] = $attributes['content']; diff --git a/packages/editor/src/hooks/index.js b/packages/editor/src/hooks/index.js index 5a48ec1bf4956..75bb34abf6cfa 100644 --- a/packages/editor/src/hooks/index.js +++ b/packages/editor/src/hooks/index.js @@ -3,4 +3,4 @@ */ import './custom-sources-backwards-compatibility'; import './default-autocompleters'; -import './pattern-partial-syncing'; +import './pattern-overrides'; diff --git a/packages/editor/src/hooks/pattern-partial-syncing.js b/packages/editor/src/hooks/pattern-overrides.js similarity index 81% rename from packages/editor/src/hooks/pattern-partial-syncing.js rename to packages/editor/src/hooks/pattern-overrides.js index f86268cb49546..442ce70a2bf71 100644 --- a/packages/editor/src/hooks/pattern-partial-syncing.js +++ b/packages/editor/src/hooks/pattern-overrides.js @@ -14,7 +14,7 @@ import { store as editorStore } from '../store'; import { unlock } from '../lock-unlock'; const { - PartialSyncingControls, + useSetPatternBindings, ResetOverridesControl, PATTERN_TYPES, PARTIAL_SYNCING_SUPPORTED_BLOCKS, @@ -29,7 +29,7 @@ const { * * @return {Component} Wrapped component. */ -const withPartialSyncingControls = createHigherOrderComponent( +const withPatternOverrideControls = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { const isSupportedBlock = Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS @@ -38,6 +38,7 @@ const withPartialSyncingControls = createHigherOrderComponent( return ( <> + { isSupportedBlock && } { props.isSelected && isSupportedBlock && ( ) } @@ -46,6 +47,15 @@ const withPartialSyncingControls = createHigherOrderComponent( } ); +function BindingUpdater( props ) { + const postType = useSelect( + ( select ) => select( editorStore ).getCurrentPostType(), + [] + ); + useSetPatternBindings( props, postType ); + return null; +} + // Split into a separate component to avoid a store subscription // on every block. function ControlsWithStoreSubscription( props ) { @@ -55,6 +65,7 @@ function ControlsWithStoreSubscription( props ) { select( editorStore ).getCurrentPostType() === PATTERN_TYPES.user, [] ); + const bindings = props.attributes.metadata?.bindings; const hasPatternBindings = !! bindings && @@ -62,19 +73,14 @@ function ControlsWithStoreSubscription( props ) { ( binding ) => binding.source === 'core/pattern-overrides' ); - const shouldShowPartialSyncingControls = - isEditingPattern && blockEditingMode === 'default'; const shouldShowResetOverridesControl = ! isEditingPattern && - !! props.attributes.metadata?.id && + !! props.attributes.metadata?.name && blockEditingMode !== 'disabled' && hasPatternBindings; return ( <> - { shouldShowPartialSyncingControls && ( - - ) } { shouldShowResetOverridesControl && ( ) } @@ -84,6 +90,6 @@ function ControlsWithStoreSubscription( props ) { addFilter( 'editor.BlockEdit', - 'core/editor/with-partial-syncing-controls', - withPartialSyncingControls + 'core/editor/with-pattern-override-controls', + withPatternOverrideControls ); diff --git a/packages/patterns/package.json b/packages/patterns/package.json index eaef15fe2a932..f35a629508cb0 100644 --- a/packages/patterns/package.json +++ b/packages/patterns/package.json @@ -44,8 +44,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", "@wordpress/private-apis": "file:../private-apis", - "@wordpress/url": "file:../url", - "nanoid": "^3.3.4" + "@wordpress/url": "file:../url" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/patterns/src/components/partial-syncing-controls.js b/packages/patterns/src/components/partial-syncing-controls.js deleted file mode 100644 index 7b3e5cb312e82..0000000000000 --- a/packages/patterns/src/components/partial-syncing-controls.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * External dependencies - */ -import { nanoid } from 'nanoid'; - -/** - * WordPress dependencies - */ -import { InspectorControls } from '@wordpress/block-editor'; -import { BaseControl, CheckboxControl } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../constants'; - -function PartialSyncingControls( { name, attributes, setAttributes } ) { - const syncedAttributes = PARTIAL_SYNCING_SUPPORTED_BLOCKS[ name ]; - const attributeSources = syncedAttributes.map( - ( attributeName ) => - attributes.metadata?.bindings?.[ attributeName ]?.source - ); - const isConnectedToOtherSources = attributeSources.every( - ( source ) => source && source !== 'core/pattern-overrides' - ); - - // Render nothing if all supported attributes are connected to other sources. - if ( isConnectedToOtherSources ) { - return null; - } - - function updateBindings( isChecked ) { - let updatedBindings = { - ...attributes?.metadata?.bindings, - }; - - if ( ! isChecked ) { - for ( const attributeName of syncedAttributes ) { - if ( - updatedBindings[ attributeName ]?.source === - 'core/pattern-overrides' - ) { - delete updatedBindings[ attributeName ]; - } - } - if ( ! Object.keys( updatedBindings ).length ) { - updatedBindings = undefined; - } - setAttributes( { - metadata: { - ...attributes.metadata, - bindings: updatedBindings, - }, - } ); - return; - } - - for ( const attributeName of syncedAttributes ) { - if ( ! updatedBindings[ attributeName ] ) { - updatedBindings[ attributeName ] = { - source: 'core/pattern-overrides', - }; - } - } - - if ( typeof attributes.metadata?.id === 'string' ) { - setAttributes( { - metadata: { - ...attributes.metadata, - bindings: updatedBindings, - }, - } ); - return; - } - - const id = nanoid( 6 ); - setAttributes( { - metadata: { - ...attributes.metadata, - id, - bindings: updatedBindings, - }, - } ); - } - - return ( - - - - { __( 'Pattern overrides' ) } - - source === 'core/pattern-overrides' - ) } - onChange={ ( isChecked ) => { - updateBindings( isChecked ); - } } - /> - - - ); -} - -export default PartialSyncingControls; diff --git a/packages/patterns/src/components/reset-overrides-control.js b/packages/patterns/src/components/reset-overrides-control.js index 586f460835234..1d3ae013addd3 100644 --- a/packages/patterns/src/components/reset-overrides-control.js +++ b/packages/patterns/src/components/reset-overrides-control.js @@ -11,13 +11,13 @@ import { store as coreStore } from '@wordpress/core-data'; import { parse } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; -function recursivelyFindBlockWithId( blocks, id ) { +function recursivelyFindBlockWithName( blocks, name ) { for ( const block of blocks ) { - if ( block.attributes.metadata?.id === id ) { + if ( block.attributes.metadata?.name === name ) { return block; } - const found = recursivelyFindBlockWithId( block.innerBlocks, id ); + const found = recursivelyFindBlockWithName( block.innerBlocks, name ); if ( found ) { return found; } @@ -26,10 +26,10 @@ function recursivelyFindBlockWithId( blocks, id ) { export default function ResetOverridesControl( props ) { const registry = useRegistry(); - const id = props.attributes.metadata?.id; + const name = props.attributes.metadata?.name; const patternWithOverrides = useSelect( ( select ) => { - if ( ! id ) { + if ( ! name ) { return undefined; } @@ -39,13 +39,13 @@ export default function ResetOverridesControl( props ) { getBlockParentsByBlockName( props.clientId, 'core/block' ) )[ 0 ]; - if ( ! patternBlock?.attributes.content?.[ id ] ) { + if ( ! patternBlock?.attributes.content?.[ name ] ) { return undefined; } return patternBlock; }, - [ props.clientId, id ] + [ props.clientId, name ] ); const resetOverrides = async () => { @@ -57,7 +57,7 @@ export default function ResetOverridesControl( props ) { patternWithOverrides.attributes.ref ); const blocks = editedRecord.blocks ?? parse( editedRecord.content ); - const block = recursivelyFindBlockWithId( blocks, id ); + const block = recursivelyFindBlockWithName( blocks, name ); const newAttributes = Object.assign( // Reset every existing attribute to undefined. diff --git a/packages/patterns/src/components/use-set-pattern-bindings.js b/packages/patterns/src/components/use-set-pattern-bindings.js new file mode 100644 index 0000000000000..df16d2b2b0591 --- /dev/null +++ b/packages/patterns/src/components/use-set-pattern-bindings.js @@ -0,0 +1,106 @@ +/** + * WordPress dependencies + */ +import { usePrevious } from '@wordpress/compose'; +import { useEffect } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import { PARTIAL_SYNCING_SUPPORTED_BLOCKS } from '../constants'; + +function removeBindings( bindings, syncedAttributes ) { + let updatedBindings = {}; + for ( const attributeName of syncedAttributes ) { + // Omit any pattern override bindings from the `updatedBindings` object. + if ( + bindings?.[ attributeName ]?.source !== 'core/pattern-overrides' && + bindings?.[ attributeName ]?.source !== undefined + ) { + updatedBindings[ attributeName ] = bindings[ attributeName ]; + } + } + if ( ! Object.keys( updatedBindings ).length ) { + updatedBindings = undefined; + } + return updatedBindings; +} + +function addBindings( bindings, syncedAttributes ) { + const updatedBindings = { ...bindings }; + for ( const attributeName of syncedAttributes ) { + if ( ! bindings?.[ attributeName ] ) { + updatedBindings[ attributeName ] = { + source: 'core/pattern-overrides', + }; + } + } + return updatedBindings; +} + +export default function useSetPatternBindings( + { name, attributes, setAttributes }, + currentPostType +) { + const metadataName = attributes?.metadata?.name ?? ''; + const prevMetadataName = usePrevious( metadataName ) ?? ''; + const bindings = attributes?.metadata?.bindings; + + useEffect( () => { + // Bindings should only be created when editing a wp_block post type, + // and also when there's a change to the user-given name for the block. + if ( + currentPostType !== 'wp_block' || + metadataName === prevMetadataName + ) { + return; + } + + const syncedAttributes = PARTIAL_SYNCING_SUPPORTED_BLOCKS[ name ]; + const attributeSources = syncedAttributes.map( + ( attributeName ) => + attributes.metadata?.bindings?.[ attributeName ]?.source + ); + const isConnectedToOtherSources = attributeSources.every( + ( source ) => source && source !== 'core/pattern-overrides' + ); + + // Avoid overwriting other (e.g. meta) bindings. + if ( isConnectedToOtherSources ) { + return; + } + + // The user-given name for the block was deleted, remove the bindings. + if ( ! metadataName?.length && prevMetadataName?.length ) { + const updatedBindings = removeBindings( + bindings, + syncedAttributes + ); + setAttributes( { + metadata: { + ...attributes.metadata, + bindings: updatedBindings, + }, + } ); + } + + // The user-given name for the block was set, set the bindings. + if ( ! prevMetadataName?.length && metadataName.length ) { + const updatedBindings = addBindings( bindings, syncedAttributes ); + setAttributes( { + metadata: { + ...attributes.metadata, + bindings: updatedBindings, + }, + } ); + } + }, [ + bindings, + prevMetadataName, + metadataName, + currentPostType, + name, + attributes.metadata, + setAttributes, + ] ); +} diff --git a/packages/patterns/src/private-apis.js b/packages/patterns/src/private-apis.js index a5fbddb62fd62..54ad5a4aa47d1 100644 --- a/packages/patterns/src/private-apis.js +++ b/packages/patterns/src/private-apis.js @@ -13,7 +13,7 @@ import { import RenamePatternModal from './components/rename-pattern-modal'; import PatternsMenuItems from './components'; import RenamePatternCategoryModal from './components/rename-pattern-category-modal'; -import PartialSyncingControls from './components/partial-syncing-controls'; +import useSetPatternBindings from './components/use-set-pattern-bindings'; import ResetOverridesControl from './components/reset-overrides-control'; import { useAddPatternCategory } from './private-hooks'; import { @@ -34,7 +34,7 @@ lock( privateApis, { RenamePatternModal, PatternsMenuItems, RenamePatternCategoryModal, - PartialSyncingControls, + useSetPatternBindings, ResetOverridesControl, useAddPatternCategory, PATTERN_TYPES, diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index 86d7b9117bef5..4542e8c789ad1 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -32,7 +32,7 @@ test.describe( 'Pattern Overrides', () => { editor, } ) => { let patternId; - let editableParagraphId; + const editableParagraphName = 'Editable Paragraph'; await test.step( 'Create a synced pattern and assign blocks to allow overrides', async () => { await admin.visitSiteEditor( { path: '/patterns' } ); @@ -85,8 +85,8 @@ test.describe( 'Pattern Overrides', () => { await advancedPanel.click(); } await editorSettings - .getByRole( 'checkbox', { name: 'Allow instance overrides' } ) - .setChecked( true ); + .getByRole( 'textbox', { name: 'Block Name' } ) + .fill( editableParagraphName ); await expect.poll( editor.getBlocks ).toMatchObject( [ { @@ -94,7 +94,7 @@ test.describe( 'Pattern Overrides', () => { attributes: { content: 'This paragraph can be edited', metadata: { - id: expect.any( String ), + name: editableParagraphName, bindings: { content: { source: 'core/pattern-overrides', @@ -123,8 +123,6 @@ test.describe( 'Pattern Overrides', () => { ).toBeVisible(); patternId = new URL( page.url() ).searchParams.get( 'postId' ); - const blocks = await editor.getBlocks(); - editableParagraphId = blocks[ 0 ].attributes.metadata.id; } ); await test.step( 'Create a post and insert the pattern with overrides', async () => { @@ -176,10 +174,8 @@ test.describe( 'Pattern Overrides', () => { attributes: { ref: patternId, content: { - [ editableParagraphId ]: { - values: { - content: 'I would word it this way', - }, + [ editableParagraphName ]: { + content: 'I would word it this way', }, }, }, @@ -189,10 +185,8 @@ test.describe( 'Pattern Overrides', () => { attributes: { ref: patternId, content: { - [ editableParagraphId ]: { - values: { - content: 'This one is different', - }, + [ editableParagraphName ]: { + content: 'This one is different', }, }, }, @@ -276,11 +270,11 @@ test.describe( 'Pattern Overrides', () => { editor, context, } ) => { - const buttonId = 'button-id'; + const buttonName = 'Editable button'; const { id } = await requestUtils.createBlock( { title: 'Button with target', content: ` -
+ `, @@ -386,21 +380,21 @@ test.describe( 'Pattern Overrides', () => { requestUtils, editor, } ) => { - const paragraphId = 'paragraph-id'; - const headingId = 'heading-id'; + const paragraphName = 'Editable paragraph'; + 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

-`, +`, status: 'publish', } ); @@ -425,8 +419,8 @@ test.describe( 'Pattern Overrides', () => { attributes: { ref: outerPattern.id, content: { - [ headingId ]: { - values: { content: 'Outer heading (edited)' }, + [ headingName ]: { + content: 'Outer heading (edited)', }, }, }, @@ -440,10 +434,8 @@ test.describe( 'Pattern Overrides', () => { attributes: { ref: innerPattern.id, content: { - [ paragraphId ]: { - values: { - content: 'Inner paragraph (edited)', - }, + [ paragraphName ]: { + content: 'Inner paragraph (edited)', }, }, }, @@ -505,14 +497,14 @@ test.describe( 'Pattern Overrides', () => { requestUtils, editor, } ) => { - const headingId = 'heading-id'; - const paragraphId = 'paragraph-id'; + const headingName = 'Editable heading'; + const paragraphName = 'Editable paragraph'; const { id } = await requestUtils.createBlock( { title: 'Pattern', - content: ` + content: `

Heading

- +

Paragraph

`, status: 'publish', @@ -597,14 +589,14 @@ test.describe( 'Pattern Overrides', () => { requestUtils, editor, } ) => { - const imageId = 'image-id'; + 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: ` + content: `
`, status: 'publish', diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.html b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.html new file mode 100644 index 0000000000000..5b7b2cdf95cfb --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.html @@ -0,0 +1 @@ + diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.json b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.json new file mode 100644 index 0000000000000..3f0292c83ac35 --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.json @@ -0,0 +1,15 @@ +[ + { + "name": "core/block", + "isValid": true, + "attributes": { + "ref": 123, + "content": { + "V98q_x": { + "content": "Some value" + } + } + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.parsed.json b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.parsed.json new file mode 100644 index 0000000000000..2cf2ac3797445 --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.parsed.json @@ -0,0 +1,16 @@ +[ + { + "blockName": "core/block", + "attrs": { + "ref": 123, + "overrides": { + "V98q_x": { + "content": "Some value" + } + } + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.serialized.html b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.serialized.html new file mode 100644 index 0000000000000..3d91f47859d01 --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-1.serialized.html @@ -0,0 +1 @@ + diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.html b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.html new file mode 100644 index 0000000000000..d75c773c630ef --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.html @@ -0,0 +1 @@ + diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.json b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.json new file mode 100644 index 0000000000000..3f0292c83ac35 --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.json @@ -0,0 +1,15 @@ +[ + { + "name": "core/block", + "isValid": true, + "attributes": { + "ref": 123, + "content": { + "V98q_x": { + "content": "Some value" + } + } + }, + "innerBlocks": [] + } +] diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.parsed.json b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.parsed.json new file mode 100644 index 0000000000000..41cd1dc9afd44 --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.parsed.json @@ -0,0 +1,18 @@ +[ + { + "blockName": "core/block", + "attrs": { + "ref": 123, + "content": { + "V98q_x": { + "values": { + "content": "Some value" + } + } + } + }, + "innerBlocks": [], + "innerHTML": "", + "innerContent": [] + } +] diff --git a/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.serialized.html b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.serialized.html new file mode 100644 index 0000000000000..3d91f47859d01 --- /dev/null +++ b/test/integration/fixtures/blocks/core__block__overrides__deprecated-2.serialized.html @@ -0,0 +1 @@ +