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

Add allowedBlocksMiddleware #7301

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/editor/src/components/inner-blocks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ InnerBlocks = compose( [
replaceInnerBlocks( blocks ) {
const clientIds = map( block.innerBlocks, 'clientId' );
if ( clientIds.length ) {
replaceBlocks( clientIds, blocks );
replaceBlocks( clientIds, blocks, true );
} else {
insertBlocks( blocks, undefined, clientId, templateInsertUpdatesSelection );
insertBlocks( blocks, undefined, clientId, templateInsertUpdatesSelection, true );
}
},
updateNestedSettings( settings ) {
Expand Down
26 changes: 18 additions & 8 deletions packages/editor/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,19 @@ export function toggleSelection( isSelectionEnabled = true ) {
* Returns an action object signalling that a blocks should be replaced with
* one or more replacement blocks.
*
* @param {(string|string[])} clientIds Block client ID(s) to replace.
* @param {(Object|Object[])} blocks Replacement block(s).
* @param {(string|string[])} clientIds Block client ID(s) to replace.
* @param {(Object|Object[])} blocks Replacement block(s).
* @param {?boolean} ignoreAllowedBlocksValidation If true the replacement will occur even if some of the new blocks were not allowed e.g: because of allowed blocks restrictions.
*
* @return {Object} Action object.
*/
export function replaceBlocks( clientIds, blocks ) {
export function replaceBlocks( clientIds, blocks, ignoreAllowedBlocksValidation = false ) {
return {
type: 'REPLACE_BLOCKS',
clientIds: castArray( clientIds ),
blocks: castArray( blocks ),
time: Date.now(),
ignoreAllowedBlocksValidation,
};
}

Expand Down Expand Up @@ -305,21 +307,29 @@ export function insertBlock( block, index, rootClientId, updateSelection = true
* Returns an action object used in signalling that an array of blocks should
* be inserted, optionally at a specific index respective a root block list.
*
* @param {Object[]} blocks Block objects to insert.
* @param {?number} index Index at which block should be inserted.
* @param {?string} rootClientId Optional root cliente 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.
* @param {Object[]} blocks Block objects to insert.
* @param {?number} index Index at which block should be inserted.
* @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.
* @param {?boolean} ignoreAllowedBlocksValidation If true the block will be inserted even if the insertion was not allowed e.g: because of allowed blocks restrictions.
*
* @return {Object} Action object.
*/
export function insertBlocks( blocks, index, rootClientId, updateSelection = true ) {
export function insertBlocks(
blocks,
index,
rootClientId,
updateSelection = true,
ignoreAllowedBlocksValidation = false
) {
return {
type: 'INSERT_BLOCKS',
blocks: castArray( blocks ),
index,
rootClientId,
time: Date.now(),
updateSelection,
ignoreAllowedBlocksValidation,
};
}

Expand Down
73 changes: 72 additions & 1 deletion packages/editor/src/store/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,82 @@
*/
import refx from 'refx';
import multi from 'redux-multi';
import { flowRight } from 'lodash';
import { every, filter, first, flowRight } from 'lodash';

/**
* Internal dependencies
*/
import effects from './effects';
import { canInsertBlockType, getBlockName, getBlockRootClientId, getTemplateLock } from './selectors';

/**
* The allowedBlocksMiddleware middleware makes sure we never add a block when that addition is not possible.
* In order to accomplish this validation allowedBlocksMiddleware makes use of canInsertBlockType selector
* and custom logic for replace, move and multi-block insertion.
* The primary objective of middleware is to make sure the store never gets in an inconsistent state with a block
* added inside in a forbidden area. So for example, if an external plugin tries to insert blocks when a locking exists
* the action will be discarded.
*
* @param {Object} store Middleware Store Object.
* @return {Function} Redux Middleware.
*/
const allowedBlocksMiddleware = ( store ) => ( next ) => ( action ) => {
if ( action.ignoreAllowedBlocksValidation ) {
next( action );
return;
}
switch ( action.type ) {
// When inserting we allow the action if at least one of the blocks can be inserted.
// Blocks that can not be inserted are removed from the action.
case 'INSERT_BLOCKS': {
const allowedBlocks = filter( action.blocks, ( block ) =>
block &&
canInsertBlockType( store.getState(), block.name, action.rootClientId )
);
if ( allowedBlocks.length ) {
next( {
...action,
blocks: allowedBlocks,
} );
}
return;
}
case 'MOVE_BLOCK_TO_POSITION': {
const { fromRootClientId, toRootClientId, clientId } = action;
const state = store.getState();
const blockName = getBlockName( state, clientId );

// If locking is equal to all on the original clientId (fromRootClientId) it is not possible to move the block to any other position.
// In the other cases (locking !== all ), if moving inside the same block the move is always possible
// 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 (
getTemplateLock( state, fromRootClientId ) !== 'all' &&
( fromRootClientId === toRootClientId || canInsertBlockType( store.getState(), blockName, toRootClientId ) )
) {
next( action );
}
return;
}
case 'REPLACE_BLOCKS': {
const clientId = getBlockRootClientId( store.getState(), first( action.clientIds ) );
// Replace is valid if the new blocks can be inserted in the root block
// or if we had a block of the same type in the position of the block being replaced.
const isOperationValid = every( action.blocks, ( block, index ) => {
if ( canInsertBlockType( store.getState(), block.name, clientId ) ) {
return true;
}
const clientIdToReplace = action.clientIds[ index ];
const nameOfBlockToReplace = clientIdToReplace && getBlockName( store.getState(), clientIdToReplace );
return nameOfBlockToReplace && nameOfBlockToReplace === block.name;
} );
if ( isOperationValid ) {
next( action );
}
return;
}
}
next( action );
};

/**
* Applies the custom middlewares used specifically in the editor module.
Expand All @@ -21,6 +91,7 @@ function applyMiddlewares( store ) {
const middlewares = [
refx( effects ),
multi,
allowedBlocksMiddleware,
];

let enhancedDispatch = () => {
Expand Down