From 44d86f064e6be5a039dc8346685c022df7536366 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:13:54 +0200 Subject: [PATCH 01/25] Move bindings registration to editor provider --- .../src/{ => components/provider}/bindings/api.js | 0 .../provider}/bindings/pattern-overrides.js | 0 .../provider}/bindings/post-meta.js | 2 +- packages/editor/src/components/provider/index.js | 14 +++++++++++++- 4 files changed, 14 insertions(+), 2 deletions(-) rename packages/editor/src/{ => components/provider}/bindings/api.js (100%) rename packages/editor/src/{ => components/provider}/bindings/pattern-overrides.js (100%) rename packages/editor/src/{ => components/provider}/bindings/post-meta.js (97%) diff --git a/packages/editor/src/bindings/api.js b/packages/editor/src/components/provider/bindings/api.js similarity index 100% rename from packages/editor/src/bindings/api.js rename to packages/editor/src/components/provider/bindings/api.js diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/components/provider/bindings/pattern-overrides.js similarity index 100% rename from packages/editor/src/bindings/pattern-overrides.js rename to packages/editor/src/components/provider/bindings/pattern-overrides.js diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/components/provider/bindings/post-meta.js similarity index 97% rename from packages/editor/src/bindings/post-meta.js rename to packages/editor/src/components/provider/bindings/post-meta.js index f8161dd47b5c4..8d16bcf30d7fe 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/components/provider/bindings/post-meta.js @@ -7,7 +7,7 @@ import { _x } from '@wordpress/i18n'; /** * Internal dependencies */ -import { store as editorStore } from '../store'; +import { store as editorStore } from '../../../store'; export default { name: 'core/post-meta', diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index aaf25621d3324..88bdc85b55cc4 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -12,7 +12,7 @@ import { } from '@wordpress/block-editor'; import { store as noticesStore } from '@wordpress/notices'; import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns'; -import { createBlock } from '@wordpress/blocks'; +import { createBlock, store as blocksStore } from '@wordpress/blocks'; /** * Internal dependencies @@ -34,6 +34,8 @@ import EditorKeyboardShortcuts from '../global-keyboard-shortcuts'; import PatternRenameModal from '../pattern-rename-modal'; import PatternDuplicateModal from '../pattern-duplicate-modal'; import TemplatePartMenuItems from '../template-part-menu-items'; +import patternOverrides from './bindings/pattern-overrides'; +import postMeta from './bindings/post-meta'; const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); @@ -223,6 +225,9 @@ export const ExperimentalEditorProvider = withRegistryProvider( setRenderingMode, } = unlock( useDispatch( editorStore ) ); const { createWarningNotice } = useDispatch( noticesStore ); + const { registerBlockBindingsSource } = unlock( + useDispatch( blocksStore ) + ); // Ideally this should be synced on each change and not just something you do once. useLayoutEffect( () => { @@ -271,6 +276,13 @@ export const ExperimentalEditorProvider = withRegistryProvider( setRenderingMode( settings.defaultRenderingMode ?? 'post-only' ); }, [ settings.defaultRenderingMode, setRenderingMode ] ); + // Register block bindings sources. + useEffect( () => { + // Initialize core sources. + registerBlockBindingsSource( postMeta ); + registerBlockBindingsSource( patternOverrides ); + }, [ postMeta, patternOverrides, registerBlockBindingsSource ] ); + useHideBlocksFromInserter( post.type, mode ); // Register the editor commands. From 0ca3e00fd0077273db21e94d0255603e07fbf43d Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:14:45 +0200 Subject: [PATCH 02/25] Remove sources from editor provider --- .../provider/bindings/pattern-overrides.js | 105 ------------------ .../components/provider/bindings/post-meta.js | 80 ------------- .../editor/src/components/provider/index.js | 14 +-- 3 files changed, 1 insertion(+), 198 deletions(-) delete mode 100644 packages/editor/src/components/provider/bindings/pattern-overrides.js delete mode 100644 packages/editor/src/components/provider/bindings/post-meta.js diff --git a/packages/editor/src/components/provider/bindings/pattern-overrides.js b/packages/editor/src/components/provider/bindings/pattern-overrides.js deleted file mode 100644 index b299211900095..0000000000000 --- a/packages/editor/src/components/provider/bindings/pattern-overrides.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * WordPress dependencies - */ -import { _x } from '@wordpress/i18n'; -import { store as blockEditorStore } from '@wordpress/block-editor'; - -const CONTENT = 'content'; - -export default { - name: 'core/pattern-overrides', - label: _x( 'Pattern Overrides', 'block bindings source' ), - getValues( { registry, clientId, context, bindings } ) { - const patternOverridesContent = context[ 'pattern/overrides' ]; - const { getBlockAttributes } = registry.select( blockEditorStore ); - const currentBlockAttributes = getBlockAttributes( clientId ); - - const overridesValues = {}; - for ( const attributeName of Object.keys( bindings ) ) { - const overridableValue = - patternOverridesContent?.[ - currentBlockAttributes?.metadata?.name - ]?.[ attributeName ]; - - // If it has not been overriden, return the original value. - // Check undefined because empty string is a valid value. - if ( overridableValue === undefined ) { - overridesValues[ attributeName ] = - currentBlockAttributes[ attributeName ]; - continue; - } else { - overridesValues[ attributeName ] = - overridableValue === '' ? undefined : overridableValue; - } - } - return overridesValues; - }, - setValues( { registry, clientId, bindings } ) { - const { getBlockAttributes, getBlockParentsByBlockName, getBlocks } = - registry.select( blockEditorStore ); - const currentBlockAttributes = getBlockAttributes( clientId ); - const blockName = currentBlockAttributes?.metadata?.name; - if ( ! blockName ) { - return; - } - - const [ patternClientId ] = getBlockParentsByBlockName( - clientId, - 'core/block', - true - ); - - // Extract the updated attributes from the source bindings. - const attributes = Object.entries( bindings ).reduce( - ( attrs, [ key, { newValue } ] ) => { - attrs[ key ] = newValue; - return attrs; - }, - {} - ); - - // If there is no pattern client ID, sync blocks with the same name and same attributes. - if ( ! patternClientId ) { - const syncBlocksWithSameName = ( blocks ) => { - for ( const block of blocks ) { - if ( block.attributes?.metadata?.name === blockName ) { - registry - .dispatch( blockEditorStore ) - .updateBlockAttributes( - block.clientId, - attributes - ); - } - syncBlocksWithSameName( block.innerBlocks ); - } - }; - - syncBlocksWithSameName( getBlocks() ); - return; - } - const currentBindingValue = - getBlockAttributes( patternClientId )?.[ CONTENT ]; - registry - .dispatch( blockEditorStore ) - .updateBlockAttributes( patternClientId, { - [ CONTENT ]: { - ...currentBindingValue, - [ blockName ]: { - ...currentBindingValue?.[ blockName ], - ...Object.entries( attributes ).reduce( - ( acc, [ key, value ] ) => { - // TODO: We need a way to represent `undefined` in the serialized overrides. - // Also see: https://github.com/WordPress/gutenberg/pull/57249#discussion_r1452987871 - // We use an empty string to represent undefined for now until - // we support a richer format for overrides and the block bindings API. - acc[ key ] = value === undefined ? '' : value; - return acc; - }, - {} - ), - }, - }, - } ); - }, - canUserEditValue: () => true, -}; diff --git a/packages/editor/src/components/provider/bindings/post-meta.js b/packages/editor/src/components/provider/bindings/post-meta.js deleted file mode 100644 index 8d16bcf30d7fe..0000000000000 --- a/packages/editor/src/components/provider/bindings/post-meta.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * WordPress dependencies - */ -import { store as coreDataStore } from '@wordpress/core-data'; -import { _x } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { store as editorStore } from '../../../store'; - -export default { - name: 'core/post-meta', - label: _x( 'Post Meta', 'block bindings source' ), - getPlaceholder( { args } ) { - return args.key; - }, - getValues( { registry, context, bindings } ) { - const meta = registry - .select( coreDataStore ) - .getEditedEntityRecord( - 'postType', - context?.postType, - context?.postId - )?.meta; - const newValues = {}; - for ( const [ attributeName, source ] of Object.entries( bindings ) ) { - newValues[ attributeName ] = meta?.[ source.args.key ]; - } - return newValues; - }, - setValues( { registry, context, bindings } ) { - const newMeta = {}; - Object.values( bindings ).forEach( ( { args, newValue } ) => { - newMeta[ args.key ] = newValue; - } ); - registry - .dispatch( coreDataStore ) - .editEntityRecord( 'postType', context?.postType, context?.postId, { - meta: newMeta, - } ); - }, - canUserEditValue( { select, context, args } ) { - // Lock editing in query loop. - if ( context?.query || context?.queryId ) { - return false; - } - - const postType = - context?.postType || select( editorStore ).getCurrentPostType(); - - // Check that editing is happening in the post editor and not a template. - if ( postType === 'wp_template' ) { - return false; - } - - // Check that the custom field is not protected and available in the REST API. - const isFieldExposed = !! select( coreDataStore ).getEntityRecord( - 'postType', - postType, - context?.postId - )?.meta?.[ args.key ]; - - if ( ! isFieldExposed ) { - return false; - } - - // Check that the user has the capability to edit post meta. - const canUserEdit = select( coreDataStore ).canUser( 'update', { - kind: 'postType', - name: context?.postType, - id: context?.postId, - } ); - if ( ! canUserEdit ) { - return false; - } - - return true; - }, -}; diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index 88bdc85b55cc4..aaf25621d3324 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -12,7 +12,7 @@ import { } from '@wordpress/block-editor'; import { store as noticesStore } from '@wordpress/notices'; import { privateApis as editPatternsPrivateApis } from '@wordpress/patterns'; -import { createBlock, store as blocksStore } from '@wordpress/blocks'; +import { createBlock } from '@wordpress/blocks'; /** * Internal dependencies @@ -34,8 +34,6 @@ import EditorKeyboardShortcuts from '../global-keyboard-shortcuts'; import PatternRenameModal from '../pattern-rename-modal'; import PatternDuplicateModal from '../pattern-duplicate-modal'; import TemplatePartMenuItems from '../template-part-menu-items'; -import patternOverrides from './bindings/pattern-overrides'; -import postMeta from './bindings/post-meta'; const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); const { PatternsMenuItems } = unlock( editPatternsPrivateApis ); @@ -225,9 +223,6 @@ export const ExperimentalEditorProvider = withRegistryProvider( setRenderingMode, } = unlock( useDispatch( editorStore ) ); const { createWarningNotice } = useDispatch( noticesStore ); - const { registerBlockBindingsSource } = unlock( - useDispatch( blocksStore ) - ); // Ideally this should be synced on each change and not just something you do once. useLayoutEffect( () => { @@ -276,13 +271,6 @@ export const ExperimentalEditorProvider = withRegistryProvider( setRenderingMode( settings.defaultRenderingMode ?? 'post-only' ); }, [ settings.defaultRenderingMode, setRenderingMode ] ); - // Register block bindings sources. - useEffect( () => { - // Initialize core sources. - registerBlockBindingsSource( postMeta ); - registerBlockBindingsSource( patternOverrides ); - }, [ postMeta, patternOverrides, registerBlockBindingsSource ] ); - useHideBlocksFromInserter( post.type, mode ); // Register the editor commands. From 758ede22a5624739eb0ff3c34b75536e4aae12c0 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:14:45 +0200 Subject: [PATCH 03/25] Create `registerCoreBlockBindingsSources` function --- packages/editor/src/bindings/api.js | 27 ++++++ .../editor/src/bindings/pattern-overrides.js | 92 +++++++++++++++++++ packages/editor/src/bindings/post-meta.js | 68 ++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 packages/editor/src/bindings/api.js create mode 100644 packages/editor/src/bindings/pattern-overrides.js create mode 100644 packages/editor/src/bindings/post-meta.js diff --git a/packages/editor/src/bindings/api.js b/packages/editor/src/bindings/api.js new file mode 100644 index 0000000000000..0037f3334215b --- /dev/null +++ b/packages/editor/src/bindings/api.js @@ -0,0 +1,27 @@ +/** + * WordPress dependencies + */ +import { privateApis as blocksPrivateApis } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import patternOverrides from './pattern-overrides'; +import postMeta from './post-meta'; +import { unlock } from '../lock-unlock'; + +/** + * Function to register core block bindings sources provided by the editor. + * + * @example + * ```js + * import { registerCoreBlockBindingsSources } from '@wordpress/editor'; + * + * registerCoreBlockBindingsSources(); + * ``` + */ +export function registerCoreBlockBindingsSources() { + const { registerBlockBindingsSource } = unlock( blocksPrivateApis ); + registerBlockBindingsSource( patternOverrides ); + registerBlockBindingsSource( postMeta ); +} diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js new file mode 100644 index 0000000000000..54ca77650a5fe --- /dev/null +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -0,0 +1,92 @@ +/** + * WordPress dependencies + */ +import { _x } from '@wordpress/i18n'; +import { store as blockEditorStore } from '@wordpress/block-editor'; + +const CONTENT = 'content'; + +export default { + name: 'core/pattern-overrides', + label: _x( 'Pattern Overrides', 'block bindings source' ), + getValue( { registry, clientId, context, attributeName } ) { + const patternOverridesContent = context[ 'pattern/overrides' ]; + const { getBlockAttributes } = registry.select( blockEditorStore ); + const currentBlockAttributes = getBlockAttributes( clientId ); + + if ( ! patternOverridesContent ) { + return currentBlockAttributes[ attributeName ]; + } + + const overridableValue = + patternOverridesContent?.[ + currentBlockAttributes?.metadata?.name + ]?.[ attributeName ]; + + // If there is no pattern client ID, or it is not overwritten, return the default value. + if ( overridableValue === undefined ) { + return currentBlockAttributes[ attributeName ]; + } + + return overridableValue === '' ? undefined : overridableValue; + }, + setValues( { registry, clientId, attributes } ) { + const { getBlockAttributes, getBlockParentsByBlockName, getBlocks } = + registry.select( blockEditorStore ); + const currentBlockAttributes = getBlockAttributes( clientId ); + const blockName = currentBlockAttributes?.metadata?.name; + if ( ! blockName ) { + return; + } + + const [ patternClientId ] = getBlockParentsByBlockName( + clientId, + 'core/block', + true + ); + + // If there is no pattern client ID, sync blocks with the same name and same attributes. + if ( ! patternClientId ) { + const syncBlocksWithSameName = ( blocks ) => { + for ( const block of blocks ) { + if ( block.attributes?.metadata?.name === blockName ) { + registry + .dispatch( blockEditorStore ) + .updateBlockAttributes( + block.clientId, + attributes + ); + } + syncBlocksWithSameName( block.innerBlocks ); + } + }; + + syncBlocksWithSameName( getBlocks() ); + return; + } + const currentBindingValue = + getBlockAttributes( patternClientId )?.[ CONTENT ]; + registry + .dispatch( blockEditorStore ) + .updateBlockAttributes( patternClientId, { + [ CONTENT ]: { + ...currentBindingValue, + [ blockName ]: { + ...currentBindingValue?.[ blockName ], + ...Object.entries( attributes ).reduce( + ( acc, [ key, value ] ) => { + // TODO: We need a way to represent `undefined` in the serialized overrides. + // Also see: https://github.com/WordPress/gutenberg/pull/57249#discussion_r1452987871 + // We use an empty string to represent undefined for now until + // we support a richer format for overrides and the block bindings API. + acc[ key ] = value === undefined ? '' : value; + return acc; + }, + {} + ), + }, + }, + } ); + }, + canUserEditValue: () => true, +}; diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js new file mode 100644 index 0000000000000..aec890c5ceff8 --- /dev/null +++ b/packages/editor/src/bindings/post-meta.js @@ -0,0 +1,68 @@ +/** + * WordPress dependencies + */ +import { store as coreDataStore } from '@wordpress/core-data'; +import { _x } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { store as editorStore } from '../store'; + +export default { + name: 'core/post-meta', + label: _x( 'Post Meta', 'block bindings source' ), + getPlaceholder( { args } ) { + return args.key; + }, + getValue( { registry, context, args } ) { + return registry + .select( coreDataStore ) + .getEditedEntityRecord( + 'postType', + context?.postType, + context?.postId + ).meta?.[ args.key ]; + }, + setValue( { registry, context, args, value } ) { + registry + .dispatch( coreDataStore ) + .editEntityRecord( 'postType', context?.postType, context?.postId, { + meta: { + [ args.key ]: value, + }, + } ); + }, + canUserEditValue( { select, context, args } ) { + const postType = + context?.postType || select( editorStore ).getCurrentPostType(); + + // Check that editing is happening in the post editor and not a template. + if ( postType === 'wp_template' ) { + return false; + } + + // Check that the custom field is not protected and available in the REST API. + const isFieldExposed = !! select( coreDataStore ).getEntityRecord( + 'postType', + postType, + context?.postId + )?.meta?.[ args.key ]; + + if ( ! isFieldExposed ) { + return false; + } + + // Check that the user has the capability to edit post meta. + const canUserEdit = select( coreDataStore ).canUserEditEntityRecord( + 'postType', + context?.postType, + context?.postId + ); + if ( ! canUserEdit ) { + return false; + } + + return true; + }, +}; From b57536956fd39fa9ae30bec0ccb9ada5e79dc1d8 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:14:45 +0200 Subject: [PATCH 04/25] Add `updateBlockBindingsSource` action --- packages/blocks/src/store/private-actions.js | 19 +++++++++++++++++++ packages/blocks/src/store/reducer.js | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js index 55cdb2128895f..178089e7935db 100644 --- a/packages/blocks/src/store/private-actions.js +++ b/packages/blocks/src/store/private-actions.js @@ -69,3 +69,22 @@ export function removeBlockBindingsSource( name ) { name, }; } + +/** + * Updates an existing block bindings source. + * They could be initialized from the server. + * + * @param {string} source Name of the source to update. + */ +export function updateBlockBindingsSource( source ) { + return { + type: 'UPDATE_BLOCK_BINDINGS_SOURCE', + name: source.name, + label: source.label, + getValue: source.getValue, + setValue: source.setValue, + setValues: source.setValues, + getPlaceholder: source.getPlaceholder, + canUserEditValue: source.canUserEditValue, + }; +} diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index fc386e7ea9f55..a69960da937d8 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -387,6 +387,24 @@ export function blockBindingsSources( state = {}, action ) { case 'REMOVE_BLOCK_BINDINGS_SOURCE': return omit( state, action.name ); } + if ( action.type === 'UPDATE_BLOCK_BINDINGS_SOURCE' ) { + // Filter the name property and the undefined values. + const updatedProperties = Object.fromEntries( + Object.entries( action ).filter( + ( [ key, value ] ) => value !== undefined && key !== 'name' + ) + ); + + return { + ...state, + [ action.name ]: { + // Keep the existing properties. + ...state[ action.name ], + // Update with the new properties. + ...updatedProperties, + }, + }; + } return state; } From 0e29fcde92e272565fa011c53d66786e3772bc02 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:14:45 +0200 Subject: [PATCH 05/25] Bootstrap sources defined in the server --- packages/edit-post/src/index.js | 17 ++++++++++++++++- packages/edit-site/src/index.js | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 2cbf1958719c6..c2276d425628e 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -1,7 +1,10 @@ /** * WordPress dependencies */ -import { store as blocksStore } from '@wordpress/blocks'; +import { + store as blocksStore, + privateApis as blocksPrivateApis, +} from '@wordpress/blocks'; import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, @@ -87,6 +90,18 @@ export function initializeEditor( } registerCoreBlocks(); + // Bootstrap block bindings sources from the server. + if ( settings?.blockBindings ) { + const { registerBlockBindingsSource } = unlock( blocksPrivateApis ); + for ( const [ name, args ] of Object.entries( + settings.blockBindings + ) ) { + registerBlockBindingsSource( { + name, + ...args, + } ); + } + } registerCoreBlockBindingsSources(); registerLegacyWidgetBlock( { inserter: false } ); registerWidgetGroupBlock( { inserter: false } ); diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 02d974e037c04..4cffb45144a21 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -1,7 +1,10 @@ /** * WordPress dependencies */ -import { store as blocksStore } from '@wordpress/blocks'; +import { + store as blocksStore, + privateApis as blocksPrivateApis, +} from '@wordpress/blocks'; import { registerCoreBlocks, __experimentalGetCoreBlocks, @@ -46,6 +49,18 @@ export function initializeEditor( id, settings ) { ( { name } ) => name !== 'core/freeform' ); registerCoreBlocks( coreBlocks ); + // Bootstrap block bindings sources from the server. + if ( settings?.blockBindings ) { + const { registerBlockBindingsSource } = unlock( blocksPrivateApis ); + for ( const [ name, args ] of Object.entries( + settings.blockBindings + ) ) { + registerBlockBindingsSource( { + name, + ...args, + } ); + } + } registerCoreBlockBindingsSources(); dispatch( blocksStore ).setFreeformFallbackBlockName( 'core/html' ); registerLegacyWidgetBlock( { inserter: false } ); From f67ff905b371c522c2e7bbefcafcf4a2f37d2ea3 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:14:45 +0200 Subject: [PATCH 06/25] Change registration to allow server bootstrap --- packages/blocks/src/api/registration.js | 41 ++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 78aab99b11b61..0e2ca18dbe095 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -800,17 +800,46 @@ export const registerBlockBindingsSource = ( source ) => { canUserEditValue, } = source; - // Check if the source is already registered. const existingSource = unlock( select( blocksStore ) ).getBlockBindingsSource( name ); - if ( existingSource ) { + + /* + * Check if the source has been already registered on the client. + * If the `getValue` property is defined, it could be assumed the source is already registered. + */ + if ( existingSource?.getValue ) { warning( 'Block bindings source "' + name + '" is already registered.' ); return; } + // Check the properties from the server aren't overriden. + if ( existingSource ) { + /* + * It is not possible to just check the properties with a value because + * in some of them, like `canUserEditValue`, a default one could be used. + */ + const serverProperties = [ 'label', 'usesContext' ]; + let shouldReturn = false; + serverProperties.forEach( ( property ) => { + if ( existingSource[ property ] && source[ property ] ) { + console.error( + 'Block bindings "' + + name + + '" source "' + + property + + '" is already defined in the server.' + ); + shouldReturn = true; + } + } ); + if ( shouldReturn ) { + return; + } + } + // Check the `name` property is correct. if ( ! name ) { warning( 'Block bindings source must contain a name.' ); @@ -849,7 +878,7 @@ export const registerBlockBindingsSource = ( source ) => { return; } - if ( typeof label !== 'string' ) { + if ( label && typeof label !== 'string' ) { warning( 'Block bindings source label must be a string.' ); return; } @@ -878,7 +907,11 @@ export const registerBlockBindingsSource = ( source ) => { return; } - return unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); + if ( existingSource ) { + unlock( dispatch( blocksStore ) ).updateBlockBindingsSource( source ); + } else { + unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); + } }; /** From a8fa943bee7f5ab88ac2a3bffeca6096c95485d2 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:14:45 +0200 Subject: [PATCH 07/25] Remove label from post meta and pattern overrides --- packages/editor/src/bindings/pattern-overrides.js | 2 -- packages/editor/src/bindings/post-meta.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js index 54ca77650a5fe..492406a9f8eb1 100644 --- a/packages/editor/src/bindings/pattern-overrides.js +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -1,14 +1,12 @@ /** * WordPress dependencies */ -import { _x } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; const CONTENT = 'content'; export default { name: 'core/pattern-overrides', - label: _x( 'Pattern Overrides', 'block bindings source' ), getValue( { registry, clientId, context, attributeName } ) { const patternOverridesContent = context[ 'pattern/overrides' ]; const { getBlockAttributes } = registry.select( blockEditorStore ); diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index aec890c5ceff8..0173e5b5f6302 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -2,7 +2,6 @@ * WordPress dependencies */ import { store as coreDataStore } from '@wordpress/core-data'; -import { _x } from '@wordpress/i18n'; /** * Internal dependencies @@ -11,7 +10,6 @@ import { store as editorStore } from '../store'; export default { name: 'core/post-meta', - label: _x( 'Post Meta', 'block bindings source' ), getPlaceholder( { args } ) { return args.key; }, From 87420661f80191a397a743d5930a2bc8bde9a1db Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:14:45 +0200 Subject: [PATCH 08/25] Remove `updateBlockBindingsSource` --- packages/blocks/src/store/private-actions.js | 19 ------------------- packages/blocks/src/store/reducer.js | 18 ------------------ 2 files changed, 37 deletions(-) diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js index 178089e7935db..55cdb2128895f 100644 --- a/packages/blocks/src/store/private-actions.js +++ b/packages/blocks/src/store/private-actions.js @@ -69,22 +69,3 @@ export function removeBlockBindingsSource( name ) { name, }; } - -/** - * Updates an existing block bindings source. - * They could be initialized from the server. - * - * @param {string} source Name of the source to update. - */ -export function updateBlockBindingsSource( source ) { - return { - type: 'UPDATE_BLOCK_BINDINGS_SOURCE', - name: source.name, - label: source.label, - getValue: source.getValue, - setValue: source.setValue, - setValues: source.setValues, - getPlaceholder: source.getPlaceholder, - canUserEditValue: source.canUserEditValue, - }; -} diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index a69960da937d8..fc386e7ea9f55 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -387,24 +387,6 @@ export function blockBindingsSources( state = {}, action ) { case 'REMOVE_BLOCK_BINDINGS_SOURCE': return omit( state, action.name ); } - if ( action.type === 'UPDATE_BLOCK_BINDINGS_SOURCE' ) { - // Filter the name property and the undefined values. - const updatedProperties = Object.fromEntries( - Object.entries( action ).filter( - ( [ key, value ] ) => value !== undefined && key !== 'name' - ) - ); - - return { - ...state, - [ action.name ]: { - // Keep the existing properties. - ...state[ action.name ], - // Update with the new properties. - ...updatedProperties, - }, - }; - } return state; } From 429ebe769355bf132dfa4811a0a711b12155990d Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:15:15 +0200 Subject: [PATCH 09/25] Use `bootstrapBlockBindingsSource` instead --- packages/blocks/src/api/index.js | 2 ++ packages/blocks/src/api/registration.js | 33 +++++++++++++++++--- packages/blocks/src/store/private-actions.js | 14 +++++++++ packages/blocks/src/store/reducer.js | 20 ++++++++++++ packages/edit-post/src/index.js | 4 +-- packages/edit-site/src/index.js | 4 +-- 6 files changed, 68 insertions(+), 9 deletions(-) diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index 803467cb2187e..b5302b2c92e92 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -7,6 +7,7 @@ import { unregisterBlockBindingsSource, getBlockBindingsSource, getBlockBindingsSources, + bootstrapBlockBindingsSource, } from './registration'; // The blocktype is the most important concept within the block API. It defines @@ -182,4 +183,5 @@ lock( privateApis, { unregisterBlockBindingsSource, getBlockBindingsSource, getBlockBindingsSources, + bootstrapBlockBindingsSource, } ); diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 0e2ca18dbe095..0417a386fac00 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -907,11 +907,34 @@ export const registerBlockBindingsSource = ( source ) => { return; } - if ( existingSource ) { - unlock( dispatch( blocksStore ) ).updateBlockBindingsSource( source ); - } else { - unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); - } + unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); +}; + +/** + * Bootstrap a new block bindings source with an object with initial properties. + * This function is meant to initialize block bindings sources from the server. + * + * @param {Object} source Properties of the source to be bootstrapped. + * @param {string} source.name The unique and machine-readable name. + * @param {string} source.label Human-readable label. + * @param {Array} [source.usesContext] Array of context needed by the source. + * + * @example + * ```js + * import { bootstrapBlockBindingsSource } from '@wordpress/blocks' + * + * bootstrapBlockBindingsSource( { + * name: 'plugin/my-custom-source', + * label: 'Label', + * usesContext: [ 'postId', 'postType' ], + * } ); + * ``` + */ +export const bootstrapBlockBindingsSource = ( source ) => { + // No need for validation as it usually happens in the server. + unlock( dispatch( blocksStore ) ).addBootstrappedBlockBindingsSource( + source + ); }; /** diff --git a/packages/blocks/src/store/private-actions.js b/packages/blocks/src/store/private-actions.js index 55cdb2128895f..6f7581da53de3 100644 --- a/packages/blocks/src/store/private-actions.js +++ b/packages/blocks/src/store/private-actions.js @@ -69,3 +69,17 @@ export function removeBlockBindingsSource( name ) { name, }; } + +/** + * Add bootstrapped block bindings sources, usually initialized from the server. + * + * @param {string} source Name of the source to bootstrap. + */ +export function addBootstrappedBlockBindingsSource( source ) { + return { + type: 'ADD_BOOTSTRAPPED_BLOCK_BINDINGS_SOURCE', + name: source.name, + label: source.label, + usesContext: source.usesContext, + }; +} diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index fc386e7ea9f55..9a61cdfbc93da 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -374,6 +374,25 @@ export function collections( state = {}, action ) { export function blockBindingsSources( state = {}, action ) { switch ( action.type ) { case 'ADD_BLOCK_BINDINGS_SOURCE': + // Filter the name property and the undefined values. + const newProperties = Object.fromEntries( + Object.entries( action ).filter( + ( [ key, value ] ) => value !== undefined && key !== 'name' + ) + ); + + return { + ...state, + [ action.name ]: { + // Keep the existing properties if it has been bootstrapped. + ...state[ action.name ], + // Update with the new properties. + ...newProperties, + canUserEditValue: + action.canUserEditValue || ( () => false ), + }, + }; + case 'ADD_BOOTSTRAPPED_BLOCK_BINDINGS_SOURCE': return { ...state, [ action.name ]: { @@ -382,6 +401,7 @@ export function blockBindingsSources( state = {}, action ) { setValues: action.setValues, getPlaceholder: action.getPlaceholder, canUserEditValue: action.canUserEditValue, + usesContext: action.usesContext, }, }; case 'REMOVE_BLOCK_BINDINGS_SOURCE': diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index c2276d425628e..5e9df79f2097a 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -92,11 +92,11 @@ export function initializeEditor( registerCoreBlocks(); // Bootstrap block bindings sources from the server. if ( settings?.blockBindings ) { - const { registerBlockBindingsSource } = unlock( blocksPrivateApis ); + const { bootstrapBlockBindingsSource } = unlock( blocksPrivateApis ); for ( const [ name, args ] of Object.entries( settings.blockBindings ) ) { - registerBlockBindingsSource( { + bootstrapBlockBindingsSource( { name, ...args, } ); diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 4cffb45144a21..cb5c9f048de63 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -51,11 +51,11 @@ export function initializeEditor( id, settings ) { registerCoreBlocks( coreBlocks ); // Bootstrap block bindings sources from the server. if ( settings?.blockBindings ) { - const { registerBlockBindingsSource } = unlock( blocksPrivateApis ); + const { bootstrapBlockBindingsSource } = unlock( blocksPrivateApis ); for ( const [ name, args ] of Object.entries( settings.blockBindings ) ) { - registerBlockBindingsSource( { + bootstrapBlockBindingsSource( { name, ...args, } ); From 575a2a93a628e8e76815a18e833fe6dc0ee5dd94 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:15:15 +0200 Subject: [PATCH 10/25] Wrap server registration in the same function --- packages/blocks/src/api/registration.js | 27 --------------------- packages/edit-post/src/index.js | 19 +++------------ packages/edit-site/src/index.js | 25 ++++++------------- packages/editor/src/bindings/api.js | 32 ++++++++++++++++++++++++- packages/editor/src/private-apis.js | 6 ++++- 5 files changed, 46 insertions(+), 63 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 0417a386fac00..d14ff038bfa2a 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -910,33 +910,6 @@ export const registerBlockBindingsSource = ( source ) => { unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); }; -/** - * Bootstrap a new block bindings source with an object with initial properties. - * This function is meant to initialize block bindings sources from the server. - * - * @param {Object} source Properties of the source to be bootstrapped. - * @param {string} source.name The unique and machine-readable name. - * @param {string} source.label Human-readable label. - * @param {Array} [source.usesContext] Array of context needed by the source. - * - * @example - * ```js - * import { bootstrapBlockBindingsSource } from '@wordpress/blocks' - * - * bootstrapBlockBindingsSource( { - * name: 'plugin/my-custom-source', - * label: 'Label', - * usesContext: [ 'postId', 'postType' ], - * } ); - * ``` - */ -export const bootstrapBlockBindingsSource = ( source ) => { - // No need for validation as it usually happens in the server. - unlock( dispatch( blocksStore ) ).addBootstrappedBlockBindingsSource( - source - ); -}; - /** * Unregisters a block bindings source * diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 5e9df79f2097a..3cca207830ef5 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -1,10 +1,7 @@ /** * WordPress dependencies */ -import { - store as blocksStore, - privateApis as blocksPrivateApis, -} from '@wordpress/blocks'; +import { store as blocksStore } from '@wordpress/blocks'; import { registerCoreBlocks, __experimentalRegisterExperimentalCoreBlocks, @@ -32,6 +29,7 @@ const { BackButton: __experimentalMainDashboardButton, registerDefaultActions, registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, } = unlock( editorPrivateApis ); /** @@ -90,18 +88,7 @@ export function initializeEditor( } registerCoreBlocks(); - // Bootstrap block bindings sources from the server. - if ( settings?.blockBindings ) { - const { bootstrapBlockBindingsSource } = unlock( blocksPrivateApis ); - for ( const [ name, args ] of Object.entries( - settings.blockBindings - ) ) { - bootstrapBlockBindingsSource( { - name, - ...args, - } ); - } - } + bootstrapBlockBindingsSourcesFromServer( settings?.blockBindings ); registerCoreBlockBindingsSources(); registerLegacyWidgetBlock( { inserter: false } ); registerWidgetGroupBlock( { inserter: false } ); diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index cb5c9f048de63..505ef753669f0 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -1,10 +1,7 @@ /** * WordPress dependencies */ -import { - store as blocksStore, - privateApis as blocksPrivateApis, -} from '@wordpress/blocks'; +import { store as blocksStore } from '@wordpress/blocks'; import { registerCoreBlocks, __experimentalGetCoreBlocks, @@ -31,8 +28,11 @@ import { store as editSiteStore } from './store'; import { unlock } from './lock-unlock'; import App from './components/app'; -const { registerDefaultActions, registerCoreBlockBindingsSources } = - unlock( editorPrivateApis ); +const { + registerDefaultActions, + registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, +} = unlock( editorPrivateApis ); /** * Initializes the site editor screen. @@ -49,18 +49,7 @@ export function initializeEditor( id, settings ) { ( { name } ) => name !== 'core/freeform' ); registerCoreBlocks( coreBlocks ); - // Bootstrap block bindings sources from the server. - if ( settings?.blockBindings ) { - const { bootstrapBlockBindingsSource } = unlock( blocksPrivateApis ); - for ( const [ name, args ] of Object.entries( - settings.blockBindings - ) ) { - bootstrapBlockBindingsSource( { - name, - ...args, - } ); - } - } + bootstrapBlockBindingsSourcesFromServer( settings?.blockBindings ); registerCoreBlockBindingsSources(); dispatch( blocksStore ).setFreeformFallbackBlockName( 'core/html' ); registerLegacyWidgetBlock( { inserter: false } ); diff --git a/packages/editor/src/bindings/api.js b/packages/editor/src/bindings/api.js index 0037f3334215b..2cfed5168a143 100644 --- a/packages/editor/src/bindings/api.js +++ b/packages/editor/src/bindings/api.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { privateApis as blocksPrivateApis } from '@wordpress/blocks'; +import { + privateApis as blocksPrivateApis, + store as blocksStore, +} from '@wordpress/blocks'; +import { dispatch } from '@wordpress/data'; /** * Internal dependencies @@ -25,3 +29,29 @@ export function registerCoreBlockBindingsSources() { registerBlockBindingsSource( patternOverrides ); registerBlockBindingsSource( postMeta ); } + +/** + * Function to bootstrap core block bindings sources defined in the server. + * + * @param {Object} sources Object containing the sources to bootstrap. + * + * @example + * ```js + * import { bootstrapBlockBindingsSourcesFromServer } from '@wordpress/editor'; + * + * bootstrapBlockBindingsSourcesFromServer( sources ); + * ``` + */ +export function bootstrapBlockBindingsSourcesFromServer( sources ) { + if ( sources ) { + const { addBootstrappedBlockBindingsSource } = unlock( + dispatch( blocksStore ) + ); + for ( const [ name, args ] of Object.entries( sources ) ) { + addBootstrappedBlockBindingsSource( { + name, + ...args, + } ); + } + } +} diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index 58688a9099e87..f949be8e9321f 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -24,7 +24,10 @@ import { GlobalStylesProvider, } from './components/global-styles-provider'; import registerDefaultActions from './dataviews/actions'; -import { registerCoreBlockBindingsSources } from './bindings/api'; +import { + registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, +} from './bindings/api'; const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis; @@ -45,6 +48,7 @@ lock( privateApis, { ResizableEditor, registerDefaultActions, registerCoreBlockBindingsSources, + bootstrapBlockBindingsSourcesFromServer, // This is a temporary private API while we're updating the site editor to use EditorProvider. useBlockEditorSettings, From 9a56150d1a031bbba698f076ddfaf147e98f542d Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:15:16 +0200 Subject: [PATCH 11/25] Ensure label is provided when source is not bootstrapped --- packages/blocks/src/api/registration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index d14ff038bfa2a..2e00535782564 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -873,7 +873,7 @@ export const registerBlockBindingsSource = ( source ) => { } // Check the `label` property is correct. - if ( ! label ) { + if ( ! label && ! existingSource?.label ) { warning( 'Block bindings source must contain a label.' ); return; } From a98c45a52e4353b5ac800fd2c313b144dbd713df Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:15:16 +0200 Subject: [PATCH 12/25] Remove old import --- packages/blocks/src/api/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/blocks/src/api/index.js b/packages/blocks/src/api/index.js index b5302b2c92e92..803467cb2187e 100644 --- a/packages/blocks/src/api/index.js +++ b/packages/blocks/src/api/index.js @@ -7,7 +7,6 @@ import { unregisterBlockBindingsSource, getBlockBindingsSource, getBlockBindingsSources, - bootstrapBlockBindingsSource, } from './registration'; // The blocktype is the most important concept within the block API. It defines @@ -183,5 +182,4 @@ lock( privateApis, { unregisterBlockBindingsSource, getBlockBindingsSource, getBlockBindingsSources, - bootstrapBlockBindingsSource, } ); From bde87d2a02c496327621a02eab72f5b7bde30113 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:15:16 +0200 Subject: [PATCH 13/25] Add compat filter to expose server sources --- lib/compat/wordpress-6.7/block-bindings.php | 35 +++++++++++++++++++++ lib/load.php | 1 + 2 files changed, 36 insertions(+) create mode 100644 lib/compat/wordpress-6.7/block-bindings.php diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php new file mode 100644 index 0000000000000..41d8df870d964 --- /dev/null +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -0,0 +1,35 @@ + $source_properties ) { + // Add source with the label to editor settings. + $editor_settings['blockBindings'][ $source_name ] = array( + 'label' => $source_properties->label, + ); + // Add `usesContext` property if exists. + if ( ! empty( $source_properties->uses_context ) ) { + $editor_settings['blockBindings'][ $source_name ]['usesContext'] = $source_properties->uses_context; + } + } + } + return $editor_settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_add_server_block_bindings_sources_to_editor_settings', 10 ); diff --git a/lib/load.php b/lib/load.php index 47ce101101297..312d95acea270 100644 --- a/lib/load.php +++ b/lib/load.php @@ -154,6 +154,7 @@ function gutenberg_is_experiment_enabled( $name ) { // WordPress 6.7 compat. require __DIR__ . '/compat/wordpress-6.7/blocks.php'; +require __DIR__ . '/compat/wordpress-6.7/block-bindings.php'; // Experimental features. require __DIR__ . '/experimental/block-editor-settings-mobile.php'; From 7cdf395e933282a3fb6a68bce55fb7d5c32d6cb6 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:15:16 +0200 Subject: [PATCH 14/25] Add e2e test --- packages/e2e-tests/plugins/block-bindings.php | 16 ++++++++-- .../editor/various/block-bindings.spec.js | 29 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/e2e-tests/plugins/block-bindings.php b/packages/e2e-tests/plugins/block-bindings.php index 74aec2adb500f..b2eb9d797610d 100644 --- a/packages/e2e-tests/plugins/block-bindings.php +++ b/packages/e2e-tests/plugins/block-bindings.php @@ -8,9 +8,19 @@ */ /** -* Register custom fields. +* Register custom fields and custom block bindings sources. */ -function gutenberg_test_block_bindings_register_custom_fields() { +function gutenberg_test_block_bindings_registration() { + // Register custom block bindings sources. + register_block_bindings_source( + 'core/server-source', + array( + 'label' => 'Server Source', + 'get_value_callback' => function () {}, + ) + ); + + // Register custom fields. register_meta( 'post', 'text_custom_field', @@ -51,4 +61,4 @@ function gutenberg_test_block_bindings_register_custom_fields() { ) ); } -add_action( 'init', 'gutenberg_test_block_bindings_register_custom_fields' ); +add_action( 'init', 'gutenberg_test_block_bindings_registration' ); diff --git a/test/e2e/specs/editor/various/block-bindings.spec.js b/test/e2e/specs/editor/various/block-bindings.spec.js index 222004c7c1bcc..1499e255482c6 100644 --- a/test/e2e/specs/editor/various/block-bindings.spec.js +++ b/test/e2e/specs/editor/various/block-bindings.spec.js @@ -2172,4 +2172,33 @@ test.describe( 'Block bindings', () => { } ); } ); } ); + + test.describe( 'Sources registration', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost( { title: 'Test bindings' } ); + } ); + + test( 'should show the label of a source only registered in the server', async ( { + editor, + page, + } ) => { + await editor.insertBlock( { + name: 'core/paragraph', + attributes: { + metadata: { + bindings: { + content: { + source: 'core/server-source', + }, + }, + }, + }, + } ); + + const bindingLabel = page.locator( + '.components-item__block-bindings-source' + ); + await expect( bindingLabel ).toHaveText( 'Server Source' ); + } ); + } ); } ); From 18f722715f6f59cfb6149abafc3115552f626ce7 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:15:16 +0200 Subject: [PATCH 15/25] Check if the sources are already added --- lib/compat/wordpress-6.7/block-bindings.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php index 41d8df870d964..354200d536ea7 100644 --- a/lib/compat/wordpress-6.7/block-bindings.php +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -14,6 +14,11 @@ * @return array The editor settings including the block bindings sources. */ function gutenberg_add_server_block_bindings_sources_to_editor_settings( $editor_settings ) { + // Check if the sources are already exposed in the editor settings. + if ( isset( $editor_settings['blockBindings'] ) ) { + return $editor_settings; + } + $registered_block_bindings_sources = get_all_registered_block_bindings_sources(); if ( ! empty( $registered_block_bindings_sources ) ) { // Initialize array. From a05caf6371be5b02bff5a13142db2bc2b3690c03 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:16:30 +0200 Subject: [PATCH 16/25] Change how label is managed --- packages/blocks/src/api/registration.js | 36 ++++++-------------- packages/blocks/src/api/test/registration.js | 14 ++++++++ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 2e00535782564..57e38f60bfa0e 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -769,7 +769,7 @@ export const unregisterBlockVariation = ( blockName, variationName ) => { * * @param {Object} source Properties of the source to be registered. * @param {string} source.name The unique and machine-readable name. - * @param {string} source.label Human-readable label. + * @param {string} [source.label] Human-readable label. * @param {Function} [source.getValues] Function to get the values from the source. * @param {Function} [source.setValues] Function to update multiple values connected to the source. * @param {Function} [source.getPlaceholder] Function to get the placeholder when the value is undefined. @@ -815,31 +815,6 @@ export const registerBlockBindingsSource = ( source ) => { return; } - // Check the properties from the server aren't overriden. - if ( existingSource ) { - /* - * It is not possible to just check the properties with a value because - * in some of them, like `canUserEditValue`, a default one could be used. - */ - const serverProperties = [ 'label', 'usesContext' ]; - let shouldReturn = false; - serverProperties.forEach( ( property ) => { - if ( existingSource[ property ] && source[ property ] ) { - console.error( - 'Block bindings "' + - name + - '" source "' + - property + - '" is already defined in the server.' - ); - shouldReturn = true; - } - } ); - if ( shouldReturn ) { - return; - } - } - // Check the `name` property is correct. if ( ! name ) { warning( 'Block bindings source must contain a name.' ); @@ -873,6 +848,15 @@ export const registerBlockBindingsSource = ( source ) => { } // Check the `label` property is correct. + if ( label && existingSource?.label ) { + console.warn( + 'Block bindings "' + + name + + '" source label is already defined in the server.' + ); + return; + } + if ( ! label && ! existingSource?.label ) { warning( 'Block bindings source must contain a label.' ); return; diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index bc1057597bcd7..9a13cc2213fc5 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -1512,6 +1512,20 @@ describe( 'blocks', () => { expect( getBlockBindingsSource( 'core/testing' ) ).toBeUndefined(); } ); + it( 'should not override label from the server', () => { + registerBlockBindingsSource( { + name: 'core/server', + label: 'Server label', + } ); + registerBlockBindingsSource( { + name: 'core/server', + label: 'Client label', + } ); + expect( console ).toHaveWarnedWith( + 'Block bindings "core/server" source label is already defined in the server.' + ); + } ); + // Check the `getValues` callback is correct. it( 'should reject invalid getValues callback', () => { registerBlockBindingsSource( { From f330a6adeafeea95ea917620e4218918f40744ff Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:16:31 +0200 Subject: [PATCH 17/25] Remove type from object --- packages/blocks/src/api/test/registration.js | 1 + packages/blocks/src/store/reducer.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 9a13cc2213fc5..01906640cae2c 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -1614,6 +1614,7 @@ describe( 'blocks', () => { const source = { name: 'core/test-source', label: 'Test Source', + getValue: () => 'value', }; registerBlockBindingsSource( source ); registerBlockBindingsSource( source ); diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 9a61cdfbc93da..62b3ebd1c28ad 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -374,10 +374,11 @@ export function collections( state = {}, action ) { export function blockBindingsSources( state = {}, action ) { switch ( action.type ) { case 'ADD_BLOCK_BINDINGS_SOURCE': - // Filter the name property and the undefined values. + // Filter the name property, the type property, and the undefined values. const newProperties = Object.fromEntries( Object.entries( action ).filter( - ( [ key, value ] ) => value !== undefined && key !== 'name' + ( [ key, value ] ) => + value !== undefined && key !== 'name' && key !== 'type' ) ); From c1ebd6bc8fe710dd90a1e95234b4c13d5fedc2be Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:16:31 +0200 Subject: [PATCH 18/25] Use blockBindingsSources name --- lib/compat/wordpress-6.7/block-bindings.php | 8 ++++---- packages/edit-post/src/index.js | 2 +- packages/edit-site/src/index.js | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php index 354200d536ea7..fe7ad62423752 100644 --- a/lib/compat/wordpress-6.7/block-bindings.php +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -15,22 +15,22 @@ */ function gutenberg_add_server_block_bindings_sources_to_editor_settings( $editor_settings ) { // Check if the sources are already exposed in the editor settings. - if ( isset( $editor_settings['blockBindings'] ) ) { + if ( isset( $editor_settings['blockBindingsSources'] ) ) { return $editor_settings; } $registered_block_bindings_sources = get_all_registered_block_bindings_sources(); if ( ! empty( $registered_block_bindings_sources ) ) { // Initialize array. - $editor_settings['blockBindings'] = array(); + $editor_settings['blockBindingsSources'] = array(); foreach ( $registered_block_bindings_sources as $source_name => $source_properties ) { // Add source with the label to editor settings. - $editor_settings['blockBindings'][ $source_name ] = array( + $editor_settings['blockBindingsSources'][ $source_name ] = array( 'label' => $source_properties->label, ); // Add `usesContext` property if exists. if ( ! empty( $source_properties->uses_context ) ) { - $editor_settings['blockBindings'][ $source_name ]['usesContext'] = $source_properties->uses_context; + $editor_settings['blockBindingsSources'][ $source_name ]['usesContext'] = $source_properties->uses_context; } } } diff --git a/packages/edit-post/src/index.js b/packages/edit-post/src/index.js index 3cca207830ef5..aa3473e6e55d4 100644 --- a/packages/edit-post/src/index.js +++ b/packages/edit-post/src/index.js @@ -88,7 +88,7 @@ export function initializeEditor( } registerCoreBlocks(); - bootstrapBlockBindingsSourcesFromServer( settings?.blockBindings ); + bootstrapBlockBindingsSourcesFromServer( settings?.blockBindingsSources ); registerCoreBlockBindingsSources(); registerLegacyWidgetBlock( { inserter: false } ); registerWidgetGroupBlock( { inserter: false } ); diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 505ef753669f0..922e2f6ab933a 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -49,7 +49,7 @@ export function initializeEditor( id, settings ) { ( { name } ) => name !== 'core/freeform' ); registerCoreBlocks( coreBlocks ); - bootstrapBlockBindingsSourcesFromServer( settings?.blockBindings ); + bootstrapBlockBindingsSourcesFromServer( settings?.blockBindingsSources ); registerCoreBlockBindingsSources(); dispatch( blocksStore ).setFreeformFallbackBlockName( 'core/html' ); registerLegacyWidgetBlock( { inserter: false } ); From 33603d3b648d71b3b969dccef92cea06cb578591 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:16:31 +0200 Subject: [PATCH 19/25] Fixes from rebase --- packages/editor/src/bindings/post-meta.js | 15 +++++++---- .../src/components/provider/bindings/api.js | 27 ------------------- 2 files changed, 10 insertions(+), 32 deletions(-) delete mode 100644 packages/editor/src/components/provider/bindings/api.js diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 0173e5b5f6302..6db9de7b7cdda 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -32,6 +32,11 @@ export default { } ); }, canUserEditValue( { select, context, args } ) { + // Lock editing in query loop. + if ( context?.query || context?.queryId ) { + return false; + } + const postType = context?.postType || select( editorStore ).getCurrentPostType(); @@ -52,11 +57,11 @@ export default { } // Check that the user has the capability to edit post meta. - const canUserEdit = select( coreDataStore ).canUserEditEntityRecord( - 'postType', - context?.postType, - context?.postId - ); + const canUserEdit = select( coreDataStore ).canUser( 'update', { + kind: 'postType', + name: context?.postType, + id: context?.postId, + } ); if ( ! canUserEdit ) { return false; } diff --git a/packages/editor/src/components/provider/bindings/api.js b/packages/editor/src/components/provider/bindings/api.js deleted file mode 100644 index 0037f3334215b..0000000000000 --- a/packages/editor/src/components/provider/bindings/api.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * WordPress dependencies - */ -import { privateApis as blocksPrivateApis } from '@wordpress/blocks'; - -/** - * Internal dependencies - */ -import patternOverrides from './pattern-overrides'; -import postMeta from './post-meta'; -import { unlock } from '../lock-unlock'; - -/** - * Function to register core block bindings sources provided by the editor. - * - * @example - * ```js - * import { registerCoreBlockBindingsSources } from '@wordpress/editor'; - * - * registerCoreBlockBindingsSources(); - * ``` - */ -export function registerCoreBlockBindingsSources() { - const { registerBlockBindingsSource } = unlock( blocksPrivateApis ); - registerBlockBindingsSource( patternOverrides ); - registerBlockBindingsSource( postMeta ); -} From facd94eef24fc6e1b8ee9c6a696a0379730fe61f Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:16:31 +0200 Subject: [PATCH 20/25] Add return back and warning --- packages/blocks/src/api/registration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index 57e38f60bfa0e..da377afa1c727 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -849,7 +849,7 @@ export const registerBlockBindingsSource = ( source ) => { // Check the `label` property is correct. if ( label && existingSource?.label ) { - console.warn( + warning( 'Block bindings "' + name + '" source label is already defined in the server.' @@ -891,7 +891,7 @@ export const registerBlockBindingsSource = ( source ) => { return; } - unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); + return unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); }; /** From b3b0a387d6d5ead6ba3777afec43dc36d4bb90e5 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:16:31 +0200 Subject: [PATCH 21/25] Add backport --- backport-changelog/6.7/7020.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 backport-changelog/6.7/7020.md diff --git a/backport-changelog/6.7/7020.md b/backport-changelog/6.7/7020.md new file mode 100644 index 0000000000000..8eacb220d340e --- /dev/null +++ b/backport-changelog/6.7/7020.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/7020 + +* https://github.com/WordPress/gutenberg/pull/63470 From cf2a8e342bdae0eaacdbf929c5fb1a7b5acce880 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:16:31 +0200 Subject: [PATCH 22/25] Improve comments --- lib/compat/wordpress-6.7/block-bindings.php | 2 +- packages/blocks/src/api/test/registration.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.7/block-bindings.php b/lib/compat/wordpress-6.7/block-bindings.php index fe7ad62423752..4c82dc6683f37 100644 --- a/lib/compat/wordpress-6.7/block-bindings.php +++ b/lib/compat/wordpress-6.7/block-bindings.php @@ -8,7 +8,7 @@ /** * Adds the block bindings sources registered in the server to the editor settings. * - * This allows to bootstrap them in the editor. + * This allows them to be bootstrapped in the editor. * * @param array $settings The block editor settings from the `block_editor_settings_all` filter. * @return array The editor settings including the block bindings sources. diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 01906640cae2c..80f15299d4194 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -1513,10 +1513,12 @@ describe( 'blocks', () => { } ); it( 'should not override label from the server', () => { + // Simulate bootstrapping a source from the server registration. registerBlockBindingsSource( { name: 'core/server', label: 'Server label', } ); + // Override the source with a different label in the client. registerBlockBindingsSource( { name: 'core/server', label: 'Client label', From 227890f8c1afa49273c4f2bb0c936e2795c1a30a Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 13:23:19 +0200 Subject: [PATCH 23/25] Fix getValues/setValues after rebase --- .../editor/src/bindings/pattern-overrides.js | 43 ++++++++++++------- packages/editor/src/bindings/post-meta.js | 21 ++++++--- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/packages/editor/src/bindings/pattern-overrides.js b/packages/editor/src/bindings/pattern-overrides.js index 492406a9f8eb1..88c6c73bdc61c 100644 --- a/packages/editor/src/bindings/pattern-overrides.js +++ b/packages/editor/src/bindings/pattern-overrides.js @@ -7,28 +7,32 @@ const CONTENT = 'content'; export default { name: 'core/pattern-overrides', - getValue( { registry, clientId, context, attributeName } ) { + getValues( { registry, clientId, context, bindings } ) { const patternOverridesContent = context[ 'pattern/overrides' ]; const { getBlockAttributes } = registry.select( blockEditorStore ); const currentBlockAttributes = getBlockAttributes( clientId ); - if ( ! patternOverridesContent ) { - return currentBlockAttributes[ attributeName ]; - } - - const overridableValue = - patternOverridesContent?.[ - currentBlockAttributes?.metadata?.name - ]?.[ attributeName ]; + const overridesValues = {}; + for ( const attributeName of Object.keys( bindings ) ) { + const overridableValue = + patternOverridesContent?.[ + currentBlockAttributes?.metadata?.name + ]?.[ attributeName ]; - // If there is no pattern client ID, or it is not overwritten, return the default value. - if ( overridableValue === undefined ) { - return currentBlockAttributes[ attributeName ]; + // If it has not been overriden, return the original value. + // Check undefined because empty string is a valid value. + if ( overridableValue === undefined ) { + overridesValues[ attributeName ] = + currentBlockAttributes[ attributeName ]; + continue; + } else { + overridesValues[ attributeName ] = + overridableValue === '' ? undefined : overridableValue; + } } - - return overridableValue === '' ? undefined : overridableValue; + return overridesValues; }, - setValues( { registry, clientId, attributes } ) { + setValues( { registry, clientId, bindings } ) { const { getBlockAttributes, getBlockParentsByBlockName, getBlocks } = registry.select( blockEditorStore ); const currentBlockAttributes = getBlockAttributes( clientId ); @@ -43,6 +47,15 @@ export default { true ); + // Extract the updated attributes from the source bindings. + const attributes = Object.entries( bindings ).reduce( + ( attrs, [ key, { newValue } ] ) => { + attrs[ key ] = newValue; + return attrs; + }, + {} + ); + // If there is no pattern client ID, sync blocks with the same name and same attributes. if ( ! patternClientId ) { const syncBlocksWithSameName = ( blocks ) => { diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 6db9de7b7cdda..aafc784a21bd4 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -13,22 +13,29 @@ export default { getPlaceholder( { args } ) { return args.key; }, - getValue( { registry, context, args } ) { - return registry + getValues( { registry, context, bindings } ) { + const meta = registry .select( coreDataStore ) .getEditedEntityRecord( 'postType', context?.postType, context?.postId - ).meta?.[ args.key ]; + )?.meta; + const newValues = {}; + for ( const [ attributeName, source ] of Object.entries( bindings ) ) { + newValues[ attributeName ] = meta?.[ source.args.key ]; + } + return newValues; }, - setValue( { registry, context, args, value } ) { + setValues( { registry, context, bindings } ) { + const newMeta = {}; + Object.values( bindings ).forEach( ( { args, newValue } ) => { + newMeta[ args.key ] = newValue; + } ); registry .dispatch( coreDataStore ) .editEntityRecord( 'postType', context?.postType, context?.postId, { - meta: { - [ args.key ]: value, - }, + meta: newMeta, } ); }, canUserEditValue( { select, context, args } ) { From 50cfa094ae7d04c4de3dce890f167484f0a40155 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 19 Jul 2024 14:08:24 +0200 Subject: [PATCH 24/25] Adapt reducer and fixes --- packages/blocks/src/api/registration.js | 13 ++++++++--- packages/blocks/src/api/test/registration.js | 2 +- packages/blocks/src/store/reducer.js | 24 +++++--------------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index da377afa1c727..a0e9d470d5a48 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -806,9 +806,9 @@ export const registerBlockBindingsSource = ( source ) => { /* * Check if the source has been already registered on the client. - * If the `getValue` property is defined, it could be assumed the source is already registered. + * If the `getValues` property is defined, it could be assumed the source is already registered. */ - if ( existingSource?.getValue ) { + if ( existingSource?.getValues ) { warning( 'Block bindings source "' + name + '" is already registered.' ); @@ -891,7 +891,14 @@ export const registerBlockBindingsSource = ( source ) => { return; } - return unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); + return unlock( dispatch( blocksStore ) ).addBlockBindingsSource( { + name, + label, + getValues, + setValues, + getPlaceholder, + canUserEditValue, + } ); }; /** diff --git a/packages/blocks/src/api/test/registration.js b/packages/blocks/src/api/test/registration.js index 80f15299d4194..d36abee2930bf 100644 --- a/packages/blocks/src/api/test/registration.js +++ b/packages/blocks/src/api/test/registration.js @@ -1616,7 +1616,7 @@ describe( 'blocks', () => { const source = { name: 'core/test-source', label: 'Test Source', - getValue: () => 'value', + getValues: () => 'value', }; registerBlockBindingsSource( source ); registerBlockBindingsSource( source ); diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 62b3ebd1c28ad..5cffb0abc9197 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -374,23 +374,15 @@ export function collections( state = {}, action ) { export function blockBindingsSources( state = {}, action ) { switch ( action.type ) { case 'ADD_BLOCK_BINDINGS_SOURCE': - // Filter the name property, the type property, and the undefined values. - const newProperties = Object.fromEntries( - Object.entries( action ).filter( - ( [ key, value ] ) => - value !== undefined && key !== 'name' && key !== 'type' - ) - ); - return { ...state, [ action.name ]: { - // Keep the existing properties if it has been bootstrapped. - ...state[ action.name ], - // Update with the new properties. - ...newProperties, - canUserEditValue: - action.canUserEditValue || ( () => false ), + // Don't override the label if it's already set. + label: state[ action.name ]?.label || action.label, + getValues: action.getValues, + setValues: action.setValues, + getPlaceholder: action.getPlaceholder, + canUserEditValue: action.canUserEditValue, }, }; case 'ADD_BOOTSTRAPPED_BLOCK_BINDINGS_SOURCE': @@ -398,10 +390,6 @@ export function blockBindingsSources( state = {}, action ) { ...state, [ action.name ]: { label: action.label, - getValues: action.getValues, - setValues: action.setValues, - getPlaceholder: action.getPlaceholder, - canUserEditValue: action.canUserEditValue, usesContext: action.usesContext, }, }; From 8389d37177b01481451a6be0d52dd30491931eb2 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jul 2024 08:56:33 +0200 Subject: [PATCH 25/25] Revert args used in `addBlockBindingsSource` --- packages/blocks/src/api/registration.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/blocks/src/api/registration.js b/packages/blocks/src/api/registration.js index a0e9d470d5a48..886309d8bd8f3 100644 --- a/packages/blocks/src/api/registration.js +++ b/packages/blocks/src/api/registration.js @@ -891,14 +891,7 @@ export const registerBlockBindingsSource = ( source ) => { return; } - return unlock( dispatch( blocksStore ) ).addBlockBindingsSource( { - name, - label, - getValues, - setValues, - getPlaceholder, - canUserEditValue, - } ); + return unlock( dispatch( blocksStore ) ).addBlockBindingsSource( source ); }; /**