From d92ed3b03fc1c4a07954ed2cc2a634e9206f847a Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 11:24:18 +0100 Subject: [PATCH 01/12] Create helper to update bindings attribute --- packages/block-editor/README.md | 12 ++++ packages/block-editor/src/utils/index.js | 1 + .../src/utils/update-block-bindings.js | 68 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 packages/block-editor/src/utils/update-block-bindings.js diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md index 5917ac235505c..d087023741953 100644 --- a/packages/block-editor/README.md +++ b/packages/block-editor/README.md @@ -812,6 +812,18 @@ _Properties_ Ensures that the text selection keeps the same vertical distance from the viewport during keyboard events within this component. The vertical distance can vary. It is the last clicked or scrolled to position. +### updateBlockBindingsAttribute + +Helper to update the bindings attribute used by the Block Bindings API. + +_Parameters_ + +- _blockAttributes_ `Object`: - The original block attributes. +- _setAttributes_ `Function`: - setAttributes function to modify the bindings property. +- _attributeName_ `string`: - The attribute in the bindings object to update. +- _sourceName_ `string`: - The source name added to the bindings property. +- _sourceAttributes_ `string`: - The source attributes added to the bindings property. + ### URLInput _Related_ diff --git a/packages/block-editor/src/utils/index.js b/packages/block-editor/src/utils/index.js index ee3b2692b369a..21f33acfc274d 100644 --- a/packages/block-editor/src/utils/index.js +++ b/packages/block-editor/src/utils/index.js @@ -1,3 +1,4 @@ export { default as transformStyles } from './transform-styles'; export * from './block-variation-transforms'; export { default as getPxFromCssUnit } from './get-px-from-css-unit'; +export * from './update-block-bindings'; diff --git a/packages/block-editor/src/utils/update-block-bindings.js b/packages/block-editor/src/utils/update-block-bindings.js new file mode 100644 index 0000000000000..c34aa8b46c6e3 --- /dev/null +++ b/packages/block-editor/src/utils/update-block-bindings.js @@ -0,0 +1,68 @@ +/** + * Helper to update the bindings attribute used by the Block Bindings API. + * + * @param {Object} blockAttributes - The original block attributes. + * @param {Function} setAttributes - setAttributes function to modify the bindings property. + * @param {string} attributeName - The attribute in the bindings object to update. + * @param {string} sourceName - The source name added to the bindings property. + * @param {string} sourceAttributes - The source attributes added to the bindings property. + */ +export const updateBlockBindingsAttribute = ( + blockAttributes, + setAttributes, + attributeName, + sourceName, + sourceAttributes +) => { + // TODO: Review if we can create a React Hook for this. + + // Assuming the following format for the bindings property of the "metadata" attribute: + // + // "bindings": { + // "title": { + // "source": { + // "name": "metadata", + // "attributes": { "value": "text_custom_field" } + // } + // }, + // "url": { + // "source": { + // "name": "metadata", + // "attributes": { "value": "text_custom_field" } + // } + // } + // }, + // . + + let updatedBindings = {}; + // // If no sourceName is provided, remove the attribute from the bindings. + if ( sourceName === null ) { + if ( ! blockAttributes?.metadata.bindings ) { + return blockAttributes?.metadata; + } + + updatedBindings = { + ...blockAttributes?.metadata?.bindings, + [ attributeName ]: undefined, + }; + if ( Object.keys( updatedBindings ).length === 1 ) { + updatedBindings = undefined; + } + } else { + updatedBindings = { + ...blockAttributes?.metadata?.bindings, + [ attributeName ]: { + source: { name: sourceName, attributes: sourceAttributes }, + }, + }; + } + + setAttributes( { + metadata: { + ...blockAttributes.metadata, + bindings: updatedBindings, + }, + } ); + + return blockAttributes.metadata; +}; From 9e419759dcf858ffc342205b9bd17631c661fc41 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 11:25:18 +0100 Subject: [PATCH 02/12] Use helper in patterns --- .../components/partial-syncing-controls.js | 73 +++++++++---------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/packages/patterns/src/components/partial-syncing-controls.js b/packages/patterns/src/components/partial-syncing-controls.js index f5ac19bc05f3d..6da7cfccd735f 100644 --- a/packages/patterns/src/components/partial-syncing-controls.js +++ b/packages/patterns/src/components/partial-syncing-controls.js @@ -6,7 +6,10 @@ import { nanoid } from 'nanoid'; /** * WordPress dependencies */ -import { InspectorControls } from '@wordpress/block-editor'; +import { + InspectorControls, + updateBlockBindingsAttribute, +} from '@wordpress/block-editor'; import { BaseControl, CheckboxControl } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -31,59 +34,49 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { } function updateBindings( isChecked ) { - let updatedBindings = { - ...attributes?.metadata?.bindings, - }; - if ( ! isChecked ) { for ( const attributeName of Object.keys( syncedAttributes ) ) { - if ( - updatedBindings[ attributeName ]?.source?.name === - 'pattern_attributes' - ) { - delete updatedBindings[ attributeName ]; - } - } - if ( ! Object.keys( updatedBindings ).length ) { - updatedBindings = undefined; + updateBlockBindingsAttribute( + attributes, + setAttributes, + attributeName, + null, + null + ); } - setAttributes( { - metadata: { - ...attributes.metadata, - bindings: updatedBindings, - }, - } ); return; } - for ( const attributeName of Object.keys( syncedAttributes ) ) { - if ( ! updatedBindings[ attributeName ] ) { - updatedBindings[ attributeName ] = { - source: { - name: 'pattern_attributes', - }, - }; + if ( typeof attributes.metadata?.id === 'string' ) { + for ( const attributeName of Object.keys( syncedAttributes ) ) { + updateBlockBindingsAttribute( + attributes, + setAttributes, + attributeName, + 'pattern_attributes', + null + ); } + return; } - if ( typeof attributes.metadata?.id === 'string' ) { + const id = nanoid( 6 ); + for ( const attributeName of Object.keys( syncedAttributes ) ) { + const newMetadata = updateBlockBindingsAttribute( + attributes, + setAttributes, + attributeName, + 'pattern_attributes', + null + ); + setAttributes( { metadata: { - ...attributes.metadata, - bindings: updatedBindings, + ...newMetadata, + id, }, } ); - return; } - - const id = nanoid( 6 ); - setAttributes( { - metadata: { - ...attributes.metadata, - id, - bindings: updatedBindings, - }, - } ); } return ( From 6be8500fc7555d75eca7d60f1eb5ad408be8b03c Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 11:51:49 +0100 Subject: [PATCH 03/12] Add UI for block bindings with a Fill for sources --- .../components/block-bindings/bindings-ui.js | 274 ++++++++++++++++++ .../src/components/block-bindings/index.js | 4 + .../src/components/block-bindings/style.scss | 30 ++ packages/editor/src/components/index.js | 3 + packages/editor/src/style.scss | 1 + 5 files changed, 312 insertions(+) create mode 100644 packages/editor/src/components/block-bindings/bindings-ui.js create mode 100644 packages/editor/src/components/block-bindings/index.js create mode 100644 packages/editor/src/components/block-bindings/style.scss diff --git a/packages/editor/src/components/block-bindings/bindings-ui.js b/packages/editor/src/components/block-bindings/bindings-ui.js new file mode 100644 index 0000000000000..3827deae790c4 --- /dev/null +++ b/packages/editor/src/components/block-bindings/bindings-ui.js @@ -0,0 +1,274 @@ +/** + * WordPress dependencies + */ +import { useState, cloneElement, Fragment } from '@wordpress/element'; +import { + BlockControls, + updateBlockBindingsAttribute, +} from '@wordpress/block-editor'; +import { + Button, + createSlotFill, + MenuItem, + MenuGroup, + Popover, +} from '@wordpress/components'; +import { + plugins as pluginsIcon, + chevronDown, + chevronUp, +} from '@wordpress/icons'; +import { addFilter } from '@wordpress/hooks'; + +const blockBindingsWhitelist = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'url', 'title' ], + 'core/button': [ 'url', 'text' ], +}; + +const { Slot, Fill } = createSlotFill( 'BlockBindingsUI' ); + +const BlockBindingsFill = ( { children, source, label } ) => { + return ( + + { ( props ) => { + return ( + <> + { cloneElement( children, { + source, + label, + ...props, + } ) } + + ); + } } + + ); +}; + +export default BlockBindingsFill; + +const BlockBindingsUI = ( props ) => { + const [ addingBinding, setAddingBinding ] = useState( false ); + const [ popoverAnchor, setPopoverAnchor ] = useState(); + return ( + <> + + + { addingBinding && ( + { + setAddingBinding( false ); + } } + onFocusOutside={ () => { + setAddingBinding( false ); + } } + placement="bottom" + shift + className="block-bindings-ui-popover" + { ...props } + > + + + ) } + + + ); +}; + +function AttributesLayer( props ) { + const [ activeAttribute, setIsActiveAttribute ] = useState( false ); + const [ activeSource, setIsActiveSource ] = useState( false ); + return ( + + { blockBindingsWhitelist[ props.name ].map( ( attribute ) => ( +
+ + setIsActiveAttribute( + activeAttribute === attribute + ? false + : attribute + ) + } + className="block-bindings-attribute-picker-button" + > + { attribute } + + { activeAttribute === attribute && ( + <> + + { /* Sources can fill this slot */ } + + { ( fills ) => { + if ( ! fills.length ) { + return null; + } + + return ( + <> + { fills.map( + ( fill, index ) => { + // TODO: Check better way to get the source and label. + const source = + fill[ 0 ].props + .children.props + .source; + const sourceLabel = + fill[ 0 ].props + .children.props + .label; + const isSourceSelected = + activeSource === + source; + + return ( + + + setIsActiveSource( + isSourceSelected + ? false + : source + ) + } + className="block-bindings-source-picker-button" + > + { + sourceLabel + } + + { isSourceSelected && + fill } + + ); + } + ) } + + ); + } } + + + + + ) } +
+ ) ) } +
+ ); +} + +function RemoveBindingButton( props ) { + return ( + + ); +} + +if ( window.__experimentalBlockBindings ) { + addFilter( + 'blocks.registerBlockType', + 'core/block-bindings-ui', + ( settings, name ) => { + if ( ! ( name in blockBindingsWhitelist ) ) { + return settings; + } + + // TODO: Review the implications of this and the code. + // Add the necessary context to the block. + const contextItems = [ 'postId', 'postType', 'queryId' ]; + const usesContextArray = settings.usesContext; + const oldUsesContextArray = new Set( usesContextArray ); + contextItems.forEach( ( item ) => { + if ( ! oldUsesContextArray.has( item ) ) { + usesContextArray.push( item ); + } + } ); + settings.usesContext = usesContextArray; + + // Add bindings button to the block toolbar. + const OriginalComponent = settings.edit; + settings.edit = ( props ) => { + return ( + <> + + + + ); + }; + + return settings; + } + ); +} + +// TODO: Add also some components to the sidebar. diff --git a/packages/editor/src/components/block-bindings/index.js b/packages/editor/src/components/block-bindings/index.js new file mode 100644 index 0000000000000..9d23f55b601a9 --- /dev/null +++ b/packages/editor/src/components/block-bindings/index.js @@ -0,0 +1,4 @@ +/** + * Internal dependencies + */ +export { default as BlockBindingsFill } from './bindings-ui'; diff --git a/packages/editor/src/components/block-bindings/style.scss b/packages/editor/src/components/block-bindings/style.scss new file mode 100644 index 0000000000000..738c747988480 --- /dev/null +++ b/packages/editor/src/components/block-bindings/style.scss @@ -0,0 +1,30 @@ +// TODO: Change the styles. +.block-bindings-ui-popover { + margin-top: 12px; + width: 300px; + .components-popover__content { + width: 100%; + } + + .block-bindings-attribute-picker-container { + border-bottom: 1px solid #0002; + } + + .block-bindings-fields-list-ui { + padding: 12px; + li { + margin: 20px 8px; + cursor: pointer; + } + .selected-meta-field { + font-weight: bold; + } + .selected-meta-field::before { + content: "✔ "; + margin-left: -16px; + } + } + .block-bindings-remove-button { + color: var(--wp-admin-theme-color, #3858e9); + } +} diff --git a/packages/editor/src/components/index.js b/packages/editor/src/components/index.js index 33a18e6f9a6ad..94cf5c50169d0 100644 --- a/packages/editor/src/components/index.js +++ b/packages/editor/src/components/index.js @@ -102,3 +102,6 @@ export { default as EditorProvider } from './provider'; export * from './deprecated'; export const VisualEditorGlobalKeyboardShortcuts = EditorKeyboardShortcuts; export const TextEditorGlobalKeyboardShortcuts = EditorKeyboardShortcuts; + +// Block Bindings Components. +export * from './block-bindings'; diff --git a/packages/editor/src/style.scss b/packages/editor/src/style.scss index ff5a55a3881f9..78696d5f092de 100644 --- a/packages/editor/src/style.scss +++ b/packages/editor/src/style.scss @@ -1,4 +1,5 @@ @import "./components/autocompleters/style.scss"; +@import "./components/block-bindings/style.scss"; @import "./components/document-bar/style.scss"; @import "./components/document-outline/style.scss"; @import "./components/document-tools/style.scss"; From b1eac6b09a3642c76f54d57a36ed2382c0f535f2 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 11:52:33 +0100 Subject: [PATCH 04/12] Add BlockBindingsFieldsList component --- .../components/block-bindings/fields-list.js | 60 +++++++++++++++++++ .../src/components/block-bindings/index.js | 1 + 2 files changed, 61 insertions(+) create mode 100644 packages/editor/src/components/block-bindings/fields-list.js diff --git a/packages/editor/src/components/block-bindings/fields-list.js b/packages/editor/src/components/block-bindings/fields-list.js new file mode 100644 index 0000000000000..ba28797940b19 --- /dev/null +++ b/packages/editor/src/components/block-bindings/fields-list.js @@ -0,0 +1,60 @@ +/** + * WordPress dependencies + */ +import { updateBlockBindingsAttribute } from '@wordpress/block-editor'; +import { MenuItem, MenuGroup } from '@wordpress/components'; + +export default function BlockBindingsFieldsList( props ) { + const { + attributes, + setAttributes, + setIsActiveAttribute, + currentAttribute, + fields, + source, + setAddingBinding, + } = props; + + // TODO: Try to abstract this function to be reused across all the sources. + function selectItem( item ) { + // Modify the attribute we are binding. + // TODO: Not sure if we should do this. We might need to process the bindings attribute somehow in the editor to modify the content with context. + // TODO: Get the type from the block attribute definition and modify/validate the value returned by the source if needed. + const newAttributes = {}; + newAttributes[ currentAttribute ] = item.value; + setAttributes( newAttributes ); + + // Update the bindings property. + updateBlockBindingsAttribute( + attributes, + setAttributes, + currentAttribute, + source, + { value: item.key } + ); + + setIsActiveAttribute( false ); + setAddingBinding( false ); + } + + return ( + + { fields.map( ( item ) => ( + selectItem( item ) } + className={ + attributes.metadata?.bindings?.[ currentAttribute ] + ?.source?.name === source && + attributes.metadata?.bindings?.[ currentAttribute ] + ?.source?.attributes?.value === item.key + ? 'selected-meta-field' + : '' + } + > + { item.label } + + ) ) } + + ); +} diff --git a/packages/editor/src/components/block-bindings/index.js b/packages/editor/src/components/block-bindings/index.js index 9d23f55b601a9..600523bc9bbf7 100644 --- a/packages/editor/src/components/block-bindings/index.js +++ b/packages/editor/src/components/block-bindings/index.js @@ -2,3 +2,4 @@ * Internal dependencies */ export { default as BlockBindingsFill } from './bindings-ui'; +export { default as BlockBindingsFieldsList } from './fields-list'; From 6f4c75f2c39d3d083ca9f451768512b508dd806e Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 12:26:26 +0100 Subject: [PATCH 05/12] Add post meta source using Fill and component --- packages/editor/package.json | 2 +- .../hooks/block-bindings-sources/post-meta.js | 90 +++++++++++++++++++ packages/editor/src/hooks/index.js | 3 + 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 packages/editor/src/hooks/block-bindings-sources/post-meta.js diff --git a/packages/editor/package.json b/packages/editor/package.json index 63656899e587c..e138089b2b916 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -27,7 +27,7 @@ "sideEffects": [ "build-style/**", "src/**/*.scss", - "{src,build,build-module}/{index.js,store/index.js,hooks/**}" + "{src,build,build-module}/{index.js,store/index.js,hooks/**,hooks/block-bindings-sources/**}" ], "dependencies": { "@babel/runtime": "^7.16.0", diff --git a/packages/editor/src/hooks/block-bindings-sources/post-meta.js b/packages/editor/src/hooks/block-bindings-sources/post-meta.js new file mode 100644 index 0000000000000..90bf447f162fa --- /dev/null +++ b/packages/editor/src/hooks/block-bindings-sources/post-meta.js @@ -0,0 +1,90 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { addFilter } from '@wordpress/hooks'; +/** + * Internal dependencies + */ +import BlockBindingsFill from '../../components/block-bindings/bindings-ui'; +import BlockBindingsFieldsList from '../../components/block-bindings/fields-list'; + +const PostMeta = ( props ) => { + const { context } = props; + + // Fetching the REST API to get the available custom fields. + // TODO: Explore how it should work in templates. + // TODO: Explore if it makes sense to create a custom endpoint for this. + const data = useSelect( + ( select ) => { + const { getEntityRecord } = select( coreStore ); + return getEntityRecord( + 'postType', + context.postType, + context.postId + ); + }, + [ context.postType, context.postId ] + ); + + // Adapt the data to the format expected by the fields list. + const fields = []; + // Prettifying the name until we receive the label from the REST API endpoint. + const keyToLabel = ( key ) => { + return key + .split( '_' ) + .map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ) ) + .join( ' ' ); + }; + Object.entries( data.meta ).forEach( ( [ key, value ] ) => { + fields.push( { + key, + label: keyToLabel( key ), + value, + } ); + } ); + + return ( + + ); +}; + +if ( window.__experimentalBlockBindings ) { + // TODO: Read the context somehow to decide if we should add the source. + // const data = useSelect( editorStore ); + + // External sources could do something similar. + const withCoreSources = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { isSelected } = props; + return ( + <> + { isSelected && ( + <> + + + + + ) } + + + ); + }, + 'withToolbarControls' + ); + + addFilter( + 'editor.BlockEdit', + 'core/block-bindings-ui/add-sources', + withCoreSources + ); +} diff --git a/packages/editor/src/hooks/index.js b/packages/editor/src/hooks/index.js index 5a48ec1bf4956..8d52e5a36e663 100644 --- a/packages/editor/src/hooks/index.js +++ b/packages/editor/src/hooks/index.js @@ -4,3 +4,6 @@ import './custom-sources-backwards-compatibility'; import './default-autocompleters'; import './pattern-partial-syncing'; + +// Block bindings sources. +import './block-bindings-sources/post-meta'; From 076d2c395e93bc3611ad2b70f499ce2e73d4bb9c Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 15:25:25 +0100 Subject: [PATCH 06/12] Change variables names --- lib/experimental/blocks.php | 11 +++++++++-- .../src/components/block-bindings/bindings-ui.js | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index 42663e127870c..17b4c8c77525a 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -88,7 +88,6 @@ function wp_enqueue_block_view_script( $block_name, $args ) { ) ) { require_once __DIR__ . '/block-bindings/index.php'; - // Allowed blocks that support block bindings. // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? global $block_bindings_allowed_blocks; $block_bindings_allowed_blocks = array( @@ -127,7 +126,15 @@ function process_block_bindings( $block_content, $block, $block_instance ) { // } // } // } - // + // }, + // "url": { + // "source": { + // "name": "post_meta", + // "attributes": { "value": "text_custom_field" } + // } + // } + // }, + // . global $block_bindings_allowed_blocks; global $block_bindings_sources; $modified_block_content = $block_content; diff --git a/packages/editor/src/components/block-bindings/bindings-ui.js b/packages/editor/src/components/block-bindings/bindings-ui.js index 3827deae790c4..c9cffde8eba9f 100644 --- a/packages/editor/src/components/block-bindings/bindings-ui.js +++ b/packages/editor/src/components/block-bindings/bindings-ui.js @@ -20,7 +20,7 @@ import { } from '@wordpress/icons'; import { addFilter } from '@wordpress/hooks'; -const blockBindingsWhitelist = { +const blockBindingsAllowedBlocks = { 'core/paragraph': [ 'content' ], 'core/heading': [ 'content' ], 'core/image': [ 'url', 'title' ], @@ -93,7 +93,7 @@ function AttributesLayer( props ) { const [ activeSource, setIsActiveSource ] = useState( false ); return ( - { blockBindingsWhitelist[ props.name ].map( ( attribute ) => ( + { blockBindingsAllowedBlocks[ props.name ].map( ( attribute ) => (
{ - if ( ! ( name in blockBindingsWhitelist ) ) { + if ( ! ( name in blockBindingsAllowedBlocks ) ) { return settings; } From 7fe22868d84feb63bb5007bf8e9c0cf1c742739c Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 16:49:41 +0100 Subject: [PATCH 07/12] Get context from "post meta" source --- .../components/block-bindings/bindings-ui.js | 12 -- .../hooks/block-bindings-sources/post-meta.js | 109 ++++++++++-------- 2 files changed, 59 insertions(+), 62 deletions(-) diff --git a/packages/editor/src/components/block-bindings/bindings-ui.js b/packages/editor/src/components/block-bindings/bindings-ui.js index c9cffde8eba9f..0e1ce6fc87e5f 100644 --- a/packages/editor/src/components/block-bindings/bindings-ui.js +++ b/packages/editor/src/components/block-bindings/bindings-ui.js @@ -243,18 +243,6 @@ if ( window.__experimentalBlockBindings ) { return settings; } - // TODO: Review the implications of this and the code. - // Add the necessary context to the block. - const contextItems = [ 'postId', 'postType', 'queryId' ]; - const usesContextArray = settings.usesContext; - const oldUsesContextArray = new Set( usesContextArray ); - contextItems.forEach( ( item ) => { - if ( ! oldUsesContextArray.has( item ) ) { - usesContextArray.push( item ); - } - } ); - settings.usesContext = usesContextArray; - // Add bindings button to the block toolbar. const OriginalComponent = settings.edit; settings.edit = ( props ) => { diff --git a/packages/editor/src/hooks/block-bindings-sources/post-meta.js b/packages/editor/src/hooks/block-bindings-sources/post-meta.js index 90bf447f162fa..c9458ae6f7e0c 100644 --- a/packages/editor/src/hooks/block-bindings-sources/post-meta.js +++ b/packages/editor/src/hooks/block-bindings-sources/post-meta.js @@ -10,68 +10,77 @@ import { addFilter } from '@wordpress/hooks'; */ import BlockBindingsFill from '../../components/block-bindings/bindings-ui'; import BlockBindingsFieldsList from '../../components/block-bindings/fields-list'; - -const PostMeta = ( props ) => { - const { context } = props; - - // Fetching the REST API to get the available custom fields. - // TODO: Explore how it should work in templates. - // TODO: Explore if it makes sense to create a custom endpoint for this. - const data = useSelect( - ( select ) => { - const { getEntityRecord } = select( coreStore ); - return getEntityRecord( - 'postType', - context.postType, - context.postId - ); - }, - [ context.postType, context.postId ] - ); - - // Adapt the data to the format expected by the fields list. - const fields = []; - // Prettifying the name until we receive the label from the REST API endpoint. - const keyToLabel = ( key ) => { - return key - .split( '_' ) - .map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ) ) - .join( ' ' ); - }; - Object.entries( data.meta ).forEach( ( [ key, value ] ) => { - fields.push( { - key, - label: keyToLabel( key ), - value, - } ); - } ); - - return ( - - ); -}; +import { store as editorStore } from '../../store'; if ( window.__experimentalBlockBindings ) { - // TODO: Read the context somehow to decide if we should add the source. - // const data = useSelect( editorStore ); - // External sources could do something similar. + const withCoreSources = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const { isSelected } = props; + const blockBindingsAllowedBlocks = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'url', 'title' ], + 'core/button': [ 'url', 'text' ], + }; + const { name, isSelected, context } = props; + // If the block is not allowed, return the original BlockEdit. + if ( ! blockBindingsAllowedBlocks[ name ] ) { + return ; + } + const fields = []; + if ( isSelected ) { + const data = useSelect( ( select ) => { + const postId = context.postId + ? context.postId + : select( editorStore ).getCurrentPostId(); + const postType = context.postType + ? context.postType + : select( editorStore ).getCurrentPostType(); + // If not a post type, return null. + if ( postType !== 'post' && postType !== 'page' ) { + return null; + } + const { getEntityRecord } = select( coreStore ); + return getEntityRecord( 'postType', postType, postId ); + }, [] ); + + if ( data ) { + // Adapt the data to the format expected by the fields list. + // Prettifying the name until we receive the label from the REST API endpoint. + const keyToLabel = ( key ) => { + return key + .split( '_' ) + .map( + ( word ) => + word.charAt( 0 ).toUpperCase() + + word.slice( 1 ) + ) + .join( ' ' ); + }; + Object.entries( data.meta ).forEach( ( [ key, value ] ) => { + fields.push( { + key, + label: keyToLabel( key ), + value, + } ); + } ); + } + } + return ( <> - { isSelected && ( + { isSelected && fields.length !== 0 && ( <> - + ) } From ad8115a22410fc77f4b0005da82f4c239a751319 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 20 Dec 2023 17:26:15 +0100 Subject: [PATCH 08/12] Create global `BLOCK_BINDINGS_ALLOWED_BLOCKS` constant --- .../components/block-bindings/bindings-ui.js | 215 +++++++++--------- .../hooks/block-bindings-sources/post-meta.js | 9 +- packages/editor/src/store/constants.js | 6 + 3 files changed, 118 insertions(+), 112 deletions(-) diff --git a/packages/editor/src/components/block-bindings/bindings-ui.js b/packages/editor/src/components/block-bindings/bindings-ui.js index 0e1ce6fc87e5f..b7677a1d4a8dd 100644 --- a/packages/editor/src/components/block-bindings/bindings-ui.js +++ b/packages/editor/src/components/block-bindings/bindings-ui.js @@ -19,13 +19,10 @@ import { chevronUp, } from '@wordpress/icons'; import { addFilter } from '@wordpress/hooks'; - -const blockBindingsAllowedBlocks = { - 'core/paragraph': [ 'content' ], - 'core/heading': [ 'content' ], - 'core/image': [ 'url', 'title' ], - 'core/button': [ 'url', 'text' ], -}; +/** + * Internal dependencies + */ +import { BLOCK_BINDINGS_ALLOWED_BLOCKS } from '../../store/constants'; const { Slot, Fill } = createSlotFill( 'BlockBindingsUI' ); @@ -93,108 +90,116 @@ function AttributesLayer( props ) { const [ activeSource, setIsActiveSource ] = useState( false ); return ( - { blockBindingsAllowedBlocks[ props.name ].map( ( attribute ) => ( -
- - setIsActiveAttribute( - activeAttribute === attribute - ? false - : attribute - ) - } - className="block-bindings-attribute-picker-button" + { BLOCK_BINDINGS_ALLOWED_BLOCKS[ props.name ].map( + ( attribute ) => ( +
- { attribute } - - { activeAttribute === attribute && ( - <> - - { /* Sources can fill this slot */ } - - { ( fills ) => { - if ( ! fills.length ) { - return null; - } + + setIsActiveAttribute( + activeAttribute === attribute + ? false + : attribute + ) + } + className="block-bindings-attribute-picker-button" + > + { attribute } + + { activeAttribute === attribute && ( + <> + + { /* Sources can fill this slot */ } + + { ( fills ) => { + if ( ! fills.length ) { + return null; + } - return ( - <> - { fills.map( - ( fill, index ) => { - // TODO: Check better way to get the source and label. - const source = - fill[ 0 ].props - .children.props - .source; - const sourceLabel = - fill[ 0 ].props - .children.props - .label; - const isSourceSelected = - activeSource === - source; + return ( + <> + { fills.map( + ( fill, index ) => { + // TODO: Check better way to get the source and label. + const source = + fill[ 0 ].props + .children + .props + .source; + const sourceLabel = + fill[ 0 ].props + .children + .props + .label; + const isSourceSelected = + activeSource === + source; - return ( - - - setIsActiveSource( - isSourceSelected - ? false - : source - ) - } - className="block-bindings-source-picker-button" > - { - sourceLabel - } - - { isSourceSelected && - fill } - - ); - } - ) } - - ); - } } - - - - - ) } -
- ) ) } + + setIsActiveSource( + isSourceSelected + ? false + : source + ) + } + className="block-bindings-source-picker-button" + > + { + sourceLabel + } + + { isSourceSelected && + fill } + + ); + } + ) } + + ); + } } + + + + + ) } +
+ ) + ) }
); } @@ -239,7 +244,7 @@ if ( window.__experimentalBlockBindings ) { 'blocks.registerBlockType', 'core/block-bindings-ui', ( settings, name ) => { - if ( ! ( name in blockBindingsAllowedBlocks ) ) { + if ( ! ( name in BLOCK_BINDINGS_ALLOWED_BLOCKS ) ) { return settings; } diff --git a/packages/editor/src/hooks/block-bindings-sources/post-meta.js b/packages/editor/src/hooks/block-bindings-sources/post-meta.js index c9458ae6f7e0c..bbc1941e03b59 100644 --- a/packages/editor/src/hooks/block-bindings-sources/post-meta.js +++ b/packages/editor/src/hooks/block-bindings-sources/post-meta.js @@ -11,21 +11,16 @@ import { addFilter } from '@wordpress/hooks'; import BlockBindingsFill from '../../components/block-bindings/bindings-ui'; import BlockBindingsFieldsList from '../../components/block-bindings/fields-list'; import { store as editorStore } from '../../store'; +import { BLOCK_BINDINGS_ALLOWED_BLOCKS } from '../../store/constants'; if ( window.__experimentalBlockBindings ) { // External sources could do something similar. const withCoreSources = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const blockBindingsAllowedBlocks = { - 'core/paragraph': [ 'content' ], - 'core/heading': [ 'content' ], - 'core/image': [ 'url', 'title' ], - 'core/button': [ 'url', 'text' ], - }; const { name, isSelected, context } = props; // If the block is not allowed, return the original BlockEdit. - if ( ! blockBindingsAllowedBlocks[ name ] ) { + if ( ! BLOCK_BINDINGS_ALLOWED_BLOCKS[ name ] ) { return ; } const fields = []; diff --git a/packages/editor/src/store/constants.js b/packages/editor/src/store/constants.js index 7882ba53e64db..c8d1c71f56abe 100644 --- a/packages/editor/src/store/constants.js +++ b/packages/editor/src/store/constants.js @@ -18,3 +18,9 @@ export const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; export const PERMALINK_POSTNAME_REGEX = /%(?:postname|pagename)%/; export const ONE_MINUTE_IN_MS = 60 * 1000; export const AUTOSAVE_PROPERTIES = [ 'title', 'excerpt', 'content' ]; +export const BLOCK_BINDINGS_ALLOWED_BLOCKS = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'url', 'title' ], + 'core/button': [ 'url', 'text' ], +}; From 3eb50844888c310a867008c1a91a39f07c1e7f53 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Thu, 21 Dec 2023 08:57:18 +0100 Subject: [PATCH 09/12] Add context back until we find a proper way --- .../components/block-bindings/bindings-ui.js | 12 ++++ .../hooks/block-bindings-sources/post-meta.js | 71 ++++++++++--------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/packages/editor/src/components/block-bindings/bindings-ui.js b/packages/editor/src/components/block-bindings/bindings-ui.js index b7677a1d4a8dd..bd6ecf6cd3b5b 100644 --- a/packages/editor/src/components/block-bindings/bindings-ui.js +++ b/packages/editor/src/components/block-bindings/bindings-ui.js @@ -248,6 +248,18 @@ if ( window.__experimentalBlockBindings ) { return settings; } + // TODO: Review the implications of this and the code. + // Add the necessary context to the block. + const contextItems = [ 'postId', 'postType', 'queryId' ]; + const usesContextArray = settings.usesContext; + const oldUsesContextArray = new Set( usesContextArray ); + contextItems.forEach( ( item ) => { + if ( ! oldUsesContextArray.has( item ) ) { + usesContextArray.push( item ); + } + } ); + settings.usesContext = usesContextArray; + // Add bindings button to the block toolbar. const OriginalComponent = settings.edit; settings.edit = ( props ) => { diff --git a/packages/editor/src/hooks/block-bindings-sources/post-meta.js b/packages/editor/src/hooks/block-bindings-sources/post-meta.js index bbc1941e03b59..c01ea97b1604b 100644 --- a/packages/editor/src/hooks/block-bindings-sources/post-meta.js +++ b/packages/editor/src/hooks/block-bindings-sources/post-meta.js @@ -25,42 +25,43 @@ if ( window.__experimentalBlockBindings ) { } const fields = []; if ( isSelected ) { - const data = useSelect( ( select ) => { - const postId = context.postId - ? context.postId - : select( editorStore ).getCurrentPostId(); - const postType = context.postType - ? context.postType - : select( editorStore ).getCurrentPostType(); - // If not a post type, return null. - if ( postType !== 'post' && postType !== 'page' ) { - return null; - } - const { getEntityRecord } = select( coreStore ); - return getEntityRecord( 'postType', postType, postId ); - }, [] ); + const data = useSelect( + ( select ) => { + const postId = context.postId + ? context.postId + : select( editorStore ).getCurrentPostId(); + const postType = context.postType + ? context.postType + : select( editorStore ).getCurrentPostType(); + const { getEntityRecord } = select( coreStore ); + return getEntityRecord( 'postType', postType, postId ); + }, + [ context.postId, context.postType ] + ); - if ( data ) { - // Adapt the data to the format expected by the fields list. - // Prettifying the name until we receive the label from the REST API endpoint. - const keyToLabel = ( key ) => { - return key - .split( '_' ) - .map( - ( word ) => - word.charAt( 0 ).toUpperCase() + - word.slice( 1 ) - ) - .join( ' ' ); - }; - Object.entries( data.meta ).forEach( ( [ key, value ] ) => { - fields.push( { - key, - label: keyToLabel( key ), - value, - } ); - } ); + // TODO: Explore how to get the list of available fields depending on the template. + if ( ! data || ! data.meta ) { + return ; } + + // Adapt the data to the format expected by the fields list. + // Prettifying the name until we receive the label from the REST API endpoint. + const keyToLabel = ( key ) => { + return key + .split( '_' ) + .map( + ( word ) => + word.charAt( 0 ).toUpperCase() + word.slice( 1 ) + ) + .join( ' ' ); + }; + Object.entries( data.meta ).forEach( ( [ key, value ] ) => { + fields.push( { + key, + label: keyToLabel( key ), + value, + } ); + } ); } return ( @@ -86,6 +87,8 @@ if ( window.__experimentalBlockBindings ) { 'withToolbarControls' ); + // TODO: Review if there is a better filter for this. + // This runs for every block. addFilter( 'editor.BlockEdit', 'core/block-bindings-ui/add-sources', From 934715ba5d80f81c58ca2a3a7942a72c386a1753 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Fri, 22 Dec 2023 17:37:38 +0100 Subject: [PATCH 10/12] Remove some comments --- packages/editor/src/components/block-bindings/fields-list.js | 4 +--- packages/editor/src/hooks/block-bindings-sources/post-meta.js | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/editor/src/components/block-bindings/fields-list.js b/packages/editor/src/components/block-bindings/fields-list.js index ba28797940b19..f681467126f5c 100644 --- a/packages/editor/src/components/block-bindings/fields-list.js +++ b/packages/editor/src/components/block-bindings/fields-list.js @@ -17,9 +17,7 @@ export default function BlockBindingsFieldsList( props ) { // TODO: Try to abstract this function to be reused across all the sources. function selectItem( item ) { - // Modify the attribute we are binding. - // TODO: Not sure if we should do this. We might need to process the bindings attribute somehow in the editor to modify the content with context. - // TODO: Get the type from the block attribute definition and modify/validate the value returned by the source if needed. + // Modify the attribute binded. const newAttributes = {}; newAttributes[ currentAttribute ] = item.value; setAttributes( newAttributes ); diff --git a/packages/editor/src/hooks/block-bindings-sources/post-meta.js b/packages/editor/src/hooks/block-bindings-sources/post-meta.js index c01ea97b1604b..a02c71021e93a 100644 --- a/packages/editor/src/hooks/block-bindings-sources/post-meta.js +++ b/packages/editor/src/hooks/block-bindings-sources/post-meta.js @@ -39,7 +39,6 @@ if ( window.__experimentalBlockBindings ) { [ context.postId, context.postType ] ); - // TODO: Explore how to get the list of available fields depending on the template. if ( ! data || ! data.meta ) { return ; } From 4607504dc46af725ebd719884e9fef668d1d374f Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 11 Jan 2024 19:44:42 +0000 Subject: [PATCH 11/12] Fix some misaligned comments after the rebase --- lib/experimental/blocks.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/experimental/blocks.php b/lib/experimental/blocks.php index 17b4c8c77525a..16248651d1c14 100644 --- a/lib/experimental/blocks.php +++ b/lib/experimental/blocks.php @@ -88,6 +88,7 @@ function wp_enqueue_block_view_script( $block_name, $args ) { ) ) { require_once __DIR__ . '/block-bindings/index.php'; + // Allowed blocks that support block bindings. // TODO: Look for a mechanism to opt-in for this. Maybe adding a property to block attributes? global $block_bindings_allowed_blocks; $block_bindings_allowed_blocks = array( @@ -126,15 +127,7 @@ function process_block_bindings( $block_content, $block, $block_instance ) { // } // } // } - // }, - // "url": { - // "source": { - // "name": "post_meta", - // "attributes": { "value": "text_custom_field" } - // } - // } - // }, - // . + // global $block_bindings_allowed_blocks; global $block_bindings_sources; $modified_block_content = $block_content; From c3c5d18a718c6bf439e58d015c45f9c7679f6871 Mon Sep 17 00:00:00 2001 From: Michal Czaplinski Date: Thu, 11 Jan 2024 20:00:57 +0000 Subject: [PATCH 12/12] Add a `return` --- packages/patterns/src/components/partial-syncing-controls.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/patterns/src/components/partial-syncing-controls.js b/packages/patterns/src/components/partial-syncing-controls.js index 6da7cfccd735f..b7a50235cf5d3 100644 --- a/packages/patterns/src/components/partial-syncing-controls.js +++ b/packages/patterns/src/components/partial-syncing-controls.js @@ -76,6 +76,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { id, }, } ); + return; } }