Skip to content

Commit

Permalink
Add allowedBlocksMiddleware
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgefilipecosta committed Nov 15, 2018
1 parent 3c8dbd5 commit f1d4502
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 11 deletions.
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

0 comments on commit f1d4502

Please sign in to comment.