diff --git a/gutenberg.php b/gutenberg.php index 1d5997eb2d6c1..095076797a69a 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -80,12 +80,12 @@ function gutenberg_pre_init() { * @param string $block_instance The block instance. */ function render_custom_sources( $block_content, $block, $block_instance ) { - $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); + $meta_custom_source = require __DIR__ . '/lib/custom-sources/meta.php'; + $block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] ); if ( null === $block_type ) { return $block_content; } - $custom_sources = _wp_array_get( $block_type->supports, 'customSources', false ); if ( ! $custom_sources ) { // TODO: for some reason the "customSources" support is not being registered as it should. @@ -95,31 +95,10 @@ function render_custom_sources( $block_content, $block, $block_instance ) { $attribute_sources = _wp_array_get( $block['attrs'], array( 'source' ), array() ); foreach ( $attribute_sources as $attribute_name => $attribute_source ) { $attribute_config = _wp_array_get( $block_type->attributes, array( $attribute_name ), false ); - if ( ! $attribute_config || ! $attribute_source || 'meta' !== $attribute_source['type'] ) { - continue; - } - $meta_field = $attribute_source['name']; - $meta_value = get_post_meta( $block_instance->context['postId'], $meta_field, true ); - $p = new WP_HTML_Tag_Processor( $block_content ); - $found = $p->next_tag( - array( - // TODO: build the query from CSS selector. - 'tag_name' => $attribute_config['selector'], - ) - ); - if ( ! $found ) { + if ( ! $attribute_config || ! $attribute_source || $meta_custom_source['name'] !== $attribute_source['type'] ) { continue; } - $tag_name = $p->get_tag(); - $markup = "<$tag_name>$meta_value"; - $p2 = new WP_HTML_Tag_Processor( $markup ); - $p2->next_tag(); - $names = $p->get_attribute_names_with_prefix( '' ); - foreach ( $names as $name ) { - $p2->set_attribute( $name, $p->get_attribute( $name ) ); - } - - $block_content = $p2 . ''; + $block_content = $meta_custom_source['apply_source']( $block_type, $block_content, $block_instance, $attribute_source, $attribute_config ); } return $block_content; diff --git a/lib/custom-sources/meta.php b/lib/custom-sources/meta.php new file mode 100644 index 0000000000000..547059bba0aed --- /dev/null +++ b/lib/custom-sources/meta.php @@ -0,0 +1,34 @@ + 'meta', + 'apply_source' => function ( $block_type, $block_content, $block_instance, $attribute_source, $attribute_config ) { + $meta_field = $attribute_source['name']; + $meta_value = get_post_meta( $block_instance->context['postId'], $meta_field, true ); + $p = new WP_HTML_Tag_Processor( $block_content ); + $found = $p->next_tag( + array( + // TODO: build the query from CSS selector. + 'tag_name' => $attribute_config['selector'], + ) + ); + if ( ! $found ) { + return $block_content; + } + $tag_name = $p->get_tag(); + $markup = "<$tag_name>$meta_value"; + $p2 = new WP_HTML_Tag_Processor( $markup ); + $p2->next_tag(); + $names = $p->get_attribute_names_with_prefix( '' ); + foreach ( $names as $name ) { + $p2->set_attribute( $name, $p->get_attribute( $name ) ); + } + + return $p2 . ''; + }, +); diff --git a/packages/editor/src/attributes-custom-sources/meta.js b/packages/editor/src/attributes-custom-sources/meta.js new file mode 100644 index 0000000000000..193198f6263e8 --- /dev/null +++ b/packages/editor/src/attributes-custom-sources/meta.js @@ -0,0 +1,110 @@ +/** + * WordPress dependencies + */ +import { getBlockType } from '@wordpress/blocks'; +import { useEntityProp } from '@wordpress/core-data'; +import { useMemo, useCallback } from '@wordpress/element'; + +export default { + name: 'meta', + + useSource( { + name, + context: { postType, postId }, + attributes, + setAttributes, + } ) { + const [ meta, setMeta ] = useEntityProp( + 'postType', + postType, + 'meta', + postId + ); + + const blockType = getBlockType( name ); + + const attributesWithSourcedAttributes = useMemo( () => { + if ( ! blockType.supports?.customSources ) { + return attributes; + } + return { + ...attributes, + ...Object.fromEntries( + Object.keys( blockType.supports.customSources ).map( + ( attributeName ) => { + if ( + attributes.source?.[ attributeName ]?.type === + 'meta' + ) { + return [ + attributeName, + meta?.[ + attributes.source?.[ attributeName ] + ?.name + ], + ]; + } + return [ + attributeName, + attributes[ attributeName ], + ]; + } + ) + ), + }; + }, [ blockType.supports?.customSources, attributes, meta ] ); + + const updatedSetAttributes = useCallback( + ( nextAttributes ) => { + const nextMeta = Object.fromEntries( + Object.entries( nextAttributes ?? {} ) + .filter( + // Filter to intersection of keys between the updated + // attributes and those with an associated meta key. + ( [ key ] ) => + blockType.supports?.customSources && + key in blockType.supports?.customSources && + attributes.source?.[ key ]?.type === 'meta' + ) + .map( ( [ attributeKey, value ] ) => [ + // Rename the keys to the expected meta key name. + attributes.source?.[ attributeKey ]?.name, + value, + ] ) + ); + + const updatedAttributes = Object.entries( nextMeta ).length + ? Object.fromEntries( + Object.entries( nextAttributes ).filter( + ( [ key ] ) => + ! ( + blockType.supports?.customSources && + key in + blockType.supports?.customSources && + attributes.source?.[ key ]?.type === + 'meta' + ) + ) + ) + : nextAttributes; + + if ( Object.entries( nextMeta ).length ) { + setMeta( nextMeta ); + } + + setAttributes( updatedAttributes ); + }, + [ + setAttributes, + attributes.source, + blockType.supports?.customSources, + setMeta, + ] + ); + + return { + setAttributes: updatedSetAttributes, + attributes: attributesWithSourcedAttributes, + }; + }, +}; diff --git a/packages/editor/src/hooks/custom-sources-v2.js b/packages/editor/src/hooks/custom-sources-v2.js deleted file mode 100644 index 465ae5f647721..0000000000000 --- a/packages/editor/src/hooks/custom-sources-v2.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * WordPress dependencies - */ -import { getBlockType, hasBlockSupport } from '@wordpress/blocks'; -import { useRegistry } from '@wordpress/data'; -import { useEntityProp } from '@wordpress/core-data'; -import { useMemo } from '@wordpress/element'; -import { createHigherOrderComponent } from '@wordpress/compose'; -import { addFilter } from '@wordpress/hooks'; - -/** - * Filters registered block settings, extending attributes to include `style` attribute. - * - * @param {Object} settings Original block settings. - * - * @return {Object} Filtered block settings. - */ -function addAttribute( settings ) { - if ( ! hasBlockSupport( settings, 'customSources' ) ) { - return settings; - } - - // Allow blocks to specify their own attribute definition with default values if needed. - if ( ! settings.attributes.source ) { - Object.assign( settings.attributes, { - source: { - type: 'object', - }, - } ); - } - - return settings; -} - -/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ -/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ - -/** - * 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. - * - * @return {WPHigherOrderComponent} Higher-order component. - */ -const createEditFunctionWithCustomSources = () => - createHigherOrderComponent( - ( BlockEdit ) => - ( { - name, - attributes, - setAttributes, - context: { postType, postId }, - ...props - } ) => { - const registry = useRegistry(); - const [ meta, setMeta ] = useEntityProp( - 'postType', - postType, - 'meta', - postId - ); - - const blockType = getBlockType( name ); - - const mergedAttributes = useMemo( () => { - if ( ! blockType.supports?.customSources ) { - return attributes; - } - return { - ...attributes, - ...Object.fromEntries( - Object.keys( blockType.supports.customSources ).map( - ( attributeName ) => { - if ( - attributes.source?.[ attributeName ] - ?.type === 'meta' - ) { - return [ - attributeName, - meta?.[ - attributes.source?.[ - attributeName - ]?.name - ], - ]; - } - return [ - attributeName, - attributes[ attributeName ], - ]; - } - ) - ), - }; - }, [ blockType.supports?.customSources, attributes, meta ] ); - - return ( - { - const nextMeta = Object.fromEntries( - Object.entries( nextAttributes ?? {} ) - .filter( - // Filter to intersection of keys between the updated - // attributes and those with an associated meta key. - ( [ key ] ) => - blockType.supports?.customSources && - key in - blockType.supports - ?.customSources && - attributes.source?.[ key ]?.type === - 'meta' - ) - .map( ( [ attributeKey, value ] ) => [ - // Rename the keys to the expected meta key name. - attributes.source?.[ attributeKey ] - ?.name, - value, - ] ) - ); - - const updatedAttributes = Object.entries( nextMeta ) - .length - ? Object.fromEntries( - Object.entries( nextAttributes ).filter( - ( [ key ] ) => - ! ( - blockType.supports - ?.customSources && - key in - blockType.supports - ?.customSources && - attributes.source?.[ key ] - ?.type === 'meta' - ) - ) - ) - : nextAttributes; - - registry.batch( () => { - if ( Object.entries( nextMeta ).length ) { - setMeta( nextMeta ); - } - - setAttributes( updatedAttributes ); - } ); - } } - { ...props } - /> - ); - }, - 'withCustomSources' - ); - -/** - * 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 ) { - settings.edit = createEditFunctionWithCustomSources()( settings.edit ); - - return settings; -} - -addFilter( - 'blocks.registerBlockType', - 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', - shimAttributeSource -); - -addFilter( - 'blocks.registerBlockType', - 'core/custom-sources-v2/addAttribute', - addAttribute -); diff --git a/packages/editor/src/hooks/custom-sources.js b/packages/editor/src/hooks/custom-sources.js new file mode 100644 index 0000000000000..26b775bc6ad50 --- /dev/null +++ b/packages/editor/src/hooks/custom-sources.js @@ -0,0 +1,104 @@ +/** + * WordPress dependencies + */ +import { hasBlockSupport } from '@wordpress/blocks'; +import { useRegistry } from '@wordpress/data'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { addFilter } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import meta from '../attributes-custom-sources/meta'; + +/** + * Filters registered block settings, extending attributes to include `style` attribute. + * + * @param {Object} settings Original block settings. + * + * @return {Object} Filtered block settings. + */ +function addAttribute( settings ) { + if ( ! hasBlockSupport( settings, 'customSources' ) ) { + return settings; + } + + // Allow blocks to specify their own attribute definition with default values if needed. + if ( ! settings.attributes.source ) { + Object.assign( settings.attributes, { + source: { + type: 'object', + }, + } ); + } + + return settings; +} + +/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ +/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ + +/** + * 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. + * + * @return {WPHigherOrderComponent} Higher-order component. + */ +const createEditFunctionWithCustomSources = () => + createHigherOrderComponent( + ( BlockEdit ) => + ( { name, attributes, setAttributes, context, ...props } ) => { + const registry = useRegistry(); + + const { + attributes: updatedAttributes, + setAttributes: updatedSetAttributes, + } = meta.useSource( { + name, + attributes, + setAttributes, + context, + } ); + + return ( + + registry.batch( () => + updatedSetAttributes( newAttributes ) + ) + } + { ...props } + /> + ); + }, + 'withCustomSources' + ); + +/** + * 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 ) { + settings.edit = createEditFunctionWithCustomSources()( settings.edit ); + + return settings; +} + +addFilter( + 'blocks.registerBlockType', + 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', + shimAttributeSource +); + +addFilter( + 'blocks.registerBlockType', + 'core/custom-sources-v2/addAttribute', + addAttribute +); diff --git a/packages/editor/src/hooks/index.js b/packages/editor/src/hooks/index.js index 3b34b42936b80..f03c798d516f0 100644 --- a/packages/editor/src/hooks/index.js +++ b/packages/editor/src/hooks/index.js @@ -2,5 +2,5 @@ * Internal dependencies */ import './custom-sources-backwards-compatibility'; -import './custom-sources-v2'; +import './custom-sources'; import './default-autocompleters';