diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 35cecf6a27e663..bc54069bb4e15b 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -42,7 +42,7 @@ Reuse this design across your site. ([Source](https://github.com/WordPress/guten - **Name:** core/block - **Category:** reusable - **Supports:** interactivity (clientNavigation), ~~customClassName~~, ~~html~~, ~~inserter~~, ~~renaming~~ -- **Attributes:** overrides, ref +- **Attributes:** content, ref ## Button diff --git a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php index 518348a43b8123..9168cfc785b557 100644 --- a/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php +++ b/lib/compat/wordpress-6.5/block-bindings/sources/pattern.php @@ -9,7 +9,7 @@ function gutenberg_block_bindings_pattern_overrides_callback( $source_attrs, $bl return null; } $block_id = $block_instance->attributes['metadata']['id']; - return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, $attribute_name ), null ); + return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $block_id, 'values', $attribute_name ), null ); } function gutenberg_register_block_bindings_pattern_overrides_source() { diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json index 0e5565233ef3c2..34dcb9a396ac6f 100644 --- a/packages/block-library/src/block/block.json +++ b/packages/block-library/src/block/block.json @@ -11,7 +11,7 @@ "ref": { "type": "number" }, - "overrides": { + "content": { "type": "object" } }, diff --git a/packages/block-library/src/block/deprecated.js b/packages/block-library/src/block/deprecated.js new file mode 100644 index 00000000000000..7bc243bbf4ce98 --- /dev/null +++ b/packages/block-library/src/block/deprecated.js @@ -0,0 +1,57 @@ +// v1: Migrate and rename the `overrides` attribute to the `content` attribute. +const v1 = { + attributes: { + ref: { + type: 'number', + }, + overrides: { + type: 'object', + }, + }, + supports: { + customClassName: false, + html: false, + inserter: false, + renaming: false, + }, + // Force this deprecation to run whenever there's an `overrides` object. + isEligible( { overrides } ) { + return !! overrides; + }, + /* + * Old attribute format: + * overrides: { + * // An key is an id that represents a block. + * // The values are the attribute values of the block. + * "V98q_x": { content: 'dwefwefwefwe' } + * } + * + * 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. + * } + * } + * + */ + migrate( attributes ) { + const { overrides, ...retainedAttributes } = attributes; + + const content = {}; + + Object.keys( overrides ).forEach( ( id ) => { + content[ id ] = { + values: overrides[ id ], + }; + } ); + + return { + ...retainedAttributes, + content, + }; + }, +}; + +export default [ v1 ]; diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index ae1cca181561f3..9c1e81de04e17a 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -38,25 +38,6 @@ import { unlock } from '../lock-unlock'; const { useLayoutClasses } = unlock( blockEditorPrivateApis ); const { PARTIAL_SYNCING_SUPPORTED_BLOCKS } = unlock( patternsPrivateApis ); -function isPartiallySynced( block ) { - return ( - Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS ).includes( - block.name - ) && - !! block.attributes.metadata?.bindings && - Object.values( block.attributes.metadata.bindings ).some( - ( binding ) => binding.source === 'core/pattern-overrides' - ) - ); -} -function getPartiallySyncedAttributes( block ) { - return Object.entries( block.attributes.metadata.bindings ) - .filter( - ( [ , binding ] ) => binding.source === 'core/pattern-overrides' - ) - .map( ( [ attributeKey ] ) => attributeKey ); -} - const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; const useInferredLayout = ( blocks, parentLayout ) => { @@ -88,25 +69,57 @@ const useInferredLayout = ( blocks, parentLayout ) => { }, [ blocks, parentLayout ] ); }; -function applyInitialOverrides( blocks, overrides = {}, defaultValues ) { +function hasOverridableAttributes( block ) { + return ( + Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS ).includes( + block.name + ) && + !! block.attributes.metadata?.bindings && + Object.values( block.attributes.metadata.bindings ).some( + ( binding ) => binding.source === 'core/pattern-overrides' + ) + ); +} + +function hasOverridableBlocks( blocks ) { + return blocks.some( ( block ) => { + if ( hasOverridableAttributes( block ) ) return true; + return hasOverridableBlocks( block.innerBlocks ); + } ); +} + +function getOverridableAttributes( block ) { + return Object.entries( block.attributes.metadata.bindings ) + .filter( + ( [ , binding ] ) => binding.source === 'core/pattern-overrides' + ) + .map( ( [ attributeKey ] ) => attributeKey ); +} + +function applyInitialContentValuesToInnerBlocks( + blocks, + content = {}, + defaultValues +) { return blocks.map( ( block ) => { - const innerBlocks = applyInitialOverrides( + const innerBlocks = applyInitialContentValuesToInnerBlocks( block.innerBlocks, - overrides, + content, defaultValues ); const blockId = block.attributes.metadata?.id; - if ( ! isPartiallySynced( block ) || ! blockId ) + if ( ! hasOverridableAttributes( block ) || ! blockId ) return { ...block, innerBlocks }; - const attributes = getPartiallySyncedAttributes( block ); + const attributes = getOverridableAttributes( block ); const newAttributes = { ...block.attributes }; for ( const attributeKey of attributes ) { defaultValues[ blockId ] ??= {}; defaultValues[ blockId ][ attributeKey ] = block.attributes[ attributeKey ]; - if ( overrides[ blockId ]?.[ attributeKey ] !== undefined ) { - newAttributes[ attributeKey ] = - overrides[ blockId ][ attributeKey ]; + + const contentValues = content[ blockId ]?.values; + if ( contentValues?.[ attributeKey ] !== undefined ) { + newAttributes[ attributeKey ] = contentValues[ attributeKey ]; } } return { @@ -117,52 +130,46 @@ function applyInitialOverrides( blocks, overrides = {}, defaultValues ) { } ); } -function getOverridesFromBlocks( blocks, defaultValues ) { - /** @type {Record>} */ - const overrides = {}; +function getContentValuesFromInnerBlocks( blocks, defaultValues ) { + /** @type {Record}>} */ + const content = {}; for ( const block of blocks ) { Object.assign( - overrides, - getOverridesFromBlocks( block.innerBlocks, defaultValues ) + content, + getContentValuesFromInnerBlocks( block.innerBlocks, defaultValues ) ); const blockId = block.attributes.metadata?.id; - if ( ! isPartiallySynced( block ) || ! blockId ) continue; - const attributes = getPartiallySyncedAttributes( block ); + if ( ! hasOverridableAttributes( block ) || ! blockId ) continue; + const attributes = getOverridableAttributes( block ); for ( const attributeKey of attributes ) { if ( block.attributes[ attributeKey ] !== defaultValues[ blockId ][ attributeKey ] ) { - overrides[ blockId ] ??= {}; + content[ blockId ] ??= { values: {} }; // TODO: We need a way to represent `undefined` in the serialized overrides. // Also see: https://github.com/WordPress/gutenberg/pull/57249#discussion_r1452987871 - overrides[ blockId ][ attributeKey ] = + content[ blockId ].values[ attributeKey ] = block.attributes[ attributeKey ]; } } } - return Object.keys( overrides ).length > 0 ? overrides : undefined; + return Object.keys( content ).length > 0 ? content : undefined; } function setBlockEditMode( setEditMode, blocks, mode ) { blocks.forEach( ( block ) => { const editMode = - mode || ( isPartiallySynced( block ) ? 'contentOnly' : 'disabled' ); + mode || + ( hasOverridableAttributes( block ) ? 'contentOnly' : 'disabled' ); setEditMode( block.clientId, editMode ); setBlockEditMode( setEditMode, block.innerBlocks, mode ); } ); } -function getHasOverridableBlocks( blocks ) { - return blocks.some( ( block ) => { - if ( isPartiallySynced( block ) ) return true; - return getHasOverridableBlocks( block.innerBlocks ); - } ); -} - export default function ReusableBlockEdit( { name, - attributes: { ref, overrides }, + attributes: { ref, content }, __unstableParentLayout: parentLayout, clientId: patternClientId, setAttributes, @@ -175,8 +182,13 @@ export default function ReusableBlockEdit( { ref ); const isMissing = hasResolved && ! record; - const initialOverrides = useRef( overrides ); - const defaultValuesRef = useRef( {} ); + + // The initial value of the `content` attribute. + const initialContent = useRef( content ); + + // The default content values from the original pattern for overridable attributes. + // Set by the `applyInitialContentValuesToInnerBlocks` function. + const defaultContent = useRef( {} ); const { replaceInnerBlocks, @@ -220,8 +232,8 @@ export default function ReusableBlockEdit( { [ innerBlocks, setBlockEditingMode ] ); - const hasOverridableBlocks = useMemo( - () => getHasOverridableBlocks( innerBlocks ), + const canOverrideBlocks = useMemo( + () => hasOverridableBlocks( innerBlocks ), [ innerBlocks ] ); @@ -237,18 +249,17 @@ export default function ReusableBlockEdit( { // Apply the initial overrides from the pattern block to the inner blocks. useEffect( () => { - defaultValuesRef.current = {}; + defaultContent.current = {}; const editingMode = getBlockEditingMode( patternClientId ); - // Replace the contents of the blocks with the overrides. registry.batch( () => { setBlockEditingMode( patternClientId, 'default' ); syncDerivedUpdates( () => { replaceInnerBlocks( patternClientId, - applyInitialOverrides( + applyInitialContentValuesToInnerBlocks( initialBlocks, - initialOverrides.current, - defaultValuesRef.current + initialContent.current, + defaultContent.current ) ); } ); @@ -287,7 +298,7 @@ export default function ReusableBlockEdit( { : InnerBlocks.ButtonBlockAppender, } ); - // Sync the `overrides` attribute from the updated blocks to the pattern block. + // Sync the `content` attribute from the updated blocks to the pattern block. // `syncDerivedUpdates` is used here to avoid creating an additional undo level. useEffect( () => { const { getBlocks } = registry.select( blockEditorStore ); @@ -298,9 +309,9 @@ export default function ReusableBlockEdit( { prevBlocks = blocks; syncDerivedUpdates( () => { setAttributes( { - overrides: getOverridesFromBlocks( + content: getContentValuesFromInnerBlocks( blocks, - defaultValuesRef.current + defaultContent.current ), } ); } ); @@ -313,8 +324,8 @@ export default function ReusableBlockEdit( { editOriginalProps.onClick( event ); }; - const resetOverrides = () => { - if ( overrides ) { + const resetContent = () => { + if ( content ) { replaceInnerBlocks( patternClientId, initialBlocks ); } }; @@ -360,12 +371,12 @@ export default function ReusableBlockEdit( { ) } - { hasOverridableBlocks && ( + { canOverrideBlocks && ( { __( 'Reset' ) } diff --git a/packages/block-library/src/block/index.js b/packages/block-library/src/block/index.js index 95e090f0afd6ad..a3272e8e174f99 100644 --- a/packages/block-library/src/block/index.js +++ b/packages/block-library/src/block/index.js @@ -9,12 +9,14 @@ import { symbol as icon } from '@wordpress/icons'; import initBlock from '../utils/init-block'; import metadata from './block.json'; import edit from './edit'; +import deprecated from './deprecated'; const { name } = metadata; export { metadata, name }; export const settings = { + deprecated, edit, icon, }; diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index 444001fa498595..8e24317501d9fa 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -46,7 +46,20 @@ function render_block_core_block( $attributes ) { $content = $wp_embed->run_shortcode( $reusable_block->post_content ); $content = $wp_embed->autoembed( $content ); - $has_pattern_overrides = isset( $attributes['overrides'] ); + // 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, + ); + } + $attributes['content'] = $migrated_content; + } + $has_pattern_overrides = isset( $attributes['content'] ); /** * We set the `pattern/overrides` context through the `render_block_context` @@ -55,7 +68,7 @@ function render_block_core_block( $attributes ) { */ if ( $has_pattern_overrides ) { $filter_block_context = static function ( $context ) use ( $attributes ) { - $context['pattern/overrides'] = $attributes['overrides']; + $context['pattern/overrides'] = $attributes['content']; return $context; }; add_filter( 'render_block_context', $filter_block_context, 1 ); diff --git a/test/e2e/specs/editor/various/pattern-overrides.spec.js b/test/e2e/specs/editor/various/pattern-overrides.spec.js index ca9fbc8d07560e..ae70f575fbb28b 100644 --- a/test/e2e/specs/editor/various/pattern-overrides.spec.js +++ b/test/e2e/specs/editor/various/pattern-overrides.spec.js @@ -170,9 +170,11 @@ test.describe( 'Pattern Overrides', () => { name: 'core/block', attributes: { ref: patternId, - overrides: { + content: { [ editableParagraphId ]: { - content: 'I would word it this way', + values: { + content: 'I would word it this way', + }, }, }, }, @@ -181,9 +183,11 @@ test.describe( 'Pattern Overrides', () => { name: 'core/block', attributes: { ref: patternId, - overrides: { + content: { [ editableParagraphId ]: { - content: 'This one is different', + values: { + content: 'This one is different', + }, }, }, },