From 3d2dc24ce5c213fac93e060652009bc289ca31cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Thu, 28 Nov 2024 08:19:18 +0100 Subject: [PATCH 1/6] Block Bindings: Move the place when the attributes get overriden --- .../src/components/block-edit/index.js | 5 +- .../with-block-bindings-attributes.js} | 81 +++---------------- .../block-list/use-block-props/index.js | 2 +- .../src/components/rich-text/index.js | 2 +- .../block-editor/src/hooks/block-bindings.js | 10 +-- packages/block-editor/src/hooks/index.js | 1 - .../block-editor/src/utils/block-bindings.js | 37 +++++++++ 7 files changed, 58 insertions(+), 80 deletions(-) rename packages/block-editor/src/{hooks/use-bindings-attributes.js => components/block-edit/with-block-bindings-attributes.js} (78%) diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js index 0c29c0e98b1bfd..09b92ad2e82462 100644 --- a/packages/block-editor/src/components/block-edit/index.js +++ b/packages/block-editor/src/components/block-edit/index.js @@ -8,6 +8,7 @@ import { hasBlockSupport } from '@wordpress/blocks'; * Internal dependencies */ import Edit from './edit'; +import { withBlockBindingSupport } from './with-block-bindings-attributes'; import { BlockEditContextProvider, useBlockEditContext, @@ -30,6 +31,8 @@ import { PrivateBlockContext } from '../block-list/private-block-context'; */ export { useBlockEditContext }; +const BlockEditWithBindings = withBlockBindingSupport( Edit ); + export default function BlockEdit( { mayDisplayControls, mayDisplayParentControls, @@ -88,7 +91,7 @@ export default function BlockEdit( { ] ) } > - + { originalBlockClientId && ( ( props ) => { const registry = useRegistry(); @@ -295,28 +259,3 @@ export const withBlockBindingSupport = createHigherOrderComponent( }, 'withBlockBindingSupport' ); - -/** - * Filters a registered block's settings to enhance a block's `edit` component - * to upgrade bound attributes. - * - * @param {WPBlockSettings} settings - Registered block settings. - * @param {string} name - Block name. - * @return {WPBlockSettings} Filtered block settings. - */ -function shimAttributeSource( settings, name ) { - if ( ! canBindBlock( name ) ) { - return settings; - } - - return { - ...settings, - edit: withBlockBindingSupport( settings.edit ), - }; -} - -addFilter( - 'blocks.registerBlockType', - 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', - shimAttributeSource -); diff --git a/packages/block-editor/src/components/block-list/use-block-props/index.js b/packages/block-editor/src/components/block-list/use-block-props/index.js index 25b9a21f0d2867..5942d393d31c16 100644 --- a/packages/block-editor/src/components/block-list/use-block-props/index.js +++ b/packages/block-editor/src/components/block-list/use-block-props/index.js @@ -29,7 +29,7 @@ import { useBlockRefProvider } from './use-block-refs'; import { useIntersectionObserver } from './use-intersection-observer'; import { useScrollIntoView } from './use-scroll-into-view'; import { useFlashEditableBlocks } from '../../use-flash-editable-blocks'; -import { canBindBlock } from '../../../hooks/use-bindings-attributes'; +import { canBindBlock } from '../../../utils/block-bindings'; /** * This hook is used to lightly mark an element as a block element. The element diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 8f179d08570ad1..4f21d46b229e59 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -39,7 +39,7 @@ import FormatEdit from './format-edit'; import { getAllowedFormats } from './utils'; import { Content, valueToHTMLString } from './content'; import { withDeprecations } from './with-deprecations'; -import { canBindBlock } from '../../hooks/use-bindings-attributes'; +import { canBindBlock } from '../../utils/block-bindings'; import BlockContext from '../block-context'; export const keyboardShortcutContext = createContext(); diff --git a/packages/block-editor/src/hooks/block-bindings.js b/packages/block-editor/src/hooks/block-bindings.js index 615804a311c0fb..33c06e4c9edae5 100644 --- a/packages/block-editor/src/hooks/block-bindings.js +++ b/packages/block-editor/src/hooks/block-bindings.js @@ -23,15 +23,15 @@ import { useViewportMatch } from '@wordpress/compose'; /** * Internal dependencies */ -import { - canBindAttribute, - getBindableAttributes, -} from '../hooks/use-bindings-attributes'; import { unlock } from '../lock-unlock'; import InspectorControls from '../components/inspector-controls'; import BlockContext from '../components/block-context'; import { useBlockEditContext } from '../components/block-edit'; -import { useBlockBindingsUtils } from '../utils/block-bindings'; +import { + canBindAttribute, + getBindableAttributes, + useBlockBindingsUtils, +} from '../utils/block-bindings'; import { store as blockEditorStore } from '../store'; const { Menu } = unlock( componentsPrivateApis ); diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index 66ff60b691b66f..7f9b29376ad1fb 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -32,7 +32,6 @@ import './metadata'; import blockHooks from './block-hooks'; import blockBindingsPanel from './block-bindings'; import './block-renaming'; -import './use-bindings-attributes'; import './grid-visualizer'; createBlockEditFilter( diff --git a/packages/block-editor/src/utils/block-bindings.js b/packages/block-editor/src/utils/block-bindings.js index dcf80d985473b2..82f0dff53531a4 100644 --- a/packages/block-editor/src/utils/block-bindings.js +++ b/packages/block-editor/src/utils/block-bindings.js @@ -13,6 +13,43 @@ function isObjectEmpty( object ) { return ! object || Object.keys( object ).length === 0; } +export const BLOCK_BINDINGS_ALLOWED_BLOCKS = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'id', 'url', 'title', 'alt' ], + 'core/button': [ 'url', 'text', 'linkTarget', 'rel' ], +}; + +/** + * Based on the given block name, + * check if it is possible to bind the block. + * + * @param {string} blockName - The block name. + * @return {boolean} Whether it is possible to bind the block to sources. + */ +export function canBindBlock( blockName ) { + return blockName in BLOCK_BINDINGS_ALLOWED_BLOCKS; +} + +/** + * Based on the given block name and attribute name, + * check if it is possible to bind the block attribute. + * + * @param {string} blockName - The block name. + * @param {string} attributeName - The attribute name. + * @return {boolean} Whether it is possible to bind the block attribute. + */ +export function canBindAttribute( blockName, attributeName ) { + return ( + canBindBlock( blockName ) && + BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ].includes( attributeName ) + ); +} + +export function getBindableAttributes( blockName ) { + return BLOCK_BINDINGS_ALLOWED_BLOCKS[ blockName ]; +} + /** * Contains utils to update the block `bindings` metadata. * From a786a9d3c1560d9d2c805e7390372d65387b30bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Thu, 28 Nov 2024 09:25:12 +0100 Subject: [PATCH 2/6] Fix failing unit tests --- .../components/block-edit/with-block-bindings-attributes.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/block-editor/src/components/block-edit/with-block-bindings-attributes.js b/packages/block-editor/src/components/block-edit/with-block-bindings-attributes.js index 602bb65a85d29d..98d3af68776efe 100644 --- a/packages/block-editor/src/components/block-edit/with-block-bindings-attributes.js +++ b/packages/block-editor/src/components/block-edit/with-block-bindings-attributes.js @@ -72,9 +72,9 @@ export const withBlockBindingSupport = createHigherOrderComponent( () => replacePatternOverrideDefaultBindings( name, - props.attributes.metadata?.bindings + props.attributes?.metadata?.bindings ), - [ props.attributes.metadata?.bindings, name ] + [ props.attributes?.metadata?.bindings, name ] ); // While this hook doesn't directly call any selectors, `useSelect` is @@ -160,7 +160,7 @@ export const withBlockBindingSupport = createHigherOrderComponent( const hasParentPattern = !! updatedContext[ 'pattern/overrides' ]; const hasPatternOverridesDefaultBinding = - props.attributes.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ] + props.attributes?.metadata?.bindings?.[ DEFAULT_ATTRIBUTE ] ?.source === 'core/pattern-overrides'; const _setAttributes = useCallback( From 848e2ad09cb32ad8874f0afa3f184a8ae0d43caf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Thu, 28 Nov 2024 09:47:28 +0100 Subject: [PATCH 3/6] Wrap Edit with bindings logic only when the block supports it --- .../block-editor/src/components/block-edit/index.js | 10 ++++++---- ...gs-attributes.js => with-block-bindings-support.js} | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) rename packages/block-editor/src/components/block-edit/{with-block-bindings-attributes.js => with-block-bindings-support.js} (99%) diff --git a/packages/block-editor/src/components/block-edit/index.js b/packages/block-editor/src/components/block-edit/index.js index 09b92ad2e82462..a16385d694ffc8 100644 --- a/packages/block-editor/src/components/block-edit/index.js +++ b/packages/block-editor/src/components/block-edit/index.js @@ -8,7 +8,7 @@ import { hasBlockSupport } from '@wordpress/blocks'; * Internal dependencies */ import Edit from './edit'; -import { withBlockBindingSupport } from './with-block-bindings-attributes'; +import { withBlockBindingsSupport } from './with-block-bindings-support'; import { BlockEditContextProvider, useBlockEditContext, @@ -20,6 +20,9 @@ import { } from './context'; import { MultipleUsageWarning } from './multiple-usage-warning'; import { PrivateBlockContext } from '../block-list/private-block-context'; +import { canBindBlock } from '../../utils/block-bindings'; + +const BlockEditWithBindings = withBlockBindingsSupport( Edit ); /** * The `useBlockEditContext` hook provides information about the block this hook is being used in. @@ -31,8 +34,6 @@ import { PrivateBlockContext } from '../block-list/private-block-context'; */ export { useBlockEditContext }; -const BlockEditWithBindings = withBlockBindingSupport( Edit ); - export default function BlockEdit( { mayDisplayControls, mayDisplayParentControls, @@ -55,6 +56,7 @@ export default function BlockEdit( { hasBlockSupport( name, 'layout', false ) || hasBlockSupport( name, '__experimentalLayout', false ); const { originalBlockClientId } = useContext( PrivateBlockContext ); + const EditComponent = canBindBlock( name ) ? BlockEditWithBindings : Edit; return ( - + { originalBlockClientId && ( ( props ) => { const registry = useRegistry(); const blockContext = useContext( BlockContext ); From 985eb9b17a0be0e26b91e27d6169a11c2d39babd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Fri, 29 Nov 2024 17:54:27 +0100 Subject: [PATCH 4/6] Extend the test plugin with `editor.BlockEdit` filter --- packages/e2e-tests/plugins/block-bindings.php | 6 ++- .../e2e-tests/plugins/block-bindings/index.js | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php index b86673c2c523d0..1fd6d8468c77db 100644 --- a/packages/e2e-tests/plugins/block-bindings.php +++ b/packages/e2e-tests/plugins/block-bindings.php @@ -41,7 +41,11 @@ function gutenberg_test_block_bindings_registration() { plugins_url( 'block-bindings/index.js', __FILE__ ), array( 'wp-blocks', - 'wp-private-apis', + 'wp-block-editor', + 'wp-components', + 'wp-compose', + 'wp-element', + 'wp-hooks', ), filemtime( plugin_dir_path( __FILE__ ) . 'block-bindings/index.js' ), true diff --git a/packages/e2e-tests/plugins/block-bindings/index.js b/packages/e2e-tests/plugins/block-bindings/index.js index 5c364257caed19..63c463e197fa8a 100644 --- a/packages/e2e-tests/plugins/block-bindings/index.js +++ b/packages/e2e-tests/plugins/block-bindings/index.js @@ -1,4 +1,9 @@ const { registerBlockBindingsSource } = wp.blocks; +const { InspectorControls } = wp.blockEditor; +const { PanelBody, TextControl } = wp.components; +const { createHigherOrderComponent } = wp.compose; +const { createElement: el, Fragment } = wp.element; +const { addFilter } = wp.hooks; const { fieldsList } = window.testingBindings || {}; const getValues = ( { bindings } ) => { @@ -46,3 +51,43 @@ registerBlockBindingsSource( { getValues, canUserEditValue: () => true, } ); + +const withBlockBindingsInspectorControl = createHigherOrderComponent( + ( BlockEdit ) => { + return ( props ) => { + if ( ! props.attributes?.metadata?.bindings?.content ) { + return el( BlockEdit, props ); + } + + return el( + Fragment, + {}, + el( BlockEdit, props ), + el( + InspectorControls, + {}, + el( + PanelBody, + { title: 'Bindings' }, + el( TextControl, { + __next40pxDefaultSize: true, + __nextHasNoMarginBottom: true, + label: 'Content', + value: props.attributes.content, + onChange: ( content ) => + props.setAttributes( { + content, + } ), + } ) + ) + ) + ); + }; + } +); + +addFilter( + 'editor.BlockEdit', + 'testing/bindings-inspector-control', + withBlockBindingsInspectorControl +); From d1e1f68b2fb7216dcb4db92bbb5382df715b0404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Fri, 29 Nov 2024 18:23:01 +0100 Subject: [PATCH 5/6] Add a new test covering the extensibility inside inspector controls --- .../various/block-bindings/post-meta.spec.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js index 32334bfc777f2a..318707e22f098d 100644 --- a/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js +++ b/test/e2e/specs/editor/various/block-bindings/post-meta.spec.js @@ -524,6 +524,47 @@ test.describe( 'Post Meta source', () => { previewPage.locator( '#connected-paragraph' ) ).toHaveText( 'new value' ); } ); + + test( 'should be possible to edit the value of the connected custom fields in the inspector control registered by the plugin', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + anchor: 'connected-paragraph', + content: 'fallback content', + metadata: { + bindings: { + content: { + source: 'core/post-meta', + args: { + key: 'movie_field', + }, + }, + }, + }, + }, + } ); + const contentInput = page.getByRole( 'textbox', { + name: 'Content', + } ); + await expect( contentInput ).toHaveValue( + 'Movie field default value' + ); + await contentInput.fill( 'new value' ); + // Check that the paragraph content attribute didn't change. + const [ paragraphBlockObject ] = await editor.getBlocks(); + expect( paragraphBlockObject.attributes.content ).toBe( + 'fallback content' + ); + // Check the value of the custom field is being updated by visiting the frontend. + const previewPage = await editor.openPreviewPage(); + await expect( + previewPage.locator( '#connected-paragraph' ) + ).toHaveText( 'new value' ); + } ); + test( 'should be possible to connect movie fields through the attributes panel', async ( { editor, page, From 7c4d983ca8a2c3bcfb563a7aabb3b920a66e0fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Zi=C3=B3=C5=82kowski?= Date: Fri, 29 Nov 2024 21:20:46 +0100 Subject: [PATCH 6/6] Fix the issue with missing context established by sources --- .../block-editor/src/components/block-edit/edit.js | 12 ++++++++++-- .../src/components/block-edit/index.js | 7 +------ .../block-edit/with-block-bindings-support.js | 14 ++++++-------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/block-edit/edit.js b/packages/block-editor/src/components/block-edit/edit.js index 83d0e3f406f829..6b1ddd86f4c76e 100644 --- a/packages/block-editor/src/components/block-edit/edit.js +++ b/packages/block-editor/src/components/block-edit/edit.js @@ -18,6 +18,8 @@ import { useContext, useMemo } from '@wordpress/element'; * Internal dependencies */ import BlockContext from '../block-context'; +import { withBlockBindingsSupport } from './with-block-bindings-support'; +import { canBindBlock } from '../../utils/block-bindings'; /** * Default value used for blocks which do not define their own context needs, @@ -47,6 +49,8 @@ const Edit = ( props ) => { const EditWithFilters = withFilters( 'editor.BlockEdit' )( Edit ); +const EditWithFiltersAndBindings = withBlockBindingsSupport( EditWithFilters ); + const EditWithGeneratedProps = ( props ) => { const { attributes = {}, name } = props; const blockType = getBlockType( name ); @@ -67,8 +71,12 @@ const EditWithGeneratedProps = ( props ) => { return null; } + const EditComponent = canBindBlock( name ) + ? EditWithFiltersAndBindings + : EditWithFilters; + if ( blockType.apiVersion > 1 ) { - return ; + return ; } // Generate a class name for the block's editable form. @@ -82,7 +90,7 @@ const EditWithGeneratedProps = ( props ) => { ); return ( - - + { originalBlockClientId && ( - - + ); }, 'withBlockBindingSupport'