Skip to content

Commit

Permalink
Pattern overrides: Update overrides attribute data structure and rena…
Browse files Browse the repository at this point in the history
…me it to `content` (#58596)

* Add a migration for changing the `overrides` format and renaming the attribute to `content`

* Update the pattern block edit function to use the new content attribute and nested `values` property

* Update naming conventions and add some doc blocks

* Update `pattern/overrides` context to use content attribute (with some back compat) and use new values property in the block bindings callback for overrides

* Revert "Update naming conventions and add some doc blocks"

(this caused a hard to find bug somewhere)

This reverts commit f243630.

* Update naming conventions used by utility functions

* Update more naming conventions within the pattern block edit function

* Fix bug where every content values is updated immediately

* Code quality: Reduce some lengthy lines

* Update e2e test to expect new data structure

* Fix incorrect type

Co-authored-by: Kai Hao <kevin830726@gmail.com>

* Use a simpler format for `defaultValues without the nested `values` property

* Fix invalid type

---------

Co-authored-by: Kai Hao <kevin830726@gmail.com>
  • Loading branch information
talldan and kevin940726 committed Feb 5, 2024
1 parent 023916c commit 2f44afa
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 72 deletions.
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion packages/block-library/src/block/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"ref": {
"type": "number"
},
"overrides": {
"content": {
"type": "object"
}
},
Expand Down
57 changes: 57 additions & 0 deletions packages/block-library/src/block/deprecated.js
Original file line number Diff line number Diff line change
@@ -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 ];
137 changes: 74 additions & 63 deletions packages/block-library/src/block/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) => {
Expand Down Expand Up @@ -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 {
Expand All @@ -117,52 +130,46 @@ function applyInitialOverrides( blocks, overrides = {}, defaultValues ) {
} );
}

function getOverridesFromBlocks( blocks, defaultValues ) {
/** @type {Record<string, Record<string, unknown>>} */
const overrides = {};
function getContentValuesFromInnerBlocks( blocks, defaultValues ) {
/** @type {Record<string, { values: Record<string, unknown>}>} */
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,
Expand All @@ -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,
Expand Down Expand Up @@ -220,8 +232,8 @@ export default function ReusableBlockEdit( {
[ innerBlocks, setBlockEditingMode ]
);

const hasOverridableBlocks = useMemo(
() => getHasOverridableBlocks( innerBlocks ),
const canOverrideBlocks = useMemo(
() => hasOverridableBlocks( innerBlocks ),
[ innerBlocks ]
);

Expand All @@ -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
)
);
} );
Expand Down Expand Up @@ -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 );
Expand All @@ -298,9 +309,9 @@ export default function ReusableBlockEdit( {
prevBlocks = blocks;
syncDerivedUpdates( () => {
setAttributes( {
overrides: getOverridesFromBlocks(
content: getContentValuesFromInnerBlocks(
blocks,
defaultValuesRef.current
defaultContent.current
),
} );
} );
Expand All @@ -313,8 +324,8 @@ export default function ReusableBlockEdit( {
editOriginalProps.onClick( event );
};

const resetOverrides = () => {
if ( overrides ) {
const resetContent = () => {
if ( content ) {
replaceInnerBlocks( patternClientId, initialBlocks );
}
};
Expand Down Expand Up @@ -360,12 +371,12 @@ export default function ReusableBlockEdit( {
</BlockControls>
) }

{ hasOverridableBlocks && (
{ canOverrideBlocks && (
<BlockControls>
<ToolbarGroup>
<ToolbarButton
onClick={ resetOverrides }
disabled={ ! overrides }
onClick={ resetContent }
disabled={ ! content }
__experimentalIsFocusable
>
{ __( 'Reset' ) }
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/block/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import { decodeEntities } from '@wordpress/html-entities';
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,
__experimentalLabel: ( { ref } ) => {
Expand Down
17 changes: 15 additions & 2 deletions packages/block-library/src/block/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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 );
Expand Down
Loading

0 comments on commit 2f44afa

Please sign in to comment.