Skip to content

Commit

Permalink
Introduce Block type level lock control (#32457)
Browse files Browse the repository at this point in the history
* Add system level remove lock

* add system level move lock

* add docs

* cover moving between blocks

* fix tests

* move logic to attributes instead of supports

* add attribute hook

* add docs

* update docs and remove extra check

* Fix lock attribute access.

* prevent merging if a block is locked from being removed

* prevent transforms

* include rootClientId

* mock canRemove

* mock canMove

* switch to failsafe

* fix test

Co-authored-by: Jorge <jorge.costa@developer.pt>
  • Loading branch information
senadir and jorgefilipecosta committed Sep 10, 2021
1 parent 1da1149 commit 69e348e
Show file tree
Hide file tree
Showing 17 changed files with 394 additions and 156 deletions.
2 changes: 1 addition & 1 deletion docs/reference-guides/block-api/block-supports.md
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ A block may want to disable the ability of being converted into a reusable block
```js
supports: {
// Don't allow the block to be converted into a reusable block.
reusable: false;
reusable: false,
}
```

Expand Down
22 changes: 21 additions & 1 deletion docs/reference-guides/block-api/block-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function myplugin_register_book_post_type() {
add_action( 'init', 'myplugin_register_book_post_type' );
```

### Locking
## Locking

Sometimes the intention might be to lock the template on the UI so that the blocks presented cannot be manipulated. This is achieved with a `template_lock` property.

Expand All @@ -120,6 +120,26 @@ _Options:_

Lock settings can be inherited by InnerBlocks. If `templateLock` is not set in an InnerBlocks area, the locking of the parent InnerBlocks area is used. If the block is a top level block, the locking configuration of the current post type is used.

## Individual block locking

Alongside template level locking, you can lock individual blocks; you can do this using a `lock` attribute on the attributes level. Block-level lock takes priority over the `templateLock` feature. Currently, you can lock moving and removing blocks.

**Block-level locking is an experimental feature that may be removed or change anytime.**
```js
attributes: {
// Prevent a block from being moved or removed.
lock: {
remove: true,
move: true,
}
}
```
_Options:_
- `remove` — Locks the ability of a block from being removed.
- `move` — Locks the ability of a block from being moved.

You can use this with `templateLock` to lock all blocks except a single block by using `false` in `remove` or `move`.

## Nested Templates

Container blocks like the columns blocks also support templates. This is achieved by assigning a nested template to the block.
Expand Down
56 changes: 56 additions & 0 deletions docs/reference-guides/data/data-core-block-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,62 @@ _Returns_

- `boolean`: Whether the given block type is allowed to be inserted.

### canMoveBlock

Determines if the given block is allowed to be moved.

_Parameters_

- _state_ `Object`: Editor state.
- _clientId_ `string`: The block client Id.
- _rootClientId_ `?string`: Optional root client ID of block list.

_Returns_

- `boolean`: Whether the given block is allowed to be moved.

### canMoveBlocks

Determines if the given blocks are allowed to be moved.

_Parameters_

- _state_ `Object`: Editor state.
- _clientIds_ `string`: The block client IDs to be moved.
- _rootClientId_ `?string`: Optional root client ID of block list.

_Returns_

- `boolean`: Whether the given blocks are allowed to be moved.

### canRemoveBlock

Determines if the given block is allowed to be deleted.

_Parameters_

- _state_ `Object`: Editor state.
- _clientId_ `string`: The block client Id.
- _rootClientId_ `?string`: Optional root client ID of block list.

_Returns_

- `boolean`: Whether the given block is allowed to be removed.

### canRemoveBlocks

Determines if the given blocks are allowed to be removed.

_Parameters_

- _state_ `Object`: Editor state.
- _clientIds_ `string`: The block client IDs to be removed.
- _rootClientId_ `?string`: Optional root client ID of block list.

_Returns_

- `boolean`: Whether the given blocks are allowed to be removed.

### didAutomaticChange

Returns true if the last change was an automatic change, false otherwise.
Expand Down
4 changes: 4 additions & 0 deletions packages/block-editor/src/components/block-actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default function BlockActions( {
canInsertBlockType,
getBlockRootClientId,
getBlocksByClientId,
canRemoveBlocks,
getTemplateLock,
} = useSelect( ( select ) => select( blockEditorStore ), [] );
const { getDefaultBlockName, getGroupingBlockName } = useSelect(
Expand All @@ -50,6 +51,8 @@ export default function BlockActions( {
rootClientId
);

const canRemove = canRemoveBlocks( clientIds, rootClientId );

const {
removeBlocks,
replaceBlocks,
Expand All @@ -67,6 +70,7 @@ export default function BlockActions( {
return children( {
canDuplicate,
canInsertDefaultBlock,
canRemove,
isLocked: !! getTemplateLock( rootClientId ),
rootClientId,
blocks,
Expand Down
14 changes: 11 additions & 3 deletions packages/block-editor/src/components/block-list/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function Block( { children, isHtml, ...props } ) {
function BlockListBlock( {
mode,
isLocked,
canRemove,
clientId,
isSelected,
isSelectionEnabled,
Expand Down Expand Up @@ -99,9 +100,9 @@ function BlockListBlock( {
attributes={ attributes }
setAttributes={ setAttributes }
insertBlocksAfter={ isLocked ? undefined : onInsertBlocksAfter }
onReplace={ isLocked ? undefined : onReplace }
onRemove={ isLocked ? undefined : onRemove }
mergeBlocks={ isLocked ? undefined : onMerge }
onReplace={ canRemove ? onReplace : undefined }
onRemove={ canRemove ? onRemove : undefined }
mergeBlocks={ canRemove ? onMerge : undefined }
clientId={ clientId }
isSelectionEnabled={ isSelectionEnabled }
toggleSelection={ toggleSelection }
Expand Down Expand Up @@ -191,10 +192,15 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => {
isSelectionEnabled,
getTemplateLock,
__unstableGetBlockWithoutInnerBlocks,
canRemoveBlock,
canMoveBlock,
} = select( blockEditorStore );
const block = __unstableGetBlockWithoutInnerBlocks( clientId );
const isSelected = isBlockSelected( clientId );
const templateLock = getTemplateLock( rootClientId );
const canRemove = canRemoveBlock( clientId, rootClientId );
const canMove = canMoveBlock( clientId, rootClientId );

// The fallback to `{}` is a temporary fix.
// This function should never be called when a block is not present in
// the state. It happens now because the order in withSelect rendering
Expand All @@ -207,6 +213,8 @@ const applyWithSelect = withSelect( ( select, { clientId, rootClientId } ) => {
mode: getBlockMode( clientId ),
isSelectionEnabled: isSelectionEnabled(),
isLocked: !! templateLock,
canRemove,
canMove,
// Users of the editor.BlockListBlock filter used to be able to
// access the block prop.
// Ideally these blocks would rely on the clientId prop only.
Expand Down
8 changes: 4 additions & 4 deletions packages/block-editor/src/components/block-mover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function BlockMover( {
isFirst,
isLast,
clientIds,
isLocked,
canMove,
isHidden,
rootClientId,
orientation,
Expand All @@ -37,7 +37,7 @@ function BlockMover( {
const onFocus = () => setIsFocused( true );
const onBlur = () => setIsFocused( false );

if ( isLocked || ( isFirst && isLast && ! rootClientId ) ) {
if ( ! canMove || ( isFirst && isLast && ! rootClientId ) ) {
return null;
}

Expand Down Expand Up @@ -100,7 +100,7 @@ export default withSelect( ( select, { clientIds } ) => {
getBlock,
getBlockIndex,
getBlockListSettings,
getTemplateLock,
canMoveBlocks,
getBlockOrder,
getBlockRootClientId,
} = select( blockEditorStore );
Expand All @@ -119,7 +119,7 @@ export default withSelect( ( select, { clientIds } ) => {

return {
blockType: block ? getBlockType( block.name ) : null,
isLocked: getTemplateLock( rootClientId ) === 'all',
canMove: canMoveBlocks( clientIds, rootClientId ),
rootClientId,
firstIndex,
isFirst,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const BLOCK_MOVER_DIRECTION_BOTTOM =
export const BlockMover = ( {
isFirst,
isLast,
isLocked,
canMove,
onMoveDown,
onMoveUp,
onLongMove,
Expand Down Expand Up @@ -86,7 +86,7 @@ export const BlockMover = ( {
if ( option && option.onSelect ) option.onSelect();
};

if ( isLocked || ( isFirst && isLast && ! rootClientId ) ) {
if ( ! canMove || ( isFirst && isLast && ! rootClientId ) ) {
return null;
}

Expand Down Expand Up @@ -130,7 +130,7 @@ export default compose(
withSelect( ( select, { clientIds } ) => {
const {
getBlockIndex,
getTemplateLock,
canMoveBlocks,
getBlockRootClientId,
getBlockOrder,
} = select( blockEditorStore );
Expand All @@ -149,7 +149,7 @@ export default compose(
numberOfBlocks: blockOrder.length - 1,
isFirst: firstIndex === 0,
isLast: lastIndex === blockOrder.length - 1,
isLocked: getTemplateLock( rootClientId ) === 'all',
canMove: canMoveBlocks( clientIds, rootClientId ),
rootClientId,
};
} ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ exports[`Block Mover Picker should match snapshot 1`] = `
<ForwardRef(ToolbarButton)
extraProps={
Object {
"hint": "Double tap to move the block up",
"hint": "Double tap to move the block to the left",
}
}
icon={
Expand All @@ -14,17 +14,19 @@ exports[`Block Mover Picker should match snapshot 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<Path
d="M12.5 3.9L6.7 9.7l1.1 1.1 4-4V20h1.4V6.7l4.5 4.1 1.1-1.1z"
d="M20 10.8H6.7l4.1-4.5-1.1-1.1-5.8 6.3 5.8 5.8 1.1-1.1-4-3.9H20z"
/>
</SVG>
}
isDisabled={false}
onClick={[MockFunction]}
onLongPress={[Function]}
title="Move block up from row NaN to row NaN"
title="Move block left from position 2 to position 1"
/>
<ForwardRef(ToolbarButton)
extraProps={
Object {
"hint": "Double tap to move the block down",
"hint": "Double tap to move the block to the right",
}
}
icon={
Expand All @@ -33,12 +35,14 @@ exports[`Block Mover Picker should match snapshot 1`] = `
xmlns="http://www.w3.org/2000/svg"
>
<Path
d="M16.2 13.2l-4 4V4h-1.5v13.3l-4.5-4.1-1 1.1 6.2 5.8 5.8-5.8-1-1.1z"
d="M14.3 6.7l-1.1 1.1 4 4H4v1.5h13.3l-4.1 4.4 1.1 1.1 5.8-6.3z"
/>
</SVG>
}
isDisabled={true}
onClick={[MockFunction]}
onLongPress={[Function]}
title="Move block down from row NaN to row NaN"
title="Move block right"
/>
<Picker
hideCancelButton={true}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,40 @@ import { BlockMover } from '../index';

describe( 'Block Mover Picker', () => {
it( 'renders without crashing', () => {
const wrapper = shallow( <BlockMover />, {
context: {
isFirst: false,
isLast: true,
isLocked: false,
numberOfBlocks: 2,
firstIndex: 1,
const props = {
isFirst: false,
isLast: true,
canMove: true,
numberOfBlocks: 2,
firstIndex: 1,

onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),
onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),

rootClientId: '',
isStackedHorizontally: true,
},
} );
rootClientId: '',
isStackedHorizontally: true,
};
const wrapper = shallow( <BlockMover { ...props } /> );
expect( wrapper ).toBeTruthy();
} );

it( 'should match snapshot', () => {
const wrapper = shallow( <BlockMover />, {
context: {
isFirst: false,
isLast: true,
isLocked: false,
numberOfBlocks: 2,
firstIndex: 1,
const props = {
isFirst: false,
isLast: true,
canMove: true,
numberOfBlocks: 2,
firstIndex: 1,

onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),
onMoveDown: jest.fn(),
onMoveUp: jest.fn(),
onLongPress: jest.fn(),

rootClientId: '',
isStackedHorizontally: true,
},
} );
rootClientId: '',
isStackedHorizontally: true,
};
const wrapper = shallow( <BlockMover { ...props } /> );
expect( wrapper ).toMatchSnapshot();
} );
} );

0 comments on commit 69e348e

Please sign in to comment.