Skip to content

Commit

Permalink
Block Bindings: Allow editing in post meta source (#61753)
Browse files Browse the repository at this point in the history
* Create bindings util for transforming block attributes

* Get attributes and name from the store

* Change function to return only the bound attributes

* Add action, selector, and reducer for block context

* Sync store in edit component

* Revert "Sync store in edit component"

This reverts commit 988c4b6.

* Move logic to BlockContextProvider

* Change parent context logic

* Use useLayoutEffect

* Go back to syncing store in edit component

* WIP: Move bindings logic to `getBlockAttributes`

* WIP: Move bindings setAttributes logic to updateBlockBindings

* Pass only `select` to `getValue` functions

* Remove old editor hook

* Add fallback to postId until context is ready

* Remove setValue post-meta code

* Simplify fallback conditional

* Change bindings destructuring

* Check canBindAttribute in updateBlockAttributes

* Update unit tests to expect a dispatch

* Add conditional in block

* Don't use `getBlockAttributes` inside `getValue`

* Revert "Don't use `getBlockAttributes` inside `getValue`"

This reverts commit 0e91129.

* Avoid processing bindings recursively

* Access context through selector

* Update getBlockAttributes logic

* Don't use fallbacks

* Add edit value posibility for post meta, add function to check if is admin

* Revert "Add edit value posibility for post meta, add function to check if is admin"

This reverts commit 9659455.

* Test editing is allowed in paragraph block

* Test protected fields are not editable

* Revert "Enable parallel processing for PHPCS sniffs (#61700)"

This reverts commit 8331820.

* Add post meta setValue function

* Update tests to check contenteditable

* Update lockAttributesEditing default

* Pass arguments to lockAttributesEditing

* Check user can edit post meta

* Check field is exposed in the REST API

* Disable editing in templates

* Add fallback for postId

* Revert "Revert "Enable parallel processing for PHPCS sniffs (#61700)""

This reverts commit e74d71c.

* Adapt old tests

* Simplify lockAttributesEditing fallbacks

* Don't use fallback for context

* Add postType fallback when locking controls

* Pass context in rich text

* Check contenteditable attribute in test

* Change name to `canUserEditValue`

* Revert changes caused by rebasing

* Add space back

* Pass block context through rich text

* Change imports

* Transform block attributes into bindings in split selection

* Pass context to in use-input

* Change REST API check

* Use getBoundAttributesValues

* Don't split when attribute is bound

* Revert changes caused by rebase

* Cover more blocks when editing custom fields

* Add a warning when pasting blocks

---------

Co-authored-by: Carlos Bravo <carlos.bravo@automattic.com>
  • Loading branch information
SantosGuillamot and cbravobernal authored May 31, 2024
1 parent 1b2c23e commit 8f74d5c
Show file tree
Hide file tree
Showing 10 changed files with 396 additions and 72 deletions.
16 changes: 11 additions & 5 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
useCallback,
forwardRef,
createContext,
useContext,
} from '@wordpress/element';
import { useDispatch, useRegistry, useSelect } from '@wordpress/data';
import { useMergeRefs, useInstanceId } from '@wordpress/compose';
Expand Down Expand Up @@ -39,6 +40,7 @@ import { Content, valueToHTMLString } from './content';
import { withDeprecations } from './with-deprecations';
import { unlock } from '../../lock-unlock';
import { canBindBlock } from '../../hooks/use-bindings-attributes';
import BlockContext from '../block-context';

export const keyboardShortcutContext = createContext();
export const inputEventContext = createContext();
Expand Down Expand Up @@ -121,6 +123,7 @@ export function RichTextWrapper(
const context = useBlockEditContext();
const { clientId, isSelected: isBlockSelected, name: blockName } = context;
const blockBindings = context[ blockBindingsKey ];
const blockContext = useContext( BlockContext );
const selector = ( select ) => {
// Avoid subscribing to the block editor store if the block is not
// selected.
Expand Down Expand Up @@ -170,7 +173,7 @@ export function RichTextWrapper(
const { getBlockBindingsSource } = unlock(
select( blocksStore )
);
for ( const [ attribute, args ] of Object.entries(
for ( const [ attribute, binding ] of Object.entries(
blockBindings
) ) {
if (
Expand All @@ -180,13 +183,16 @@ export function RichTextWrapper(
break;
}

// If the source is not defined, or if its value of `lockAttributesEditing` is `true`, disable it.
// If the source is not defined, or if its value of `canUserEditValue` is `false`, disable it.
const blockBindingsSource = getBlockBindingsSource(
args.source
binding.source
);
if (
! blockBindingsSource ||
blockBindingsSource.lockAttributesEditing()
! blockBindingsSource?.canUserEditValue( {
select,
context: blockContext,
args: binding.args,
} )
) {
_disableBoundBlocks = true;
break;
Expand Down
29 changes: 26 additions & 3 deletions packages/block-editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@wordpress/blocks';
import { speak } from '@wordpress/a11y';
import { __, _n, sprintf } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';
import { create, insert, remove, toHTMLString } from '@wordpress/rich-text';
import deprecated from '@wordpress/deprecated';

Expand Down Expand Up @@ -872,6 +873,30 @@ export const __unstableSplitSelection =
typeof selectionB.attributeKey === 'string'
? selectionB.attributeKey
: findRichTextAttributeKey( blockBType );
const blockAttributes = select.getBlockAttributes(
selectionA.clientId
);
const bindings = blockAttributes?.metadata?.bindings;

// If the attribute is bound, don't split the selection and insert a new block instead.
if ( bindings?.[ attributeKeyA ] ) {
// Show warning if user tries to insert a block into another block with bindings.
if ( blocks.length ) {
const { createWarningNotice } =
registry.dispatch( noticesStore );
createWarningNotice(
__(
"Blocks can't be inserted into other blocks with bindings"
),
{
type: 'snackbar',
}
);
return;
}
dispatch.insertAfterBlock( selectionA.clientId );
return;
}

// Can't split if the selection is not set.
if (
Expand Down Expand Up @@ -918,9 +943,7 @@ export const __unstableSplitSelection =
);
}

const length = select.getBlockAttributes( selectionA.clientId )[
attributeKeyA
].length;
const length = blockAttributes[ attributeKeyA ].length;

if ( selectionA.offset === 0 && length ) {
dispatch.insertBlocks(
Expand Down
8 changes: 6 additions & 2 deletions packages/block-library/src/button/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ function ButtonEdit( props ) {
onReplace,
mergeBlocks,
clientId,
context,
} = props;
const {
tagName,
Expand Down Expand Up @@ -246,8 +247,11 @@ function ButtonEdit( props ) {
return {
lockUrlControls:
!! metadata?.bindings?.url &&
( ! blockBindingsSource ||
blockBindingsSource?.lockAttributesEditing() ),
! blockBindingsSource?.canUserEditValue( {
select,
context,
args: metadata?.bindings?.url?.args,
} ),
};
},
[ isSelected, metadata?.bindings?.url ]
Expand Down
7 changes: 5 additions & 2 deletions packages/block-library/src/image/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,11 @@ export function ImageEdit( {
return {
lockUrlControls:
!! metadata?.bindings?.url &&
( ! blockBindingsSource ||
blockBindingsSource?.lockAttributesEditing() ),
! blockBindingsSource?.canUserEditValue( {
select,
context,
args: metadata?.bindings?.url?.args,
} ),
lockUrlControlsMessage: blockBindingsSource?.label
? sprintf(
/* translators: %s: Label of the bindings source. */
Expand Down
21 changes: 15 additions & 6 deletions packages/block-library/src/image/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,11 @@ export default function Image( {
return {
lockUrlControls:
!! urlBinding &&
( ! urlBindingSource ||
urlBindingSource?.lockAttributesEditing() ),
! urlBindingSource?.canUserEditValue( {
select,
context,
args: urlBinding?.args,
} ),
lockHrefControls:
// Disable editing the link of the URL if the image is inside a pattern instance.
// This is a temporary solution until we support overriding the link on the frontend.
Expand All @@ -474,8 +477,11 @@ export default function Image( {
hasParentPattern,
lockAltControls:
!! altBinding &&
( ! altBindingSource ||
altBindingSource?.lockAttributesEditing() ),
! altBindingSource?.canUserEditValue( {
select,
context,
args: altBinding?.args,
} ),
lockAltControlsMessage: altBindingSource?.label
? sprintf(
/* translators: %s: Label of the bindings source. */
Expand All @@ -485,8 +491,11 @@ export default function Image( {
: __( 'Connected to dynamic data' ),
lockTitleControls:
!! titleBinding &&
( ! titleBindingSource ||
titleBindingSource?.lockAttributesEditing() ),
! titleBindingSource?.canUserEditValue( {
select,
context,
args: titleBinding?.args,
} ),
lockTitleControlsMessage: titleBindingSource?.label
? sprintf(
/* translators: %s: Label of the bindings source. */
Expand Down
2 changes: 1 addition & 1 deletion packages/blocks/src/store/private-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ export function registerBlockBindingsSource( source ) {
setValue: source.setValue,
setValues: source.setValues,
getPlaceholder: source.getPlaceholder,
lockAttributesEditing: source.lockAttributesEditing,
canUserEditValue: source.canUserEditValue,
};
}
5 changes: 1 addition & 4 deletions packages/blocks/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,10 +381,7 @@ export function blockBindingsSources( state = {}, action ) {
setValue: action.setValue,
setValues: action.setValues,
getPlaceholder: action.getPlaceholder,
lockAttributesEditing: () =>
action.lockAttributesEditing
? action.lockAttributesEditing()
: true,
canUserEditValue: action.canUserEditValue || ( () => false ),
},
};
}
Expand Down
4 changes: 1 addition & 3 deletions packages/editor/src/bindings/pattern-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,5 @@ export default {
},
} );
},
lockAttributesEditing() {
return false;
},
canUserEditValue: () => true,
};
52 changes: 46 additions & 6 deletions packages/editor/src/bindings/post-meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,53 @@ export default {
return args.key;
},
getValue( { registry, context, args } ) {
const postType = context.postType
? context.postType
: registry.select( editorStore ).getCurrentPostType();

return registry
.select( coreDataStore )
.getEditedEntityRecord( 'postType', postType, context.postId )
.meta?.[ args.key ];
.getEditedEntityRecord(
'postType',
context?.postType,
context?.postId
).meta?.[ args.key ];
},
setValue( { registry, context, args, value } ) {
registry
.dispatch( coreDataStore )
.editEntityRecord( 'postType', context?.postType, context?.postId, {
meta: {
[ args.key ]: value,
},
} );
},
canUserEditValue( { select, context, args } ) {
const postType =
context?.postType || select( editorStore ).getCurrentPostType();

// Check that editing is happening in the post editor and not a template.
if ( postType === 'wp_template' ) {
return false;
}

// Check that the custom field is not protected and available in the REST API.
const isFieldExposed = !! select( coreDataStore ).getEntityRecord(
'postType',
postType,
context?.postId
)?.meta?.[ args.key ];

if ( ! isFieldExposed ) {
return false;
}

// Check that the user has the capability to edit post meta.
const canUserEdit = select( coreDataStore ).canUserEditEntityRecord(
'postType',
context?.postType,
context?.postId
);
if ( ! canUserEdit ) {
return false;
}

return true;
},
};
Loading

0 comments on commit 8f74d5c

Please sign in to comment.