Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Block type level lock control #32457

Merged
merged 17 commits into from
Sep 10, 2021
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: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool work.

It looks like the documentation needs to be fixed. It differs from the PR's description and feels broken here:

Screen Shot 2021-10-06 at 21 21 33

// 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The canRemoveBlock block should also be used on the transforms verification. If a block can not be removed I should also not be able to transform it into another block and right now we can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now resolved with 15fac68


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 @@ -72,6 +72,7 @@ function Block( { children, isHtml, ...props } ) {
function BlockListBlock( {
mode,
isLocked,
canRemove,
clientId,
isSelected,
isSelectionEnabled,
Expand Down Expand Up @@ -100,9 +101,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 }
jorgefilipecosta marked this conversation as resolved.
Show resolved Hide resolved
mergeBlocks={ canRemove ? onMerge : undefined }
clientId={ clientId }
isSelectionEnabled={ isSelectionEnabled }
toggleSelection={ toggleSelection }
Expand Down Expand Up @@ -195,10 +196,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 @@ -211,6 +217,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 ) ) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canMove takes isLocked into consideration.

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 } /> );
Comment on lines +13 to +46
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those tests were never running correctly, they're fixed now.

expect( wrapper ).toMatchSnapshot();
} );
} );