-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
POC: Implement partially synced patterns using the block bindings API #55807
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
/** | ||
* Pattern block support flag. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
$gutenberg_experiments = get_option( 'gutenberg-experiments' ); | ||
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-connections', $gutenberg_experiments ) ) { | ||
function gutenberg_register_pattern_support( $block_type ) { | ||
$pattern_support = property_exists( $block_type, 'supports' ) ? _wp_array_get( $block_type->supports, array( '__experimentalConnections' ), false ) : false; | ||
|
||
if ( $pattern_support ) { | ||
if ( ! $block_type->uses_context ) { | ||
$block_type->uses_context = array(); | ||
} | ||
|
||
if ( ! in_array( 'dynamicContent', $block_type->uses_context, true ) ) { | ||
$block_type->uses_context[] = 'dynamicContent'; | ||
} | ||
} | ||
} | ||
|
||
// Register the block support. | ||
WP_Block_Supports::get_instance()->register( | ||
'pattern', | ||
array( | ||
'register_attribute' => 'gutenberg_register_pattern_support', | ||
) | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,7 @@ | |
// if it doesn't, `get_post_meta()` will just return an empty string. | ||
return get_post_meta( $block_instance->context['postId'], $meta_field, true ); | ||
}, | ||
'pattern_attributes' => function ( $block_instance, $meta_field ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great to get confirmation from @michalczaplinski or @SantosGuillamot on how this array should be shaped. There is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that nothing was ultimately decided here! Just for reference, this is a previous discussion with Riad: #53247 (comment) We'll need a nested array here OR have a one file per source (like in Riad's original prototype) so |
||
return _wp_array_get( $block_instance->context, array( 'dynamicContent', $meta_field ), false ); | ||
} | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useRegistry } from '@wordpress/data'; | ||
import { addFilter } from '@wordpress/hooks'; | ||
import { PanelBody, TextControl } from '@wordpress/components'; | ||
import { PanelBody, TextControl, SelectControl } from '@wordpress/components'; | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { hasBlockSupport } from '@wordpress/blocks'; | ||
import { createHigherOrderComponent } from '@wordpress/compose'; | ||
|
@@ -55,7 +56,11 @@ const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { | |
|
||
// Check if the current block is a paragraph or image block. | ||
// Currently, only these two blocks are supported. | ||
if ( ! [ 'core/paragraph', 'core/image' ].includes( props.name ) ) { | ||
if ( | ||
! [ 'core/paragraph', 'core/image', 'core/heading' ].includes( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We discussed enabling the Button block, too. It's fine to take care of it as a follow-up. I'm leaving it mostly as a note. |
||
props.name | ||
) | ||
) { | ||
return <BlockEdit { ...props } />; | ||
} | ||
|
||
|
@@ -65,6 +70,41 @@ const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { | |
let attributeName; | ||
if ( props.name === 'core/paragraph' ) attributeName = 'content'; | ||
if ( props.name === 'core/image' ) attributeName = 'url'; | ||
if ( props.name === 'core/heading' ) attributeName = 'content'; | ||
|
||
const connectionSource = | ||
props.attributes?.connections?.attributes?.[ attributeName ] | ||
?.source || ''; | ||
const connectionValue = | ||
props.attributes?.connections?.attributes?.[ attributeName ] | ||
?.value || ''; | ||
|
||
function updateConnections( source, value ) { | ||
if ( value === '' ) { | ||
props.setAttributes( { | ||
connections: undefined, | ||
placeholder: undefined, | ||
} ); | ||
} else { | ||
props.setAttributes( { | ||
connections: { | ||
attributes: { | ||
// The attributeName will be either `content` or `url`. | ||
[ attributeName ]: { | ||
// Source will be variable, could be post_meta, user_meta, term_meta, etc. | ||
// Could even be a custom source like a social media attribute. | ||
source, | ||
value, | ||
}, | ||
}, | ||
}, | ||
placeholder: sprintf( | ||
'This content will be replaced on the frontend by the value of "%s" custom field.', | ||
value | ||
), | ||
} ); | ||
} | ||
} | ||
|
||
if ( hasCustomFieldsSupport && props.isSelected ) { | ||
return ( | ||
|
@@ -76,42 +116,40 @@ const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { | |
title={ __( 'Connections' ) } | ||
initialOpen={ true } | ||
> | ||
<SelectControl | ||
label={ __( 'Source' ) } | ||
value={ connectionSource } | ||
options={ [ | ||
{ | ||
label: __( 'None' ), | ||
value: '', | ||
}, | ||
{ | ||
label: __( 'Meta fields' ), | ||
value: 'meta_fields', | ||
}, | ||
{ | ||
label: __( 'Pattern attributes' ), | ||
value: 'pattern_attributes', | ||
}, | ||
] } | ||
onChange={ ( nextSource ) => { | ||
updateConnections( | ||
nextSource, | ||
connectionValue | ||
); | ||
} } | ||
/> | ||
<TextControl | ||
__nextHasNoMarginBottom | ||
autoComplete="off" | ||
label={ __( 'Custom field meta_key' ) } | ||
value={ | ||
props.attributes?.connections | ||
?.attributes?.[ attributeName ] | ||
?.value || '' | ||
} | ||
value={ connectionValue } | ||
onChange={ ( nextValue ) => { | ||
if ( nextValue === '' ) { | ||
props.setAttributes( { | ||
connections: undefined, | ||
[ attributeName ]: undefined, | ||
placeholder: undefined, | ||
} ); | ||
} else { | ||
props.setAttributes( { | ||
connections: { | ||
attributes: { | ||
// The attributeName will be either `content` or `url`. | ||
[ attributeName ]: { | ||
// Source will be variable, could be post_meta, user_meta, term_meta, etc. | ||
// Could even be a custom source like a social media attribute. | ||
source: 'meta_fields', | ||
value: nextValue, | ||
}, | ||
}, | ||
}, | ||
[ attributeName ]: undefined, | ||
placeholder: sprintf( | ||
'This content will be replaced on the frontend by the value of "%s" custom field.', | ||
nextValue | ||
), | ||
} ); | ||
} | ||
updateConnections( | ||
connectionSource, | ||
nextValue | ||
); | ||
} } | ||
/> | ||
</PanelBody> | ||
|
@@ -125,6 +163,30 @@ const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { | |
}; | ||
}, 'withInspectorControl' ); | ||
|
||
const createEditFunctionWithPatternSource = () => | ||
createHigherOrderComponent( | ||
( BlockEdit ) => | ||
( { attributes, ...props } ) => { | ||
const registry = useRegistry(); | ||
const sourceAttributes = | ||
registry._selectAttributes?.( { | ||
clientId: props.clientId, | ||
name: props.name, | ||
attributes, | ||
} ) ?? attributes; | ||
|
||
return ( | ||
<BlockEdit { ...props } attributes={ sourceAttributes } /> | ||
); | ||
} | ||
); | ||
|
||
function shimAttributeSource( settings ) { | ||
settings.edit = createEditFunctionWithPatternSource()( settings.edit ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the role of this HOC? There is a sub-registry injected in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, it was simply the minimum effort for me to make the POC work at the time. We should revisit the "reading" side of the story. |
||
|
||
return settings; | ||
} | ||
|
||
if ( window.__experimentalConnections ) { | ||
addFilter( | ||
'blocks.registerBlockType', | ||
|
@@ -136,4 +198,9 @@ if ( window.__experimentalConnections ) { | |
'core/connections/with-inspector-control', | ||
withInspectorControl | ||
); | ||
addFilter( | ||
'blocks.registerBlockType', | ||
'core/pattern/shimAttributeSource', | ||
shimAttributeSource | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,9 @@ | |
"keywords": [ "reusable" ], | ||
"textdomain": "default", | ||
"attributes": { | ||
"dynamicContent": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we plan to keep it as an experiment, then it should get injected from outside, similar to |
||
"type": "object" | ||
}, | ||
"ref": { | ||
"type": "number" | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noting, the injected context is only necessary when dealing with Partially Synced Patterns. The condition based on the UI elements seems to work fine here, but it isn't a direct relationship.