Skip to content

Commit

Permalink
Automatically assign block ids to pattern blocks for use in partial s…
Browse files Browse the repository at this point in the history
…ynching of pattern instances (#56495)

* Automatically assign block ids

* Downgrade package to fix tooling

* Move to the editor package and allow core/button

* Fix test resolver
  • Loading branch information
kevin940726 authored Nov 30, 2023
1 parent 66ed476 commit fd0d8e3
Show file tree
Hide file tree
Showing 13 changed files with 270 additions and 91 deletions.
28 changes: 19 additions & 9 deletions lib/experimental/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ function gutenberg_render_block_connections( $block_content, $block, $block_inst
// Allowlist of blocks that support block connections.
// Currently, we only allow the following blocks and attributes:
// - Paragraph: content.
// - Button: text.
// - Image: url.
$blocks_attributes_allowlist = array(
'core/paragraph' => array( 'content' ),
'core/button' => array( 'text' ),
'core/image' => array( 'url' ),
);

Expand Down Expand Up @@ -142,17 +144,25 @@ function gutenberg_render_block_connections( $block_content, $block, $block_inst
continue;
}

// If the attribute does not specify the name of the custom field, skip it.
if ( ! isset( $attribute_value['value'] ) ) {
continue;
if ( 'pattern_attributes' === $attribute_value['source'] ) {
if ( ! _wp_array_get( $block_instance->attributes, array( 'metadata', 'id' ), false ) ) {
continue;
}

$custom_value = $connection_sources[ $attribute_value['source'] ]( $block_instance );
} else {
// If the attribute does not specify the name of the custom field, skip it.
if ( ! isset( $attribute_value['value'] ) ) {
continue;
}

// Get the content from the connection source.
$custom_value = $connection_sources[ $attribute_value['source'] ](
$block_instance,
$attribute_value['value']
);
}

// Get the content from the connection source.
$custom_value = $connection_sources[ $attribute_value['source'] ](
$block_instance,
$attribute_value['value']
);

if ( false === $custom_value ) {
continue;
}
Expand Down
5 changes: 3 additions & 2 deletions lib/experimental/connection-sources/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
// 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 ) {
return _wp_array_get( $block_instance->context, array( 'overrides', $meta_field ), false );
'pattern_attributes' => function ( $block_instance ) {
$block_id = $block_instance->attributes['metadata']['id'];
return _wp_array_get( $block_instance->context, array( 'overrides', $block_id ), false );
},
);
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 32 additions & 58 deletions packages/block-editor/src/hooks/custom-fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import { PanelBody, TextControl, SelectControl } from '@wordpress/components';
import { PanelBody, TextControl } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
import { hasBlockSupport } from '@wordpress/blocks';
import { createHigherOrderComponent } from '@wordpress/compose';
Expand Down Expand Up @@ -47,71 +47,45 @@ function CustomFieldsControl( props ) {
if ( props.name === 'core/paragraph' ) attributeName = 'content';
if ( props.name === 'core/image' ) attributeName = 'url';

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
),
} );
}
}

return (
<InspectorControls>
<PanelBody 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={ connectionValue }
value={
props.attributes?.connections?.attributes?.[
attributeName
]?.value || ''
}
onChange={ ( nextValue ) => {
updateConnections( connectionSource, 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
),
} );
}
} }
/>
</PanelBody>
Expand Down
36 changes: 19 additions & 17 deletions packages/block-library/src/block/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,11 @@ function isPartiallySynced( block ) {
);
}
function getPartiallySyncedAttributes( block ) {
const attributes = {};
for ( const [ attribute, connection ] of Object.entries(
block.attributes.connections.attributes
) ) {
if ( connection.source !== 'pattern_attributes' ) continue;
attributes[ attribute ] = connection.value;
}
return attributes;
return Object.entries( block.attributes.connections.attributes )
.filter(
( [ , connection ] ) => connection.source === 'pattern_attributes'
)
.map( ( [ attributeKey ] ) => attributeKey );
}

