Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pattern overrides: Update overrides attribute data structure and rename it to content #58596

Merged
merged 13 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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,
};
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
Loading