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

Editor: Shim meta attribute source on registered edit component #18960

Merged
merged 10 commits into from
Dec 6, 2019
30 changes: 18 additions & 12 deletions packages/e2e-tests/plugins/meta-attribute-block.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,9 @@
*/

/**
* Registers a custom script and a custom meta for the plugin.
* Registers a custom meta for use by the test block.
*/
function init_test_meta_attribute_block_plugin() {
wp_enqueue_script(
'gutenberg-test-meta-attribute-block',
plugins_url( 'meta-attribute-block/index.js', __FILE__ ),
array(
'wp-blocks',
'wp-element',
),
filemtime( plugin_dir_path( __FILE__ ) . 'meta-attribute-block/index.js' ),
true
);

register_meta(
'post',
'my_meta',
Expand All @@ -34,3 +23,20 @@ function init_test_meta_attribute_block_plugin() {
}

add_action( 'init', 'init_test_meta_attribute_block_plugin' );

/**
* Enqueues block assets for the custom meta test block.
*/
function enqueue_test_meta_attribute_block() {
wp_enqueue_script(
'gutenberg-test-meta-attribute-block',
plugins_url( 'meta-attribute-block/index.js', __FILE__ ),
array(
'wp-blocks',
'wp-element',
),
filemtime( plugin_dir_path( __FILE__ ) . 'meta-attribute-block/index.js' ),
true
);
}
add_action( 'enqueue_block_assets', 'enqueue_test_meta_attribute_block' );
124 changes: 73 additions & 51 deletions packages/editor/src/hooks/custom-sources-backwards-compatibility.js
Original file line number Diff line number Diff line change
@@ -1,76 +1,98 @@
/**
* External dependencies
*/
import { pickBy, mapValues, isEmpty, mapKeys } from 'lodash';

/**
* WordPress dependencies
*/
import { getBlockType } from '@wordpress/blocks';
import { useSelect } from '@wordpress/data';
import { useEntityProp } from '@wordpress/core-data';
import { useMemo, useCallback } from '@wordpress/element';
import { useMemo } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';

const EMPTY_OBJECT = {};
function useMetaAttributeSource( name, _attributes, _setAttributes ) {
const { attributes: attributeTypes = EMPTY_OBJECT } =
getBlockType( name ) || EMPTY_OBJECT;
let [ attributes, setAttributes ] = [ _attributes, _setAttributes ];
/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */
/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */

/**
* Object whose keys are the names of block attributes, where each value
* represents the meta key to which the block attribute is intended to save.
*
* @see https://developer.wordpress.org/reference/functions/register_meta/
*
* @typedef {Object<string,string>} WPMetaAttributeMapping
*/

if ( Object.values( attributeTypes ).some( ( type ) => type.source === 'meta' ) ) {
// eslint-disable-next-line react-hooks/rules-of-hooks
const type = useSelect( ( select ) => select( 'core/editor' ).getCurrentPostType(), [] );
// eslint-disable-next-line react-hooks/rules-of-hooks
const [ meta, setMeta ] = useEntityProp( 'postType', type, 'meta' );
/**
* Given a mapping of attribute names (meta source attributes) to their
* associated meta key, returns a higher order component that overrides its
* `attributes` and `setAttributes` props to sync any changes with the edited
* post's meta keys.
*
* @param {WPMetaAttributeMapping} metaAttributes Meta attribute mapping.
*
* @return {WPHigherOrderComponent} Higher-order component.
*/
const createWithMetaAttributeSource = ( metaAttributes ) => createHigherOrderComponent(
( BlockEdit ) => ( { attributes, setAttributes, ...props } ) => {
const postType = useSelect( ( select ) => select( 'core/editor' ).getCurrentPostType(), [] );
const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' );

// eslint-disable-next-line react-hooks/rules-of-hooks
attributes = useMemo(
const mergedAttributes = useMemo(
() => ( {
..._attributes,
...Object.keys( attributeTypes ).reduce( ( acc, key ) => {
if ( attributeTypes[ key ].source === 'meta' ) {
acc[ key ] = meta[ attributeTypes[ key ].meta ];
}
return acc;
}, {} ),
...attributes,
...mapValues( metaAttributes, ( metaKey ) => meta[ metaKey ] ),
} ),
[ attributeTypes, meta, _attributes ]
[ attributes, meta ]
);

// eslint-disable-next-line react-hooks/rules-of-hooks
setAttributes = useCallback(
( ...args ) => {
Object.keys( args[ 0 ] ).forEach( ( key ) => {
if ( attributeTypes[ key ].source === 'meta' ) {
setMeta( { [ attributeTypes[ key ].meta ]: args[ 0 ][ key ] } );
return (
<BlockEdit
attributes={ mergedAttributes }
setAttributes={ ( nextAttributes ) => {
const nextMeta = mapKeys(
// Filter to intersection of keys between the updated
// attributes and those with an associated meta key.
pickBy( nextAttributes, ( value, key ) => metaAttributes[ key ] ),

// Rename the keys to the expected meta key name.
( value, attributeKey ) => metaAttributes[ attributeKey ],
);

if ( ! isEmpty( nextMeta ) ) {
setMeta( nextMeta );
}
} );
return _setAttributes( ...args );
},
[ attributeTypes, setMeta, _setAttributes ]
);
}

return [ attributes, setAttributes ];
}
const withMetaAttributeSource = createHigherOrderComponent(
( BlockListBlock ) => ( { attributes, setAttributes, name, ...props } ) => {
[ attributes, setAttributes ] = useMetaAttributeSource(
name,
attributes,
setAttributes
);
return (
<BlockListBlock
attributes={ attributes }
setAttributes={ setAttributes }
name={ name }
setAttributes( nextAttributes );
} }
{ ...props }
/>
);
},
'withMetaAttributeSource'
);

/**
* Filters a registered block's settings to enhance a block's `edit` component
* to upgrade meta-sourced attributes to use the post's meta entity property.
*
* @param {WPBlockSettings} settings Registered block settings.
*
* @return {WPBlockSettings} Filtered block settings.
*/
function shimAttributeSource( settings ) {
/** @type {WPMetaAttributeMapping} */
const metaAttributes = mapValues( pickBy( settings.attributes, { source: 'meta' } ), 'meta' );
if ( ! isEmpty( metaAttributes ) ) {
settings.edit = createWithMetaAttributeSource( metaAttributes )( settings.edit );
}

return settings;
}

addFilter(
'editor.BlockListBlock',
'core/editor/custom-sources-backwards-compatibility/with-meta-attribute-source',
withMetaAttributeSource
'blocks.registerBlockType',
'core/editor/custom-sources-backwards-compatibility/shim-attribute-source',
shimAttributeSource
);