const fullAlignments = [ 'full', 'wide', 'left', 'right' ];
Expand Down Expand Up @@ -94,13 +91,15 @@ function applyInitialOverrides( blocks, overrides = {}, defaultValues ) {
overrides,
defaultValues
);
if ( ! isPartiallySynced( block ) ) return { ...block, innerBlocks };
const blockId = block.attributes.metadata?.id;
if ( ! isPartiallySynced( block ) || ! blockId )
return { ...block, innerBlocks };
const attributes = getPartiallySyncedAttributes( block );
const newAttributes = { ...block.attributes };
for ( const [ attributeKey, id ] of Object.entries( attributes ) ) {
defaultValues[ id ] = block.attributes[ attributeKey ];
if ( overrides[ id ] ) {
newAttributes[ attributeKey ] = overrides[ id ];
for ( const attributeKey of attributes ) {
defaultValues[ blockId ] = block.attributes[ attributeKey ];
if ( overrides[ blockId ] ) {
newAttributes[ attributeKey ] = overrides[ blockId ];
}
}
return {
Expand All @@ -119,11 +118,14 @@ function getOverridesFromBlocks( blocks, defaultValues ) {
overrides,
getOverridesFromBlocks( block.innerBlocks, defaultValues )
);
if ( ! isPartiallySynced( block ) ) continue;
const blockId = block.attributes.metadata?.id;
if ( ! isPartiallySynced( block ) || ! blockId ) continue;
const attributes = getPartiallySyncedAttributes( block );
for ( const [ attributeKey, id ] of Object.entries( attributes ) ) {
if ( block.attributes[ attributeKey ] !== defaultValues[ id ] ) {
overrides[ id ] = block.attributes[ attributeKey ];
for ( const attributeKey of attributes ) {
if (
block.attributes[ attributeKey ] !== defaultValues[ blockId ]
) {
overrides[ blockId ] = block.attributes[ attributeKey ];
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/block-library/src/button/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@
"width": true
}
},
"__experimentalSelector": ".wp-block-button .wp-block-button__link"
"__experimentalSelector": ".wp-block-button .wp-block-button__link",
"__experimentalConnections": true
},
"styles": [
{ "name": "fill", "label": "Fill", "isDefault": true },
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
*/
import './custom-sources-backwards-compatibility';
import './default-autocompleters';
import './pattern-partial-syncing';
73 changes: 73 additions & 0 deletions packages/editor/src/hooks/pattern-partial-syncing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import { privateApis } from '@wordpress/patterns';
import { createHigherOrderComponent } from '@wordpress/compose';
import { useBlockEditingMode } from '@wordpress/block-editor';
import { hasBlockSupport } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as editorStore } from '../store';
import { unlock } from '../lock-unlock';

const {
PartialSyncingControls,
PATTERN_TYPES,
PARTIAL_SYNCING_SUPPORTED_BLOCKS,
} = unlock( privateApis );

/**
* Override the default edit UI to include a new block inspector control for
* assigning a partial syncing controls to supported blocks in the pattern editor.
* Currently, only the `core/paragraph` block is supported.
*
* @param {Component} BlockEdit Original component.
*
* @return {Component} Wrapped component.
*/
const withPartialSyncingControls = createHigherOrderComponent(
( BlockEdit ) => ( props ) => {
const blockEditingMode = useBlockEditingMode();
const hasCustomFieldsSupport = hasBlockSupport(
props.name,
'__experimentalConnections',
false
);
const isEditingPattern = useSelect(
( select ) =>
select( editorStore ).getCurrentPostType() ===
PATTERN_TYPES.user,
[]
);

const shouldShowPartialSyncingControls =
hasCustomFieldsSupport &&
props.isSelected &&
isEditingPattern &&
blockEditingMode === 'default' &&
Object.keys( PARTIAL_SYNCING_SUPPORTED_BLOCKS ).includes(
props.name
);

return (
<>
<BlockEdit { ...props } />
{ shouldShowPartialSyncingControls && (
<PartialSyncingControls { ...props } />
) }
</>
);
}
);

if ( window.__experimentalConnections ) {
addFilter(
'editor.BlockEdit',
'core/editor/with-partial-syncing-controls',
withPartialSyncingControls
);
}
3 changes: 2 additions & 1 deletion packages/patterns/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"@wordpress/icons": "file:../icons",
"@wordpress/notices": "file:../notices",
"@wordpress/private-apis": "file:../private-apis",
"@wordpress/url": "file:../url"
"@wordpress/url": "file:../url",
"nanoid": "^3.3.4"
},
"peerDependencies": {
"react": "^18.0.0",
Expand Down
Loading

0 comments on commit fd0d8e3

Please sign in to comment.