From 2f071ad6187f61f22e64c7a15f55e9928e88c915 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jan 2024 15:56:44 +0100 Subject: [PATCH 01/26] Add actions and selectors to register new sources --- .../block-editor/src/store/private-actions.js | 10 ++++++++++ .../block-editor/src/store/private-selectors.js | 8 ++++++++ packages/block-editor/src/store/reducer.js | 16 ++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index a31455a0b7e7b..81c66b8303136 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -360,3 +360,13 @@ export function stopEditingAsBlocks( clientId ) { dispatch.__unstableSetTemporarilyEditingAsBlocks(); }; } + +export function registerBlockBindingsSource( source ) { + return { + type: 'REGISTER_BLOCK_BINDINGS_SOURCE', + sourceName: source.name, + sourceLabel: source.label, + sourceComponent: source.component, + useSource: source.useSource, + }; +} diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index e8230eea89daa..863207e0290d9 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -286,3 +286,11 @@ export const hasAllowedPatterns = createSelector( export function getLastFocus( state ) { return state.lastFocus; } + +export function getAllBlockBindingsSources( state ) { + return state.blockBindingsSources; +} + +export function getBlockBindingsSource( state, sourceName ) { + return state?.blockBindingsSources?.[ sourceName ]; +} diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index fa6c8942e66ad..f0fa02b83353c 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2017,6 +2017,21 @@ export function lastFocus( state = false, action ) { return state; } +function blockBindingsSources( state = {}, action ) { + if ( action.type === 'REGISTER_BLOCK_BINDINGS_SOURCE' ) { + return { + ...state, + [ action.sourceName ]: { + label: action.sourceLabel, + component: action.sourceComponent, + useSource: action.useSource, + }, + }; + } + + return state; +} + const combinedReducers = combineReducers( { blocks, isTyping, @@ -2047,6 +2062,7 @@ const combinedReducers = combineReducers( { blockRemovalRules, openedBlockSettingsMenu, registeredInserterMediaCategories, + blockBindingsSources, } ); function withAutomaticChangeReset( reducer ) { From 57ab9b2bc3eff18e679edb0fb32b6d6817d1764f Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jan 2024 15:58:49 +0100 Subject: [PATCH 02/26] Add hook to read the bindings attribute in Edit --- packages/block-editor/src/hooks/index.js | 1 + .../src/hooks/use-bindings-attributes.js | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 packages/block-editor/src/hooks/use-bindings-attributes.js diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index f17c0a22166e4..d6eeda197a621 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -27,6 +27,7 @@ import contentLockUI from './content-lock-ui'; import './metadata'; import blockHooks from './block-hooks'; import blockRenaming from './block-renaming'; +import './use-bindings-attributes'; createBlockEditFilter( [ diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js new file mode 100644 index 0000000000000..ee80b9e515242 --- /dev/null +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -0,0 +1,123 @@ +/** + * WordPress dependencies + */ +import { createHigherOrderComponent } from '@wordpress/compose'; +import { useRegistry, useSelect } from '@wordpress/data'; +import { addFilter } from '@wordpress/hooks'; +/** + * Internal dependencies + */ +import { store as blockEditorStore } from '../store'; +import { useBlockEditContext } from '../components/block-edit/context'; +import { unlock } from '../lock-unlock'; + +/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */ +/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */ + +/** + * Given a binding of block attributes, returns a higher order component that + * overrides its `attributes` and `setAttributes` props to sync any changes needed. + * + * @return {WPHigherOrderComponent} Higher-order component. + */ + +const BLOCK_BINDINGS_ALLOWED_BLOCKS = { + 'core/paragraph': [ 'content' ], + 'core/heading': [ 'content' ], + 'core/image': [ 'url', 'title', 'alt' ], + 'core/button': [ 'url', 'text' ], +}; + +const createEditFunctionWithBindingsAttribute = () => + createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { clientId } = useBlockEditContext(); + + const { + getBlockBindingsSource, + getBlockAttributes, + updateBlockAttributes, + } = useSelect( ( select ) => { + return { + getBlockBindingsSource: unlock( select( blockEditorStore ) ) + .getBlockBindingsSource, + getBlockAttributes: + select( blockEditorStore ).getBlockAttributes, + updateBlockAttributes: + select( blockEditorStore ).updateBlockAttributes, + }; + }, [] ); + + const updatedAttributes = getBlockAttributes( clientId ); + if ( updatedAttributes?.metadata?.bindings ) { + Object.entries( updatedAttributes.metadata.bindings ).forEach( + ( [ attributeName, settings ] ) => { + const source = getBlockBindingsSource( + settings.source.name + ); + + if ( source ) { + // Second argument (`updateMetaValue`) will be used to update the value in the future. + const { + placeholder, + useValue: [ metaValue = null ] = [], + } = source.useSource( + props, + settings.source.attributes + ); + + if ( placeholder ) { + updatedAttributes.placeholder = placeholder; + updatedAttributes[ attributeName ] = null; + } + + if ( metaValue ) { + updatedAttributes[ attributeName ] = metaValue; + } + } + } + ); + } + + const registry = useRegistry(); + + return ( + <> + + registry.batch( () => + updateBlockAttributes( blockId, newAttributes ) + ) + } + { ...props } + /> + + ); + }, + 'useBoundAttributes' + ); + +/** + * Filters a registered block's settings to enhance a block's `edit` component + * to upgrade bound attributes. + * + * @param {WPBlockSettings} settings Registered block settings. + * + * @return {WPBlockSettings} Filtered block settings. + */ +function shimAttributeSource( settings ) { + if ( ! ( settings.name in BLOCK_BINDINGS_ALLOWED_BLOCKS ) ) { + return settings; + } + settings.edit = createEditFunctionWithBindingsAttribute()( settings.edit ); + + return settings; +} + +addFilter( + 'blocks.registerBlockType', + 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', + shimAttributeSource +); From 445405f4fe796edd0714012f86ac25f293e23312 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jan 2024 16:02:21 +0100 Subject: [PATCH 03/26] Add context to all the blocks with bindings --- .../src/hooks/use-bindings-attributes.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index ee80b9e515242..c00303c421f59 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -121,3 +121,24 @@ addFilter( 'core/editor/custom-sources-backwards-compatibility/shim-attribute-source', shimAttributeSource ); + +// Add the context to all blocks. +addFilter( + 'blocks.registerBlockType', + 'core/block-bindings-ui', + ( settings, name ) => { + if ( ! ( name in BLOCK_BINDINGS_ALLOWED_BLOCKS ) ) { + return settings; + } + 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; + return settings; + } +); From af7f8707f9874cf70544708167b2d00439df39e6 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jan 2024 16:04:36 +0100 Subject: [PATCH 04/26] Lock rich text when `isContentBound` is true --- packages/block-editor/src/components/rich-text/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 51a70677a5edc..83d8aac3386d4 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -376,7 +376,7 @@ export function RichTextWrapper( useFirefoxCompat(), anchorRef, ] ) } - contentEditable={ true } + contentEditable={ props.isContentBound ? false : true } suppressContentEditableWarning={ true } className={ classnames( 'block-editor-rich-text__editable', From c3567a489186e8a28cc55e7cacb5567be424e693 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jan 2024 16:05:24 +0100 Subject: [PATCH 05/26] Adapt paragraph and heading blocks UI --- packages/block-library/src/heading/edit.js | 4 +++- packages/block-library/src/paragraph/edit.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index e0e36a831429b..bd07a4cf3edbd 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -33,7 +33,8 @@ function HeadingEdit( { style, clientId, } ) { - const { textAlign, content, level, placeholder, anchor } = attributes; + const { textAlign, content, level, placeholder, anchor, metadata } = + attributes; const tagName = 'h' + level; const blockProps = useBlockProps( { className: classnames( { @@ -138,6 +139,7 @@ function HeadingEdit( { onRemove={ () => onReplace( [] ) } placeholder={ placeholder || __( 'Heading' ) } textAlign={ textAlign } + isContentBound={ metadata?.bindings?.content } { ...( Platform.isNative && { deleteEnter: true } ) } // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side { ...blockProps } /> diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index cecfdce474c51..830ddb141a376 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -100,7 +100,8 @@ function ParagraphBlock( { setAttributes, clientId, } ) { - const { align, content, direction, dropCap, placeholder } = attributes; + const { align, content, direction, dropCap, placeholder, metadata } = + attributes; const blockProps = useBlockProps( { ref: useOnEnter( { clientId, content } ), className: classnames( { @@ -180,6 +181,7 @@ function ParagraphBlock( { data-empty={ RichText.isEmpty( content ) } placeholder={ placeholder || __( 'Type / to choose a block' ) } data-custom-placeholder={ placeholder ? true : undefined } + isContentBound={ metadata?.bindings?.content } __unstableEmbedURLOnPaste __unstableAllowPrefixTransformations /> From 35c8c6460e1ffefff9487eb4a6dd1c87f21af206 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jan 2024 16:07:20 +0100 Subject: [PATCH 06/26] Adapt button block UI --- packages/block-library/src/button/edit.js | 79 ++++++++++++----------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index b46e145d760ad..28fbf711dfc11 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -164,6 +164,7 @@ function ButtonEdit( props ) { text, url, width, + metadata, } = attributes; const TagName = tagName || 'a'; @@ -276,6 +277,7 @@ function ButtonEdit( props ) { onReplace={ onReplace } onMerge={ mergeBlocks } identifier="text" + isContentBound={ metadata?.bindings?.text } /> @@ -287,7 +289,7 @@ function ButtonEdit( props ) { } } /> ) } - { ! isURLSet && isLinkTag && ( + { ! isURLSet && isLinkTag && ! metadata?.bindings?.url && ( ) } - { isURLSet && isLinkTag && ( + { isURLSet && isLinkTag && ! metadata?.bindings?.url && ( ) } - { isLinkTag && isSelected && ( isEditingURL || isURLSet ) && ( - { - setIsEditingURL( false ); - richTextRef.current?.focus(); - } } - anchor={ popoverAnchor } - focusOnMount={ isEditingURL ? 'firstElement' : false } - __unstableSlotName={ '__unstable-block-tools-after' } - shift - > - - setAttributes( - getUpdatedLinkAttributes( { - rel, - url: newURL, - opensInNewTab: newOpensInNewTab, - nofollow: newNofollow, - } ) - ) - } - onRemove={ () => { - unlink(); + { isLinkTag && + isSelected && + ( isEditingURL || isURLSet ) && + ! metadata?.bindings?.url && ( + { + setIsEditingURL( false ); richTextRef.current?.focus(); } } - forceIsEditingLink={ isEditingURL } - settings={ LINK_SETTINGS } - /> - - ) } + anchor={ popoverAnchor } + focusOnMount={ isEditingURL ? 'firstElement' : false } + __unstableSlotName={ '__unstable-block-tools-after' } + shift + > + + setAttributes( + getUpdatedLinkAttributes( { + rel, + url: newURL, + opensInNewTab: newOpensInNewTab, + nofollow: newNofollow, + } ) + ) + } + onRemove={ () => { + unlink(); + richTextRef.current?.focus(); + } } + forceIsEditingLink={ isEditingURL } + settings={ LINK_SETTINGS } + /> + + ) } Date: Mon, 22 Jan 2024 16:29:35 +0100 Subject: [PATCH 07/26] Adapt image block UI --- packages/block-library/src/image/edit.js | 25 +++-- packages/block-library/src/image/editor.scss | 7 +- packages/block-library/src/image/image.js | 105 ++++++++++++------- 3 files changed, 90 insertions(+), 47 deletions(-) diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js index d189af32efcbe..723c8c27508de 100644 --- a/packages/block-library/src/image/edit.js +++ b/packages/block-library/src/image/edit.js @@ -19,7 +19,7 @@ import { } from '@wordpress/block-editor'; import { useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { image as icon } from '@wordpress/icons'; +import { image as icon, plugins as pluginsIcon } from '@wordpress/icons'; import { store as noticesStore } from '@wordpress/notices'; /** @@ -111,6 +111,7 @@ export function ImageEdit( { aspectRatio, scale, align, + metadata, } = attributes; const [ temporaryURL, setTemporaryURL ] = useState(); @@ -332,6 +333,7 @@ export function ImageEdit( { } ); // Much of this description is duplicated from MediaPlaceholder. + const isUrlAttributeConnected = !! metadata?.bindings?.url; const placeholder = ( content ) => { return ( - { content } + { isUrlAttributeConnected ? ( + + { __( 'Connected to a custom field' ) } + + ) : ( + content + ) } ); }; diff --git a/packages/block-library/src/image/editor.scss b/packages/block-library/src/image/editor.scss index 934682ed91b7d..ded3768dfa7d3 100644 --- a/packages/block-library/src/image/editor.scss +++ b/packages/block-library/src/image/editor.scss @@ -27,6 +27,12 @@ opacity: 0; } } + .block-bindings-media-placeholder-message { + opacity: 0; + } + &.is-selected .block-bindings-media-placeholder-message { + opacity: 1; + } // Remove the transition while we still have a legacy placeholder style. // Otherwise the content jumps between the 1px placeholder border, and any inherited custom @@ -38,7 +44,6 @@ } } - figure.wp-block-image:not(.wp-block) { margin: 0; } diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index d8788fde4844f..0a2fd431d7882 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -124,6 +124,7 @@ export default function Image( { linkTarget, sizeSlug, lightbox, + metadata, } = attributes; // The only supported unit is px, so we can parseInt to strip the px here. @@ -410,21 +411,27 @@ export default function Image( { ); + const isUrlAttributeConnected = !! metadata?.bindings?.url; + const isAltAttributeConnected = !! metadata?.bindings?.alt; + const isTitleAttributeConnected = !! metadata?.bindings?.title; + const controls = ( <> - { ! multiImageSelection && ! isEditingImage && ( - - ) } + { ! multiImageSelection && + ! isEditingImage && + ! isUrlAttributeConnected && ( + + ) } { allowCrop && ( setIsEditingImage( true ) } @@ -440,19 +447,21 @@ export default function Image( { /> ) } - { ! multiImageSelection && ! isEditingImage && ( - - - - ) } + { ! multiImageSelection && + ! isEditingImage && + ! isUrlAttributeConnected && ( + + + + ) } { ! multiImageSelection && externalBlob && ( @@ -483,16 +492,27 @@ export default function Image( { label={ __( 'Alternative text' ) } value={ alt || '' } onChange={ updateAlt } + disabled={ isAltAttributeConnected } help={ - <> - + isAltAttributeConnected ? ( + <> + { __( + 'Connected to a custom field' + ) } + + ) : ( + <> + + { __( + 'Describe the purpose of the image.' + ) } + +
{ __( - 'Describe the purpose of the image.' + 'Leave empty if decorative.' ) } -
-
- { __( 'Leave empty if decorative.' ) } - + + ) } __nextHasNoMarginBottom /> @@ -542,17 +562,22 @@ export default function Image( { label={ __( 'Title attribute' ) } value={ title || '' } onChange={ onSetTitle } + disabled={ isTitleAttributeConnected } help={ - <> - { __( - 'Describe the role of this image on the page.' - ) } - + isTitleAttributeConnected ? ( + <>{ __( 'Connected to a custom field' ) } + ) : ( + <> { __( - '(Note: many devices and browsers do not display this text.)' + 'Describe the role of this image on the page.' ) } - - + + { __( + '(Note: many devices and browsers do not display this text.)' + ) } + + + ) } /> From a4dc34a9eaa717c953b0393c82398ec28e9c62c7 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Mon, 22 Jan 2024 16:30:44 +0100 Subject: [PATCH 08/26] Register post meta source --- packages/editor/package.json | 2 +- packages/editor/src/bindings/index.js | 13 +++++++ packages/editor/src/bindings/post-meta.js | 47 +++++++++++++++++++++++ packages/editor/src/index.js | 1 + 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 packages/editor/src/bindings/index.js create mode 100644 packages/editor/src/bindings/post-meta.js diff --git a/packages/editor/package.json b/packages/editor/package.json index b974c7443851f..63c81cbd5cc7f 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/**,bindings/**}" ], "dependencies": { "@babel/runtime": "^7.16.0", diff --git a/packages/editor/src/bindings/index.js b/packages/editor/src/bindings/index.js new file mode 100644 index 0000000000000..8a883e8904a71 --- /dev/null +++ b/packages/editor/src/bindings/index.js @@ -0,0 +1,13 @@ +/** + * WordPress dependencies + */ +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { dispatch } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { unlock } from '../lock-unlock'; +import postMeta from './post-meta'; + +const { registerBlockBindingsSource } = unlock( dispatch( blockEditorStore ) ); +registerBlockBindingsSource( postMeta ); diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js new file mode 100644 index 0000000000000..3e1824e9ef102 --- /dev/null +++ b/packages/editor/src/bindings/post-meta.js @@ -0,0 +1,47 @@ +/** + * WordPress dependencies + */ +import { useEntityProp } from '@wordpress/core-data'; +import { select } from '@wordpress/data'; +/** + * Internal dependencies + */ +import { store as editorStore } from '../store'; + +const { getCurrentPostType } = select( editorStore ); + +// Prettify the name until the label is available in the REST API endpoint. +const keyToLabel = ( key ) => { + return key + .split( '_' ) + .map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ) ) + .join( ' ' ); +}; + +export default { + name: 'post_meta', + label: 'Post Meta', + component: null, + useSource( props, sourceAttributes ) { + const { context } = props; + const { value: metaKey } = sourceAttributes; + const postType = context.postType + ? context.postType + : getCurrentPostType(); + const [ meta, setMeta ] = useEntityProp( + 'postType', + context.postType, + 'meta', + context.postId + ); + + if ( postType === 'wp_template' ) { + return { placeholder: keyToLabel( metaKey ) }; + } + const metaValue = meta[ metaKey ]; + const updateMetaValue = ( newValue ) => { + setMeta( { ...meta, [ metaKey ]: newValue } ); + }; + return { useValue: [ metaValue, updateMetaValue ] }; + }, +}; diff --git a/packages/editor/src/index.js b/packages/editor/src/index.js index 05c04b8232907..3f6d7a78d837c 100644 --- a/packages/editor/src/index.js +++ b/packages/editor/src/index.js @@ -1,6 +1,7 @@ /** * Internal dependencies */ +import './bindings'; import './hooks'; export { storeConfig, store } from './store'; From 35155385e8fd4099c9f5e48b5042e5f3880ce7e1 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 09:26:59 +0100 Subject: [PATCH 09/26] Don't use placeholder if attribute is `src` or `href` --- .../src/hooks/use-bindings-attributes.js | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index c00303c421f59..ad0099093600b 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { getBlockType } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useRegistry, useSelect } from '@wordpress/data'; import { addFilter } from '@wordpress/hooks'; @@ -31,7 +32,7 @@ const BLOCK_BINDINGS_ALLOWED_BLOCKS = { const createEditFunctionWithBindingsAttribute = () => createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - const { clientId } = useBlockEditContext(); + const { clientId, name: blockName } = useBlockEditContext(); const { getBlockBindingsSource, @@ -66,9 +67,25 @@ const createEditFunctionWithBindingsAttribute = () => settings.source.attributes ); - if ( placeholder ) { - updatedAttributes.placeholder = placeholder; - updatedAttributes[ attributeName ] = null; + if ( + placeholder && + ( ! metaValue || metaValue === '' ) + ) { + // If the attribute is `src` or `href`, a placeholder can't be used because it is not a valid url. + // Adding this workaround until attributes and metadata fields types are improved and include `url`. + const htmlAttribute = + getBlockType( blockName ).attributes[ + attributeName + ].attribute; + if ( + htmlAttribute === 'src' || + htmlAttribute === 'href' + ) { + updatedAttributes[ attributeName ] = null; + } else { + updatedAttributes[ attributeName ] = + placeholder; + } } if ( metaValue ) { From 41709fab8d24f1aa5f9f0356e87655c1ed3648fb Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 09:31:44 +0100 Subject: [PATCH 10/26] Always share placeholder in case meta is empty --- packages/editor/src/bindings/post-meta.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 3e1824e9ef102..cf21878aa1ed6 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -42,6 +42,9 @@ export default { const updateMetaValue = ( newValue ) => { setMeta( { ...meta, [ metaKey ]: newValue } ); }; - return { useValue: [ metaValue, updateMetaValue ] }; + return { + placeholder: keyToLabel( metaKey ), + useValue: [ metaValue, updateMetaValue ], + }; }, }; From 1567f177389a7292ec78584cc182381f44543f05 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 09:37:40 +0100 Subject: [PATCH 11/26] Remove `keyToLabel` and use just label --- packages/editor/src/bindings/post-meta.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index cf21878aa1ed6..496d429901c37 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -10,14 +10,6 @@ import { store as editorStore } from '../store'; const { getCurrentPostType } = select( editorStore ); -// Prettify the name until the label is available in the REST API endpoint. -const keyToLabel = ( key ) => { - return key - .split( '_' ) - .map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ) ) - .join( ' ' ); -}; - export default { name: 'post_meta', label: 'Post Meta', @@ -36,14 +28,14 @@ export default { ); if ( postType === 'wp_template' ) { - return { placeholder: keyToLabel( metaKey ) }; + return { placeholder: metaKey }; } const metaValue = meta[ metaKey ]; const updateMetaValue = ( newValue ) => { setMeta( { ...meta, [ metaKey ]: newValue } ); }; return { - placeholder: keyToLabel( metaKey ), + placeholder: metaKey, useValue: [ metaValue, updateMetaValue ], }; }, From 1984749af6502fe017903c15f6f5f6b05c21ae8c Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 09:43:02 +0100 Subject: [PATCH 12/26] Remove source component until it is needed --- packages/block-editor/src/store/private-actions.js | 1 - packages/block-editor/src/store/reducer.js | 1 - packages/editor/src/bindings/post-meta.js | 1 - 3 files changed, 3 deletions(-) diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 81c66b8303136..2fa72476de455 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -366,7 +366,6 @@ export function registerBlockBindingsSource( source ) { type: 'REGISTER_BLOCK_BINDINGS_SOURCE', sourceName: source.name, sourceLabel: source.label, - sourceComponent: source.component, useSource: source.useSource, }; } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index f0fa02b83353c..ccf949c846255 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2023,7 +2023,6 @@ function blockBindingsSources( state = {}, action ) { ...state, [ action.sourceName ]: { label: action.sourceLabel, - component: action.sourceComponent, useSource: action.useSource, }, }; diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 496d429901c37..178d9cd53d7f7 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -13,7 +13,6 @@ const { getCurrentPostType } = select( editorStore ); export default { name: 'post_meta', label: 'Post Meta', - component: null, useSource( props, sourceAttributes ) { const { context } = props; const { value: metaKey } = sourceAttributes; From 96449467ec05e18806d6bf4dadb92a16feba0ec5 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 09:43:48 +0100 Subject: [PATCH 13/26] Use translations in the source label --- packages/editor/src/bindings/post-meta.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 178d9cd53d7f7..de7c9a92b2a24 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -3,6 +3,7 @@ */ import { useEntityProp } from '@wordpress/core-data'; import { select } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ @@ -12,7 +13,7 @@ const { getCurrentPostType } = select( editorStore ); export default { name: 'post_meta', - label: 'Post Meta', + label: __( 'Post Meta' ), useSource( props, sourceAttributes ) { const { context } = props; const { value: metaKey } = sourceAttributes; From 478f861c2f7a5ca360617ee9bb40b7aa7f87d22d Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 10:35:45 +0100 Subject: [PATCH 14/26] Move `select` inside `useSource` --- packages/editor/src/bindings/post-meta.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index de7c9a92b2a24..7215ea2b2a56b 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -2,19 +2,22 @@ * WordPress dependencies */ import { useEntityProp } from '@wordpress/core-data'; -import { select } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ import { store as editorStore } from '../store'; -const { getCurrentPostType } = select( editorStore ); - export default { name: 'post_meta', label: __( 'Post Meta' ), useSource( props, sourceAttributes ) { + const { getCurrentPostType } = useSelect( ( select ) => { + return { + getCurrentPostType: select( editorStore ).getCurrentPostType, + }; + } ); const { context } = props; const { value: metaKey } = sourceAttributes; const postType = context.postType From 2acf6bd94a969277cd883baf7408ff8c9489fcbc Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 12:24:23 +0100 Subject: [PATCH 15/26] Read `lockEditorUI` prop and add it for patterns --- packages/block-library/src/button/edit.js | 13 +++++++--- packages/block-library/src/heading/edit.js | 5 +++- packages/block-library/src/image/image.js | 24 ++++++++++++------- packages/block-library/src/paragraph/edit.js | 5 +++- .../components/partial-syncing-controls.js | 1 + 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 28fbf711dfc11..2a527e0cec8d0 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -229,6 +229,13 @@ function ButtonEdit( props ) { const useEnterRef = useEnter( { content: text, clientId } ); const mergedRef = useMergeRefs( [ useEnterRef, richTextRef ] ); + const lockUrlControls = + !! metadata?.bindings?.url && + metadata?.bindings?.url?.lockEditorUI !== false; + const lockTextControls = + !! metadata?.bindings?.text && + metadata?.bindings?.text?.lockEditorUI !== false; + return ( <>
@@ -289,7 +296,7 @@ function ButtonEdit( props ) { } } /> ) } - { ! isURLSet && isLinkTag && ! metadata?.bindings?.url && ( + { ! isURLSet && isLinkTag && ! lockUrlControls && ( ) } - { isURLSet && isLinkTag && ! metadata?.bindings?.url && ( + { isURLSet && isLinkTag && ! lockUrlControls && ( onReplace( [] ) } placeholder={ placeholder || __( 'Heading' ) } textAlign={ textAlign } - isContentBound={ metadata?.bindings?.content } + isContentBound={ + !! metadata?.bindings?.content && + metadata?.bindings?.content?.lockEditorUI !== false + } { ...( Platform.isNative && { deleteEnter: true } ) } // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side { ...blockProps } /> diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 0a2fd431d7882..2871328ca166d 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -411,16 +411,22 @@ export default function Image( { ); - const isUrlAttributeConnected = !! metadata?.bindings?.url; - const isAltAttributeConnected = !! metadata?.bindings?.alt; - const isTitleAttributeConnected = !! metadata?.bindings?.title; + const lockUrlControls = + !! metadata?.bindings?.url && + metadata?.bindings?.url?.lockEditorUI !== false; + const lockAltControls = + !! metadata?.bindings?.alt && + metadata?.bindings?.alt?.lockEditorUI !== false; + const lockTitleControls = + !! metadata?.bindings?.title && + metadata?.bindings?.title?.lockEditorUI !== false; const controls = ( <> { ! multiImageSelection && ! isEditingImage && - ! isUrlAttributeConnected && ( + ! lockUrlControls && ( { ! multiImageSelection && ! isEditingImage && - ! isUrlAttributeConnected && ( + ! lockUrlControls && ( { __( 'Connected to a custom field' @@ -562,9 +568,9 @@ export default function Image( { label={ __( 'Title attribute' ) } value={ title || '' } onChange={ onSetTitle } - disabled={ isTitleAttributeConnected } + disabled={ lockTitleControls } help={ - isTitleAttributeConnected ? ( + lockTitleControls ? ( <>{ __( 'Connected to a custom field' ) } ) : ( <> diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index 830ddb141a376..b9d84a3062747 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -181,7 +181,10 @@ function ParagraphBlock( { data-empty={ RichText.isEmpty( content ) } placeholder={ placeholder || __( 'Type / to choose a block' ) } data-custom-placeholder={ placeholder ? true : undefined } - isContentBound={ metadata?.bindings?.content } + isContentBound={ + !! metadata?.bindings?.content && + metadata?.bindings?.content?.lockEditorUI !== false + } __unstableEmbedURLOnPaste __unstableAllowPrefixTransformations /> diff --git a/packages/patterns/src/components/partial-syncing-controls.js b/packages/patterns/src/components/partial-syncing-controls.js index f5ac19bc05f3d..d0de13eaca24f 100644 --- a/packages/patterns/src/components/partial-syncing-controls.js +++ b/packages/patterns/src/components/partial-syncing-controls.js @@ -62,6 +62,7 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { source: { name: 'pattern_attributes', }, + lockEditorUI: false, }; } } From a8a6da3508405cf0772b132ad1ca204b2bc6da1a Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 12:52:40 +0100 Subject: [PATCH 16/26] Move logic to lock editing directly to RichText --- .../src/components/rich-text/index.js | 33 ++++++++++++++++--- packages/block-library/src/button/edit.js | 4 --- packages/block-library/src/heading/edit.js | 7 +--- packages/block-library/src/paragraph/edit.js | 7 +--- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 83d8aac3386d4..04f54e00b58f9 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -19,6 +19,7 @@ import { removeFormat, } from '@wordpress/rich-text'; import { Popover } from '@wordpress/components'; +import { getBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -113,7 +114,11 @@ export function RichTextWrapper( props = removeNativeProps( props ); const anchorRef = useRef(); - const { clientId, isSelected: isBlockSelected } = useBlockEditContext(); + const { + clientId, + isSelected: isBlockSelected, + name: blockName, + } = useBlockEditContext(); const selector = ( select ) => { // Avoid subscribing to the block editor store if the block is not // selected. @@ -149,8 +154,12 @@ export function RichTextWrapper( originalIsSelected, isBlockSelected, ] ); - const { getSelectionStart, getSelectionEnd, getBlockRootClientId } = - useSelect( blockEditorStore ); + const { + getSelectionStart, + getSelectionEnd, + getBlockRootClientId, + getBlockAttributes, + } = useSelect( blockEditorStore ); const { selectionChange } = useDispatch( blockEditorStore ); const adjustedAllowedFormats = getAllowedFormats( { allowedFormats, @@ -289,6 +298,22 @@ export function RichTextWrapper( anchorRef.current?.focus(); } + const bindings = getBlockAttributes( clientId )?.metadata?.bindings; + const blockTypeAttributes = getBlockType( blockName ).attributes; + let shouldDisableEditing = false; + if ( bindings ) + for ( const [ attribute, settings ] of Object.entries( bindings ) ) { + // If any of the attributes with source "rich-text" is part of the bindings, + // disable editing unless it is specified otherwise. + if ( + blockTypeAttributes?.[ attribute ]?.source === 'rich-text' && + settings.lockEditorUI !== false + ) { + shouldDisableEditing = true; + break; + } + } + const TagName = tagName; return ( <> @@ -376,7 +401,7 @@ export function RichTextWrapper( useFirefoxCompat(), anchorRef, ] ) } - contentEditable={ props.isContentBound ? false : true } + contentEditable={ ! shouldDisableEditing } suppressContentEditableWarning={ true } className={ classnames( 'block-editor-rich-text__editable', diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 2a527e0cec8d0..e31e1f0c33994 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -232,9 +232,6 @@ function ButtonEdit( props ) { const lockUrlControls = !! metadata?.bindings?.url && metadata?.bindings?.url?.lockEditorUI !== false; - const lockTextControls = - !! metadata?.bindings?.text && - metadata?.bindings?.text?.lockEditorUI !== false; return ( <> @@ -284,7 +281,6 @@ function ButtonEdit( props ) { onReplace={ onReplace } onMerge={ mergeBlocks } identifier="text" - isContentBound={ lockTextControls } /> diff --git a/packages/block-library/src/heading/edit.js b/packages/block-library/src/heading/edit.js index 14d40e5a12421..e0e36a831429b 100644 --- a/packages/block-library/src/heading/edit.js +++ b/packages/block-library/src/heading/edit.js @@ -33,8 +33,7 @@ function HeadingEdit( { style, clientId, } ) { - const { textAlign, content, level, placeholder, anchor, metadata } = - attributes; + const { textAlign, content, level, placeholder, anchor } = attributes; const tagName = 'h' + level; const blockProps = useBlockProps( { className: classnames( { @@ -139,10 +138,6 @@ function HeadingEdit( { onRemove={ () => onReplace( [] ) } placeholder={ placeholder || __( 'Heading' ) } textAlign={ textAlign } - isContentBound={ - !! metadata?.bindings?.content && - metadata?.bindings?.content?.lockEditorUI !== false - } { ...( Platform.isNative && { deleteEnter: true } ) } // setup RichText on native mobile to delete the "Enter" key as it's handled by the JS/RN side { ...blockProps } /> diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index b9d84a3062747..cecfdce474c51 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -100,8 +100,7 @@ function ParagraphBlock( { setAttributes, clientId, } ) { - const { align, content, direction, dropCap, placeholder, metadata } = - attributes; + const { align, content, direction, dropCap, placeholder } = attributes; const blockProps = useBlockProps( { ref: useOnEnter( { clientId, content } ), className: classnames( { @@ -181,10 +180,6 @@ function ParagraphBlock( { data-empty={ RichText.isEmpty( content ) } placeholder={ placeholder || __( 'Type / to choose a block' ) } data-custom-placeholder={ placeholder ? true : undefined } - isContentBound={ - !! metadata?.bindings?.content && - metadata?.bindings?.content?.lockEditorUI !== false - } __unstableEmbedURLOnPaste __unstableAllowPrefixTransformations /> From 30b635e16aed2422864e22c584a44b0f0ae02ab7 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 13:19:41 +0100 Subject: [PATCH 17/26] Improve `useSelect` destructuring --- .../src/hooks/use-bindings-attributes.js | 20 +++++-------------- packages/editor/src/bindings/post-meta.js | 6 +----- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index ad0099093600b..30fd0b0068427 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -33,21 +33,11 @@ const createEditFunctionWithBindingsAttribute = () => createHigherOrderComponent( ( BlockEdit ) => ( props ) => { const { clientId, name: blockName } = useBlockEditContext(); - - const { - getBlockBindingsSource, - getBlockAttributes, - updateBlockAttributes, - } = useSelect( ( select ) => { - return { - getBlockBindingsSource: unlock( select( blockEditorStore ) ) - .getBlockBindingsSource, - getBlockAttributes: - select( blockEditorStore ).getBlockAttributes, - updateBlockAttributes: - select( blockEditorStore ).updateBlockAttributes, - }; - }, [] ); + const { getBlockBindingsSource } = unlock( + useSelect( blockEditorStore ) + ); + const { getBlockAttributes, updateBlockAttributes } = + useSelect( blockEditorStore ); const updatedAttributes = getBlockAttributes( clientId ); if ( updatedAttributes?.metadata?.bindings ) { diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 7215ea2b2a56b..4bc48b63992fd 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -13,11 +13,7 @@ export default { name: 'post_meta', label: __( 'Post Meta' ), useSource( props, sourceAttributes ) { - const { getCurrentPostType } = useSelect( ( select ) => { - return { - getCurrentPostType: select( editorStore ).getCurrentPostType, - }; - } ); + const { getCurrentPostType } = useSelect( editorStore ); const { context } = props; const { value: metaKey } = sourceAttributes; const postType = context.postType From a6f5fdeeaea3e7e68b6d54e40e1d392cd5ea4a6c Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 13:35:52 +0100 Subject: [PATCH 18/26] Load all image controls if attributes are bound --- packages/block-library/src/image/image.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 2871328ca166d..5386ee7dcd4b0 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -778,7 +778,8 @@ export default function Image( { } if ( ! url && ! temporaryURL ) { - return sizeControls; + // Add all controls if the image attributes are connected. + return metadata?.bindings ? controls : sizeControls; } return ( From 7d0cb9ad60e8db368098ef93b13a04eaed61592f Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 13:46:25 +0100 Subject: [PATCH 19/26] Remove unnecessary condition --- packages/block-editor/src/hooks/use-bindings-attributes.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/block-editor/src/hooks/use-bindings-attributes.js b/packages/block-editor/src/hooks/use-bindings-attributes.js index 30fd0b0068427..6f669f1a321c2 100644 --- a/packages/block-editor/src/hooks/use-bindings-attributes.js +++ b/packages/block-editor/src/hooks/use-bindings-attributes.js @@ -57,10 +57,7 @@ const createEditFunctionWithBindingsAttribute = () => settings.source.attributes ); - if ( - placeholder && - ( ! metaValue || metaValue === '' ) - ) { + if ( placeholder && ! metaValue ) { // If the attribute is `src` or `href`, a placeholder can't be used because it is not a valid url. // Adding this workaround until attributes and metadata fields types are improved and include `url`. const htmlAttribute = From e6a5a4de52a2960e715a1cdf4e20170570d0062f Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 15:35:04 +0100 Subject: [PATCH 20/26] Move `lockAttributesEditing` to source definition --- .../src/components/rich-text/index.js | 7 +++++-- .../block-editor/src/store/private-actions.js | 1 + packages/block-editor/src/store/reducer.js | 1 + packages/block-library/src/button/edit.js | 5 ++++- packages/block-library/src/image/image.js | 21 +++++++++++++------ packages/editor/src/bindings/post-meta.js | 1 + .../components/partial-syncing-controls.js | 1 - 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 04f54e00b58f9..d69c2f01e052d 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -45,6 +45,7 @@ import FormatEdit from './format-edit'; import { getAllowedFormats } from './utils'; import { Content } from './content'; import { withDeprecations } from './with-deprecations'; +import { unlock } from '../../lock-unlock'; export const keyboardShortcutContext = createContext(); export const inputEventContext = createContext(); @@ -300,14 +301,16 @@ export function RichTextWrapper( const bindings = getBlockAttributes( clientId )?.metadata?.bindings; const blockTypeAttributes = getBlockType( blockName ).attributes; + const { getBlockBindingsSource } = unlock( useSelect( blockEditorStore ) ); let shouldDisableEditing = false; if ( bindings ) for ( const [ attribute, settings ] of Object.entries( bindings ) ) { // If any of the attributes with source "rich-text" is part of the bindings, - // disable editing unless it is specified otherwise. + // has a source with `lockAttributesEditing`, disable it. if ( blockTypeAttributes?.[ attribute ]?.source === 'rich-text' && - settings.lockEditorUI !== false + getBlockBindingsSource( settings.source.name ) + ?.lockAttributesEditing ) { shouldDisableEditing = true; break; diff --git a/packages/block-editor/src/store/private-actions.js b/packages/block-editor/src/store/private-actions.js index 2fa72476de455..aea3613884bb6 100644 --- a/packages/block-editor/src/store/private-actions.js +++ b/packages/block-editor/src/store/private-actions.js @@ -367,5 +367,6 @@ export function registerBlockBindingsSource( source ) { sourceName: source.name, sourceLabel: source.label, useSource: source.useSource, + lockAttributesEditing: source.lockAttributesEditing, }; } diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index ccf949c846255..212d24c55d37e 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2024,6 +2024,7 @@ function blockBindingsSources( state = {}, action ) { [ action.sourceName ]: { label: action.sourceLabel, useSource: action.useSource, + lockAttributesEditing: action.lockAttributesEditing, }, }; } diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index e31e1f0c33994..987df49d9c81d 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -9,6 +9,7 @@ import classnames from 'classnames'; import { NEW_TAB_TARGET, NOFOLLOW_REL } from './constants'; import { getUpdatedLinkAttributes } from './get-updated-link-attributes'; import removeAnchorTag from '../utils/remove-anchor-tag'; +import { unlock } from '../lock-unlock'; /** * WordPress dependencies @@ -229,9 +230,11 @@ function ButtonEdit( props ) { const useEnterRef = useEnter( { content: text, clientId } ); const mergedRef = useMergeRefs( [ useEnterRef, richTextRef ] ); + const { getBlockBindingsSource } = unlock( useSelect( blockEditorStore ) ); const lockUrlControls = !! metadata?.bindings?.url && - metadata?.bindings?.url?.lockEditorUI !== false; + getBlockBindingsSource( metadata?.bindings?.url?.source?.name ) + ?.lockAttributesEditing === true; return ( <> diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index 5386ee7dcd4b0..e52a216930780 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -411,15 +411,24 @@ export default function Image( { ); + const { getBlockBindingsSource } = unlock( useSelect( blockEditorStore ) ); + const { + url: urlBinding, + alt: altBinding, + title: titleBinding, + } = metadata?.bindings || {}; const lockUrlControls = - !! metadata?.bindings?.url && - metadata?.bindings?.url?.lockEditorUI !== false; + !! urlBinding && + getBlockBindingsSource( urlBinding?.source?.name ) + ?.lockAttributesEditing === true; const lockAltControls = - !! metadata?.bindings?.alt && - metadata?.bindings?.alt?.lockEditorUI !== false; + !! altBinding && + getBlockBindingsSource( altBinding?.source?.name ) + ?.lockAttributesEditing === true; const lockTitleControls = - !! metadata?.bindings?.title && - metadata?.bindings?.title?.lockEditorUI !== false; + !! titleBinding && + getBlockBindingsSource( titleBinding?.source?.name ) + ?.lockAttributesEditing === true; const controls = ( <> diff --git a/packages/editor/src/bindings/post-meta.js b/packages/editor/src/bindings/post-meta.js index 4bc48b63992fd..17f5e1837e35e 100644 --- a/packages/editor/src/bindings/post-meta.js +++ b/packages/editor/src/bindings/post-meta.js @@ -38,4 +38,5 @@ export default { useValue: [ metaValue, updateMetaValue ], }; }, + lockAttributesEditing: true, }; diff --git a/packages/patterns/src/components/partial-syncing-controls.js b/packages/patterns/src/components/partial-syncing-controls.js index d0de13eaca24f..f5ac19bc05f3d 100644 --- a/packages/patterns/src/components/partial-syncing-controls.js +++ b/packages/patterns/src/components/partial-syncing-controls.js @@ -62,7 +62,6 @@ function PartialSyncingControls( { name, attributes, setAttributes } ) { source: { name: 'pattern_attributes', }, - lockEditorUI: false, }; } } From 7c1ca5a2bf0a1fa8a23bc56938ad2e6eddaa5c58 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 17:08:32 +0100 Subject: [PATCH 21/26] Move `useSelect` into existing hook --- .../src/components/rich-text/index.js | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index d69c2f01e052d..c61e59b16989a 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -127,10 +127,13 @@ export function RichTextWrapper( return { isSelected: false }; } - const { getSelectionStart, getSelectionEnd } = + const { getSelectionStart, getSelectionEnd, getBlockAttributes } = select( blockEditorStore ); const selectionStart = getSelectionStart(); const selectionEnd = getSelectionEnd(); + const blockBindings = + getBlockAttributes( clientId )?.metadata?.bindings; + const { getBlockBindingsSource } = unlock( select( blockEditorStore ) ); let isSelected; @@ -147,20 +150,24 @@ export function RichTextWrapper( selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected, + blockBindings, + getBlockBindingsSource, }; }; - const { selectionStart, selectionEnd, isSelected } = useSelect( selector, [ + const { + selectionStart, + selectionEnd, + isSelected, + blockBindings, + getBlockBindingsSource, + } = useSelect( selector, [ clientId, identifier, originalIsSelected, isBlockSelected, ] ); - const { - getSelectionStart, - getSelectionEnd, - getBlockRootClientId, - getBlockAttributes, - } = useSelect( blockEditorStore ); + const { getSelectionStart, getSelectionEnd, getBlockRootClientId } = + useSelect( blockEditorStore ); const { selectionChange } = useDispatch( blockEditorStore ); const adjustedAllowedFormats = getAllowedFormats( { allowedFormats, @@ -299,23 +306,22 @@ export function RichTextWrapper( anchorRef.current?.focus(); } - const bindings = getBlockAttributes( clientId )?.metadata?.bindings; - const blockTypeAttributes = getBlockType( blockName ).attributes; - const { getBlockBindingsSource } = unlock( useSelect( blockEditorStore ) ); let shouldDisableEditing = false; - if ( bindings ) - for ( const [ attribute, settings ] of Object.entries( bindings ) ) { + if ( blockBindings ) { + const blockTypeAttributes = getBlockType( blockName ).attributes; + for ( const [ attribute, args ] of Object.entries( blockBindings ) ) { // If any of the attributes with source "rich-text" is part of the bindings, // has a source with `lockAttributesEditing`, disable it. if ( blockTypeAttributes?.[ attribute ]?.source === 'rich-text' && - getBlockBindingsSource( settings.source.name ) + getBlockBindingsSource( args.source.name ) ?.lockAttributesEditing ) { shouldDisableEditing = true; break; } } + } const TagName = tagName; return ( From 42462609211628115cd9b03cb4758228a87ab103 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 17:11:13 +0100 Subject: [PATCH 22/26] Fix `RichText` not being selected on click --- .../block-editor/src/components/block-list/content.scss | 2 +- packages/block-editor/src/components/rich-text/index.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-list/content.scss b/packages/block-editor/src/components/block-list/content.scss index 8cd75f6855b62..fa42e69d871b7 100644 --- a/packages/block-editor/src/components/block-list/content.scss +++ b/packages/block-editor/src/components/block-list/content.scss @@ -89,7 +89,7 @@ _::-webkit-full-page-media, _:future, :root .has-multi-selection .block-editor-b .block-editor-block-list__block.is-highlighted, .block-editor-block-list__block.is-highlighted ~ .is-multi-selected, &.is-navigate-mode .block-editor-block-list__block.is-selected, - .block-editor-block-list__block:not([contenteditable]):focus { + .block-editor-block-list__block:not([contenteditable="true"]):focus { outline: none; // We're using a pseudo element to overflow placeholder borders diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index c61e59b16989a..14e6a9643bf06 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -423,7 +423,11 @@ export function RichTextWrapper( // select blocks when Shift Clicking into an element with // tabIndex because Safari will focus the element. However, // Safari will correctly ignore nested contentEditable elements. - tabIndex={ props.tabIndex === 0 ? null : props.tabIndex } + tabIndex={ + props.tabIndex === 0 && ! shouldDisableEditing + ? null + : props.tabIndex + } data-wp-block-attribute-key={ identifier } /> From 5a0cee8afc2a104a4a61cef4602a76282cc3eaff Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 23 Jan 2024 17:49:07 +0100 Subject: [PATCH 23/26] Lock button and image controls only when selected --- packages/block-library/src/button/edit.js | 25 ++++++++--- packages/block-library/src/image/image.js | 51 +++++++++++++++-------- 2 files changed, 54 insertions(+), 22 deletions(-) diff --git a/packages/block-library/src/button/edit.js b/packages/block-library/src/button/edit.js index 987df49d9c81d..815857192822a 100644 --- a/packages/block-library/src/button/edit.js +++ b/packages/block-library/src/button/edit.js @@ -230,11 +230,26 @@ function ButtonEdit( props ) { const useEnterRef = useEnter( { content: text, clientId } ); const mergedRef = useMergeRefs( [ useEnterRef, richTextRef ] ); - const { getBlockBindingsSource } = unlock( useSelect( blockEditorStore ) ); - const lockUrlControls = - !! metadata?.bindings?.url && - getBlockBindingsSource( metadata?.bindings?.url?.source?.name ) - ?.lockAttributesEditing === true; + const { lockUrlControls = false } = useSelect( + ( select ) => { + if ( ! isSelected ) { + return {}; + } + + const { getBlockBindingsSource } = unlock( + select( blockEditorStore ) + ); + + return { + lockUrlControls: + !! metadata?.bindings?.url && + getBlockBindingsSource( + metadata?.bindings?.url?.source?.name + )?.lockAttributesEditing === true, + }; + }, + [ isSelected ] + ); return ( <> diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js index e52a216930780..0e1856ed99d2c 100644 --- a/packages/block-library/src/image/image.js +++ b/packages/block-library/src/image/image.js @@ -411,24 +411,41 @@ export default function Image( { ); - const { getBlockBindingsSource } = unlock( useSelect( blockEditorStore ) ); const { - url: urlBinding, - alt: altBinding, - title: titleBinding, - } = metadata?.bindings || {}; - const lockUrlControls = - !! urlBinding && - getBlockBindingsSource( urlBinding?.source?.name ) - ?.lockAttributesEditing === true; - const lockAltControls = - !! altBinding && - getBlockBindingsSource( altBinding?.source?.name ) - ?.lockAttributesEditing === true; - const lockTitleControls = - !! titleBinding && - getBlockBindingsSource( titleBinding?.source?.name ) - ?.lockAttributesEditing === true; + lockUrlControls = false, + lockAltControls = false, + lockTitleControls = false, + } = useSelect( + ( select ) => { + if ( ! isSelected ) { + return {}; + } + + const { getBlockBindingsSource } = unlock( + select( blockEditorStore ) + ); + const { + url: urlBinding, + alt: altBinding, + title: titleBinding, + } = metadata?.bindings || {}; + return { + lockUrlControls: + !! urlBinding && + getBlockBindingsSource( urlBinding?.source?.name ) + ?.lockAttributesEditing === true, + lockAltControls: + !! altBinding && + getBlockBindingsSource( altBinding?.source?.name ) + ?.lockAttributesEditing === true, + lockTitleControls: + !! titleBinding && + getBlockBindingsSource( titleBinding?.source?.name ) + ?.lockAttributesEditing === true, + }; + }, + [ isSelected ] + ); const controls = ( <> From 54c313dc3366bd263c8d67b517813c3e368990f8 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 24 Jan 2024 12:40:49 +0100 Subject: [PATCH 24/26] Remove unnecesarry optional chaining --- packages/block-editor/src/store/private-selectors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/store/private-selectors.js b/packages/block-editor/src/store/private-selectors.js index 863207e0290d9..71c1e3c824f5d 100644 --- a/packages/block-editor/src/store/private-selectors.js +++ b/packages/block-editor/src/store/private-selectors.js @@ -292,5 +292,5 @@ export function getAllBlockBindingsSources( state ) { } export function getBlockBindingsSource( state, sourceName ) { - return state?.blockBindingsSources?.[ sourceName ]; + return state.blockBindingsSources[ sourceName ]; } From aaf8652d0e1facf6f1cd7431a4f96819e1f59c1b Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 24 Jan 2024 13:01:00 +0100 Subject: [PATCH 25/26] Move `shouldDisableEditing` logic inside callback --- .../src/components/rich-text/index.js | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/block-editor/src/components/rich-text/index.js b/packages/block-editor/src/components/rich-text/index.js index 14e6a9643bf06..69b04fe4c4904 100644 --- a/packages/block-editor/src/components/rich-text/index.js +++ b/packages/block-editor/src/components/rich-text/index.js @@ -133,7 +133,6 @@ export function RichTextWrapper( const selectionEnd = getSelectionEnd(); const blockBindings = getBlockAttributes( clientId )?.metadata?.bindings; - const { getBlockBindingsSource } = unlock( select( blockEditorStore ) ); let isSelected; @@ -146,26 +145,44 @@ export function RichTextWrapper( isSelected = selectionStart.clientId === clientId; } + // Disable Rich Text editing if block bindings specify that. + let shouldDisableEditing = false; + if ( blockBindings ) { + const blockTypeAttributes = getBlockType( blockName ).attributes; + const { getBlockBindingsSource } = unlock( + select( blockEditorStore ) + ); + for ( const [ attribute, args ] of Object.entries( + blockBindings + ) ) { + // If any of the attributes with source "rich-text" is part of the bindings, + // has a source with `lockAttributesEditing`, disable it. + if ( + blockTypeAttributes?.[ attribute ]?.source === + 'rich-text' && + getBlockBindingsSource( args.source.name ) + ?.lockAttributesEditing + ) { + shouldDisableEditing = true; + break; + } + } + } + return { selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected, - blockBindings, - getBlockBindingsSource, + shouldDisableEditing, }; }; - const { - selectionStart, - selectionEnd, - isSelected, - blockBindings, - getBlockBindingsSource, - } = useSelect( selector, [ - clientId, - identifier, - originalIsSelected, - isBlockSelected, - ] ); + const { selectionStart, selectionEnd, isSelected, shouldDisableEditing } = + useSelect( selector, [ + clientId, + identifier, + originalIsSelected, + isBlockSelected, + ] ); const { getSelectionStart, getSelectionEnd, getBlockRootClientId } = useSelect( blockEditorStore ); const { selectionChange } = useDispatch( blockEditorStore ); @@ -306,23 +323,6 @@ export function RichTextWrapper( anchorRef.current?.focus(); } - let shouldDisableEditing = false; - if ( blockBindings ) { - const blockTypeAttributes = getBlockType( blockName ).attributes; - for ( const [ attribute, args ] of Object.entries( blockBindings ) ) { - // If any of the attributes with source "rich-text" is part of the bindings, - // has a source with `lockAttributesEditing`, disable it. - if ( - blockTypeAttributes?.[ attribute ]?.source === 'rich-text' && - getBlockBindingsSource( args.source.name ) - ?.lockAttributesEditing - ) { - shouldDisableEditing = true; - break; - } - } - } - const TagName = tagName; return ( <> From 895e6dd6126d2a28da781addd009ac2fe8e0eff4 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Wed, 24 Jan 2024 13:19:51 +0100 Subject: [PATCH 26/26] Fix formatting issue --- packages/block-editor/src/store/reducer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js index ae9a897d48cb7..dc69a4da609a4 100644 --- a/packages/block-editor/src/store/reducer.js +++ b/packages/block-editor/src/store/reducer.js @@ -2033,8 +2033,8 @@ function blockBindingsSources( state = {}, action ) { lockAttributesEditing: action.lockAttributesEditing, }, }; - } - return state; + } + return state; } function blockPatterns( state = [], action ) {