-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Fix: Restrict block insert, move, and replace attending to allowedBlocks, locking, and child blocks #14003
Fix: Restrict block insert, move, and replace attending to allowedBlocks, locking, and child blocks #14003
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { castArray } from 'lodash'; | ||
import { castArray, first } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
|
@@ -13,6 +13,25 @@ import { getDefaultBlockName, createBlock } from '@wordpress/blocks'; | |
*/ | ||
import { select } from './controls'; | ||
|
||
/** | ||
* Generator which will yield a default block insert action if there | ||
* are no other blocks at the root of the editor. This generator should be used | ||
* in actions which may result in no blocks remaining in the editor (removal, | ||
* replacement, etc). | ||
*/ | ||
function* ensureDefaultBlock() { | ||
const count = yield select( | ||
'core/block-editor', | ||
'getBlockCount', | ||
); | ||
|
||
// To avoid a focus loss when removing the last block, assure there is | ||
// always a default block if the last of the blocks have been removed. | ||
if ( count === 0 ) { | ||
yield insertDefaultBlock(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns an action object used in signalling that blocks state should be | ||
* reset to the specified array of blocks, taking precedence over any other | ||
|
@@ -202,15 +221,36 @@ export function toggleSelection( isSelectionEnabled = true ) { | |
* @param {(string|string[])} clientIds Block client ID(s) to replace. | ||
* @param {(Object|Object[])} blocks Replacement block(s). | ||
* | ||
* @return {Object} Action object. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
* @yields {Object} Action object. | ||
*/ | ||
export function replaceBlocks( clientIds, blocks ) { | ||
return { | ||
export function* replaceBlocks( clientIds, blocks ) { | ||
clientIds = castArray( clientIds ); | ||
blocks = castArray( blocks ); | ||
const rootClientId = yield select( | ||
'core/block-editor', | ||
'getBlockRootClientId', | ||
first( clientIds ) | ||
); | ||
// Replace is valid if the new blocks can be inserted in the root block. | ||
for ( let index = 0; index < blocks.length; index++ ) { | ||
const block = blocks[ index ]; | ||
const canInsertBlock = yield select( | ||
'core/block-editor', | ||
'canInsertBlockType', | ||
block.name, | ||
rootClientId | ||
); | ||
if ( ! canInsertBlock ) { | ||
return; | ||
} | ||
} | ||
yield { | ||
type: 'REPLACE_BLOCKS', | ||
clientIds: castArray( clientIds ), | ||
blocks: castArray( blocks ), | ||
clientIds, | ||
blocks, | ||
time: Date.now(), | ||
}; | ||
yield* ensureDefaultBlock(); | ||
} | ||
|
||
/** | ||
|
@@ -256,16 +296,51 @@ export const moveBlocksUp = createOnMove( 'MOVE_BLOCKS_UP' ); | |
* @param {?string} toRootClientId Root client ID destination. | ||
* @param {number} index The index to move the block into. | ||
* | ||
* @return {Object} Action object. | ||
* @yields {Object} Action object. | ||
*/ | ||
export function moveBlockToPosition( clientId, fromRootClientId, toRootClientId, index ) { | ||
return { | ||
export function* moveBlockToPosition( clientId, fromRootClientId, toRootClientId, index ) { | ||
const templateLock = yield select( | ||
'core/block-editor', | ||
'getTemplateLock', | ||
fromRootClientId | ||
); | ||
|
||
// If locking is equal to all on the original clientId (fromRootClientId), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Locking is checked in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we need this condition -- If the user is moving the block from a parent with locking=all to a block without locking, canInsertBlockType will return true, but the move is not possible. |
||
// it is not possible to move the block to any other position. | ||
if ( templateLock === 'all' ) { | ||
return; | ||
} | ||
|
||
const action = { | ||
type: 'MOVE_BLOCK_TO_POSITION', | ||
fromRootClientId, | ||
toRootClientId, | ||
clientId, | ||
index, | ||
}; | ||
// If moving inside the same root block the move is always possible. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This too seems contrary in the same sense as #14003 (comment) , and it could produce simpler code to avoid specialized conditions. I could see an argument being made that this helps as an optimized majority case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @aduth although this could be seen as optimization as the majority of cases will return here, this is also required. |
||
if ( fromRootClientId === toRootClientId ) { | ||
yield action; | ||
return; | ||
} | ||
|
||
const blockName = yield select( | ||
'core/block-editor', | ||
'getBlockName', | ||
clientId | ||
); | ||
|
||
const canInsertBlock = yield select( | ||
'core/block-editor', | ||
'canInsertBlockType', | ||
blockName, | ||
toRootClientId | ||
); | ||
|
||
// If moving to other parent block, the move is possible if we can insert a block of the same type inside the new parent block. | ||
if ( canInsertBlock ) { | ||
yield action; | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -279,8 +354,18 @@ export function moveBlockToPosition( clientId, fromRootClientId, toRootClientId, | |
* | ||
* @return {Object} Action object. | ||
*/ | ||
export function insertBlock( block, index, rootClientId, updateSelection = true ) { | ||
return insertBlocks( [ block ], index, rootClientId, updateSelection ); | ||
export function insertBlock( | ||
block, | ||
index, | ||
rootClientId, | ||
updateSelection = true, | ||
) { | ||
return insertBlocks( | ||
[ block ], | ||
index, | ||
rootClientId, | ||
updateSelection | ||
); | ||
} | ||
|
||
/** | ||
|
@@ -292,17 +377,37 @@ export function insertBlock( block, index, rootClientId, updateSelection = true | |
* @param {?string} rootClientId Optional root client ID of block list on which to insert. | ||
* @param {?boolean} updateSelection If true block selection will be updated. If false, block selection will not change. Defaults to true. | ||
* | ||
* @return {Object} Action object. | ||
*/ | ||
export function insertBlocks( blocks, index, rootClientId, updateSelection = true ) { | ||
return { | ||
type: 'INSERT_BLOCKS', | ||
blocks: castArray( blocks ), | ||
index, | ||
rootClientId, | ||
time: Date.now(), | ||
updateSelection, | ||
}; | ||
* @return {Object} Action object. | ||
*/ | ||
export function* insertBlocks( | ||
jorgefilipecosta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
blocks, | ||
index, | ||
rootClientId, | ||
updateSelection = true | ||
) { | ||
blocks = castArray( blocks ); | ||
const allowedBlocks = []; | ||
for ( const block of blocks ) { | ||
const isValid = yield select( | ||
'core/block-editor', | ||
'canInsertBlockType', | ||
block.name, | ||
rootClientId | ||
); | ||
if ( isValid ) { | ||
allowedBlocks.push( block ); | ||
} | ||
} | ||
if ( allowedBlocks.length ) { | ||
return { | ||
type: 'INSERT_BLOCKS', | ||
blocks: allowedBlocks, | ||
index, | ||
rootClientId, | ||
time: Date.now(), | ||
updateSelection, | ||
}; | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -394,16 +499,9 @@ export function* removeBlocks( clientIds, selectPrevious = true ) { | |
clientIds, | ||
}; | ||
|
||
const count = yield select( | ||
'core/block-editor', | ||
'getBlockCount', | ||
); | ||
|
||
// To avoid a focus loss when removing the last block, assure there is | ||
// always a default block if the last of the blocks have been removed. | ||
if ( count === 0 ) { | ||
yield insertDefaultBlock(); | ||
} | ||
yield* ensureDefaultBlock(); | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given #14754 and the possibility we may want to try to find a different way to "ensure" the default block, I think it's wise to not expose this as an exported action (i.e. as-is).