From 1eb005785b3506714ba77dfe831b759399d443db Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 31 Mar 2022 16:47:41 +0400 Subject: [PATCH 1/7] Introduce canEditBlock selector --- .../data/data-core-block-editor.md | 13 ++++++++++ packages/block-editor/src/store/selectors.js | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/docs/reference-guides/data/data-core-block-editor.md b/docs/reference-guides/data/data-core-block-editor.md index ba256530cd32a9..bdbfc78eb482e4 100644 --- a/docs/reference-guides/data/data-core-block-editor.md +++ b/docs/reference-guides/data/data-core-block-editor.md @@ -19,6 +19,19 @@ _Returns_ - `boolean`: True if the block has controlled inner blocks. +### canEditBlock + +Determines if the given block is allowed to be edited. + +_Parameters_ + +- _state_ `Object`: Editor state. +- _clientId_ `string`: The block client Id. + +_Returns_ + +- `boolean`: Whether the given block is allowed to be edited. + ### canInsertBlocks Determines if the given blocks are allowed to be inserted into the block diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 6c9a95e08d6856..778dffd7215bb2 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1569,6 +1569,30 @@ export function canMoveBlocks( state, clientIds, rootClientId = null ) { ); } +/** + * Determines if the given block is allowed to be edited. + * + * @param {Object} state Editor state. + * @param {string} clientId The block client Id. + * + * @return {boolean} Whether the given block is allowed to be edited. + */ +export function canEditBlock( state, clientId ) { + const attributes = getBlockAttributes( state, clientId ); + if ( attributes === null ) { + return true; + } + + const { lock } = attributes; + // No `lock` attribute means we can edit the block. + if ( lock === undefined || lock?.edit === undefined ) { + return true; + } + + // When the edit is true, we cannot edit the block. + return ! lock?.edit; +} + /** * Determines if the given block type can be locked/unlocked by a user. * From eb392d308b7888d49e939c8d399a3a41a14a8a61 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 31 Mar 2022 17:08:06 +0400 Subject: [PATCH 2/7] Update BlockContentOverlay --- .../components/block-content-overlay/index.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/block-content-overlay/index.js b/packages/block-editor/src/components/block-content-overlay/index.js index 249f6f3bc8e3e1..56f3da5b28c217 100644 --- a/packages/block-editor/src/components/block-content-overlay/index.js +++ b/packages/block-editor/src/components/block-content-overlay/index.js @@ -25,6 +25,7 @@ export default function BlockContentOverlay( { const [ isHovered, setIsHovered ] = useState( false ); const { + canEdit, isParentSelected, hasChildSelected, isDraggingBlocks, @@ -36,8 +37,10 @@ export default function BlockContentOverlay( { hasSelectedInnerBlock, isDraggingBlocks: _isDraggingBlocks, isBlockHighlighted, + canEditBlock, } = select( blockEditorStore ); return { + canEdit: canEditBlock( clientId ), isParentSelected: isBlockSelected( clientId ), hasChildSelected: hasSelectedInnerBlock( clientId, true ), isDraggingBlocks: _isDraggingBlocks(), @@ -59,6 +62,11 @@ export default function BlockContentOverlay( { ); useEffect( () => { + // The overlay is always active when editing is locked. + if ( ! canEdit ) { + return; + } + // Reenable when blocks are not in use. if ( ! isParentSelected && ! hasChildSelected && ! isOverlayActive ) { setIsOverlayActive( true ); @@ -75,7 +83,13 @@ export default function BlockContentOverlay( { if ( hasChildSelected && isOverlayActive ) { setIsOverlayActive( false ); } - }, [ isParentSelected, hasChildSelected, isOverlayActive, isHovered ] ); + }, [ + isParentSelected, + hasChildSelected, + isOverlayActive, + isHovered, + canEdit, + ] ); // Disabled because the overlay div doesn't actually have a role or functionality // as far as the a11y is concerned. We're just catching the first click so that @@ -88,7 +102,9 @@ export default function BlockContentOverlay( { onMouseEnter={ () => setIsHovered( true ) } onMouseLeave={ () => setIsHovered( false ) } onMouseUp={ - isOverlayActive ? () => setIsOverlayActive( false ) : undefined + isOverlayActive && canEdit + ? () => setIsOverlayActive( false ) + : undefined } > { wrapperProps?.children } From 19610a9d2831e02bc222623587cd09aca24045f2 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 31 Mar 2022 17:42:17 +0400 Subject: [PATCH 3/7] Update UI --- .../components/block-content-overlay/index.js | 1 + .../src/components/block-lock/menu-item.js | 7 ++- .../src/components/block-lock/modal.js | 50 ++++++++++++++++--- .../src/components/block-lock/toolbar.js | 4 +- .../components/block-lock/use-block-lock.js | 2 + 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-content-overlay/index.js b/packages/block-editor/src/components/block-content-overlay/index.js index 56f3da5b28c217..727d2445883ce1 100644 --- a/packages/block-editor/src/components/block-content-overlay/index.js +++ b/packages/block-editor/src/components/block-content-overlay/index.js @@ -64,6 +64,7 @@ export default function BlockContentOverlay( { useEffect( () => { // The overlay is always active when editing is locked. if ( ! canEdit ) { + setIsOverlayActive( true ); return; } diff --git a/packages/block-editor/src/components/block-lock/menu-item.js b/packages/block-editor/src/components/block-lock/menu-item.js index 7c66038beb6f68..2f08802a65668c 100644 --- a/packages/block-editor/src/components/block-lock/menu-item.js +++ b/packages/block-editor/src/components/block-lock/menu-item.js @@ -13,8 +13,11 @@ import useBlockLock from './use-block-lock'; import BlockLockModal from './modal'; export default function BlockLockMenuItem( { clientId } ) { - const { canMove, canRemove, canLock } = useBlockLock( clientId, true ); - const isLocked = ! canMove || ! canRemove; + const { canEdit, canMove, canRemove, canLock } = useBlockLock( + clientId, + true + ); + const isLocked = ! canEdit || ! canMove || ! canRemove; const [ isModalOpen, toggleModal ] = useReducer( ( isActive ) => ! isActive, diff --git a/packages/block-editor/src/components/block-lock/modal.js b/packages/block-editor/src/components/block-lock/modal.js index 99ff8f7c55ba93..321e59f1cd1f22 100644 --- a/packages/block-editor/src/components/block-lock/modal.js +++ b/packages/block-editor/src/components/block-lock/modal.js @@ -13,7 +13,8 @@ import { } from '@wordpress/components'; import { lock as lockIcon, unlock as unlockIcon } from '@wordpress/icons'; import { useInstanceId } from '@wordpress/compose'; -import { useDispatch } from '@wordpress/data'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { isReusableBlock, getBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -24,7 +25,18 @@ import { store as blockEditorStore } from '../../store'; export default function BlockLockModal( { clientId, onClose } ) { const [ lock, setLock ] = useState( { move: false, remove: false } ); - const { canMove, canRemove } = useBlockLock( clientId, true ); + const { canEdit, canMove, canRemove } = useBlockLock( clientId, true ); + const { isReusable } = useSelect( + ( select ) => { + const { getBlockName } = select( blockEditorStore ); + const blockName = getBlockName( clientId ); + + return { + isReusable: isReusableBlock( getBlockType( blockName ) ), + }; + }, + [ clientId ] + ); const { updateBlockAttributes } = useDispatch( blockEditorStore ); const blockInformation = useBlockDisplayInformation( clientId ); const instanceId = useInstanceId( @@ -36,12 +48,12 @@ export default function BlockLockModal( { clientId, onClose } ) { setLock( { move: ! canMove, remove: ! canRemove, + ...( isReusable ? { edit: ! canEdit } : {} ), } ); - }, [ canMove, canRemove ] ); + }, [ canEdit, canMove, canRemove, isReusable ] ); const isAllChecked = Object.values( lock ).every( Boolean ); - const isIndeterminate = - Object.values( lock ).some( Boolean ) && ! isAllChecked; + const isMixed = Object.values( lock ).some( Boolean ) && ! isAllChecked; return ( { __( 'Lock all' ) } } checked={ isAllChecked } - indeterminate={ isIndeterminate } + indeterminate={ isMixed } onChange={ ( newValue ) => setLock( { move: newValue, remove: newValue, + ...( isReusable ? { edit: newValue } : {} ), } ) } />
    + { isReusable && ( +
  • + + { __( 'Restrict editing' ) } + + + } + checked={ lock.edit } + onChange={ ( edit ) => + setLock( ( prevLock ) => ( { + ...prevLock, + edit, + } ) ) + } + /> +
  • + ) }
  • ! isActive, @@ -26,7 +26,7 @@ export default function BlockLockToolbar( { clientId } ) { return null; } - if ( canMove && canRemove ) { + if ( canEdit && canMove && canRemove ) { return null; } diff --git a/packages/block-editor/src/components/block-lock/use-block-lock.js b/packages/block-editor/src/components/block-lock/use-block-lock.js index 61c387d790f2c8..e52ed23beb0533 100644 --- a/packages/block-editor/src/components/block-lock/use-block-lock.js +++ b/packages/block-editor/src/components/block-lock/use-block-lock.js @@ -21,6 +21,7 @@ export default function useBlockLock( clientId, checkParent = false ) { return useSelect( ( select ) => { const { + canEditBlock, canMoveBlock, canRemoveBlock, canLockBlockType, @@ -32,6 +33,7 @@ export default function useBlockLock( clientId, checkParent = false ) { : null; return { + canEdit: canEditBlock( clientId ), canMove: canMoveBlock( clientId, rootClientId ), canRemove: canRemoveBlock( clientId, rootClientId ), canLock: canLockBlockType( getBlockName( clientId ) ), From 416270562c4431838d77b1fe657fcf912d894efa Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Sun, 3 Apr 2022 17:38:36 +0400 Subject: [PATCH 4/7] Fix indeterminate state and e2e tests --- packages/block-editor/src/components/block-lock/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/block-lock/modal.js b/packages/block-editor/src/components/block-lock/modal.js index 321e59f1cd1f22..311254d9c3e759 100644 --- a/packages/block-editor/src/components/block-lock/modal.js +++ b/packages/block-editor/src/components/block-lock/modal.js @@ -114,7 +114,7 @@ export default function BlockLockModal( { clientId, onClose } ) { /> } - checked={ lock.edit } + checked={ !! lock.edit } onChange={ ( edit ) => setLock( ( prevLock ) => ( { ...prevLock, From 3b84542c666af9ba9945d493a8b175eb1e6b051b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 11 Apr 2022 13:49:13 +0400 Subject: [PATCH 5/7] Update list view --- .../src/components/list-view/block-select-button.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-editor/src/components/list-view/block-select-button.js b/packages/block-editor/src/components/list-view/block-select-button.js index 27ca4bff483f61..307e9caa510e83 100644 --- a/packages/block-editor/src/components/list-view/block-select-button.js +++ b/packages/block-editor/src/components/list-view/block-select-button.js @@ -35,8 +35,8 @@ function ListViewBlockSelectButton( ref ) { const blockInformation = useBlockDisplayInformation( clientId ); - const { canMove, canRemove } = useBlockLock( clientId ); - const isLocked = ! canMove || ! canRemove; + const { canEdit, canMove, canRemove } = useBlockLock( clientId ); + const isLocked = ! canEdit || ! canMove || ! canRemove; // The `href` attribute triggers the browser's native HTML drag operations. // When the link is dragged, the element's outerHTML is set in DataTransfer object as text/html. From 46562453c5927fd593f052b075cdd21bc43ab63c Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 11 Apr 2022 17:06:04 +0400 Subject: [PATCH 6/7] Update selector --- packages/block-editor/src/store/selectors.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 778dffd7215bb2..96972c7ac6a8e0 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -1584,10 +1584,6 @@ export function canEditBlock( state, clientId ) { } const { lock } = attributes; - // No `lock` attribute means we can edit the block. - if ( lock === undefined || lock?.edit === undefined ) { - return true; - } // When the edit is true, we cannot edit the block. return ! lock?.edit; From 71ebda894c35bcc17521fb8ad30b68bc5927c332 Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Mon, 11 Apr 2022 17:12:52 +0400 Subject: [PATCH 7/7] Provide 'isLocked' value via hook --- .../src/components/block-lock/menu-item.js | 6 +----- .../src/components/block-lock/use-block-lock.js | 11 ++++++++--- .../src/components/list-view/block-select-button.js | 3 +-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/block-editor/src/components/block-lock/menu-item.js b/packages/block-editor/src/components/block-lock/menu-item.js index 2f08802a65668c..6a7a1517cf4090 100644 --- a/packages/block-editor/src/components/block-lock/menu-item.js +++ b/packages/block-editor/src/components/block-lock/menu-item.js @@ -13,11 +13,7 @@ import useBlockLock from './use-block-lock'; import BlockLockModal from './modal'; export default function BlockLockMenuItem( { clientId } ) { - const { canEdit, canMove, canRemove, canLock } = useBlockLock( - clientId, - true - ); - const isLocked = ! canEdit || ! canMove || ! canRemove; + const { canLock, isLocked } = useBlockLock( clientId, true ); const [ isModalOpen, toggleModal ] = useReducer( ( isActive ) => ! isActive, diff --git a/packages/block-editor/src/components/block-lock/use-block-lock.js b/packages/block-editor/src/components/block-lock/use-block-lock.js index e52ed23beb0533..6d4d7d8e86095f 100644 --- a/packages/block-editor/src/components/block-lock/use-block-lock.js +++ b/packages/block-editor/src/components/block-lock/use-block-lock.js @@ -32,11 +32,16 @@ export default function useBlockLock( clientId, checkParent = false ) { ? getBlockRootClientId( clientId ) : null; + const canEdit = canEditBlock( clientId ); + const canMove = canMoveBlock( clientId, rootClientId ); + const canRemove = canRemoveBlock( clientId, rootClientId ); + return { - canEdit: canEditBlock( clientId ), - canMove: canMoveBlock( clientId, rootClientId ), - canRemove: canRemoveBlock( clientId, rootClientId ), + canEdit, + canMove, + canRemove, canLock: canLockBlockType( getBlockName( clientId ) ), + isLocked: ! canEdit || ! canMove || ! canRemove, }; }, [ clientId, checkParent ] diff --git a/packages/block-editor/src/components/list-view/block-select-button.js b/packages/block-editor/src/components/list-view/block-select-button.js index 307e9caa510e83..f6eb82be503784 100644 --- a/packages/block-editor/src/components/list-view/block-select-button.js +++ b/packages/block-editor/src/components/list-view/block-select-button.js @@ -35,8 +35,7 @@ function ListViewBlockSelectButton( ref ) { const blockInformation = useBlockDisplayInformation( clientId ); - const { canEdit, canMove, canRemove } = useBlockLock( clientId ); - const isLocked = ! canEdit || ! canMove || ! canRemove; + const { isLocked } = useBlockLock( clientId ); // The `href` attribute triggers the browser's native HTML drag operations. // When the link is dragged, the element's outerHTML is set in DataTransfer object as text/html.