diff --git a/blocks/inner-blocks/index.js b/blocks/inner-blocks/index.js
index 37ae97e13c71a7..6b1d56c44932db 100644
--- a/blocks/inner-blocks/index.js
+++ b/blocks/inner-blocks/index.js
@@ -3,8 +3,8 @@
*/
import { withContext } from '@wordpress/components';
-function InnerBlocks( { BlockList, layouts } ) {
- return ;
+function InnerBlocks( { BlockList, layouts, allowedBlocks, template } ) {
+ return ;
}
InnerBlocks = withContext( 'BlockList' )()( InnerBlocks );
diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js
index 43d145c4948ad6..39fdd044139063 100644
--- a/editor/components/block-list/block.js
+++ b/editor/components/block-list/block.js
@@ -591,7 +591,7 @@ export class BlockListBlock extends Component {
{ showSideInserter && (
-
+
-
@@ -60,7 +60,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = `
value="Write your story"
/>
-
@@ -84,7 +84,7 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = `
value=""
/>
-
diff --git a/editor/components/inserter-with-shortcuts/index.js b/editor/components/inserter-with-shortcuts/index.js
index 009c50056759be..2bbea70352c907 100644
--- a/editor/components/inserter-with-shortcuts/index.js
+++ b/editor/components/inserter-with-shortcuts/index.js
@@ -52,9 +52,13 @@ export default compose(
allowedBlockTypes,
};
} ),
- withSelect( ( select, { allowedBlockTypes } ) => ( {
- items: select( 'core/editor' ).getFrecentInserterItems( allowedBlockTypes, 4 ),
- } ) ),
+ withSelect( ( select, { allowedBlockTypes, rootUID } ) => {
+ const { getFrecentInserterItems, getSupportedBlocks } = select( 'core/editor' );
+ const supportedBlocks = getSupportedBlocks( rootUID, allowedBlockTypes );
+ return {
+ items: getFrecentInserterItems( supportedBlocks, 4 ),
+ };
+ } ),
withDispatch( ( dispatch, ownProps ) => {
const { uid, rootUID, layout } = ownProps;
diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js
index 6814c79aa598bd..b1ddc21ce0025c 100644
--- a/editor/components/inserter/index.js
+++ b/editor/components/inserter/index.js
@@ -87,11 +87,32 @@ class Inserter extends Component {
}
export default compose( [
- withSelect( ( select ) => ( {
- title: select( 'core/editor' ).getEditedPostAttribute( 'title' ),
- insertionPoint: select( 'core/editor' ).getBlockInsertionPoint(),
- selectedBlock: select( 'core/editor' ).getSelectedBlock(),
- } ) ),
+ withEditorSettings( ( settings ) => {
+ const { allowedBlockTypes, templateLock } = settings;
+
+ return {
+ allowedBlockTypes,
+ isLocked: !! templateLock,
+ };
+ } ),
+ withSelect( ( select, { allowedBlockTypes } ) => {
+ const {
+ getEditedPostAttribute,
+ getBlockInsertionPoint,
+ getSelectedBlock,
+ getSupportedBlocks,
+ } = select( 'core/editor' );
+
+ const insertionPoint = getBlockInsertionPoint();
+ const { rootUID } = insertionPoint;
+ const supportedBlocks = getSupportedBlocks( rootUID, allowedBlockTypes );
+ return {
+ title: getEditedPostAttribute( 'title' ),
+ insertionPoint,
+ selectedBlock: getSelectedBlock(),
+ hasSupportedBlocks: true === supportedBlocks || ! isEmpty( supportedBlocks ),
+ };
+ } ),
withDispatch( ( dispatch, ownProps ) => ( {
showInsertionPoint: dispatch( 'core/editor' ).showInsertionPoint,
hideInsertionPoint: dispatch( 'core/editor' ).hideInsertionPoint,
@@ -106,12 +127,4 @@ export default compose( [
return dispatch( 'core/editor' ).insertBlock( insertedBlock, index, rootUID );
},
} ) ),
- withEditorSettings( ( settings ) => {
- const { allowedBlockTypes, templateLock } = settings;
-
- return {
- hasSupportedBlocks: true === allowedBlockTypes || ! isEmpty( allowedBlockTypes ),
- isLocked: !! templateLock,
- };
- } ),
] )( Inserter );
diff --git a/editor/components/inserter/menu.js b/editor/components/inserter/menu.js
index f863ad697f3376..1d7c058a1fae28 100644
--- a/editor/components/inserter/menu.js
+++ b/editor/components/inserter/menu.js
@@ -344,10 +344,17 @@ export default compose(
};
} ),
withSelect( ( select, { allowedBlockTypes } ) => {
- const { getInserterItems, getFrecentInserterItems } = select( 'core/editor' );
+ const {
+ getBlockInsertionPoint,
+ getInserterItems,
+ getFrecentInserterItems,
+ getSupportedBlocks,
+ } = select( 'core/editor' );
+ const { rootUID } = getBlockInsertionPoint();
+ const supportedBlocks = getSupportedBlocks( rootUID, allowedBlockTypes );
return {
- items: getInserterItems( allowedBlockTypes ),
- frecentItems: getFrecentInserterItems( allowedBlockTypes ),
+ items: getInserterItems( supportedBlocks ),
+ frecentItems: getFrecentInserterItems( supportedBlocks ),
};
} ),
withDispatch( ( dispatch ) => ( {
diff --git a/editor/store/actions.js b/editor/store/actions.js
index 07a22bc3ec3a5f..c78cdc25ec9acc 100644
--- a/editor/store/actions.js
+++ b/editor/store/actions.js
@@ -666,3 +666,19 @@ export function insertDefaultBlock( attributes, rootUID, index ) {
isProvisional: true,
};
}
+
+/**
+ * Returns an action object that changes the nested settings of a given block.
+ *
+ * @param {string} id UID of the block whose nested setting.
+ * @param {Object} settings Object with the new settings for the nested block.
+ *
+ * @return {Object} Action object
+ */
+export function updateBlockListSettings( id, settings ) {
+ return {
+ type: 'UPDATE_BLOCK_LIST_SETTINGS',
+ id,
+ settings,
+ };
+}
diff --git a/editor/store/reducer.js b/editor/store/reducer.js
index ca558159fa15e6..ee6dfab934abec 100644
--- a/editor/store/reducer.js
+++ b/editor/store/reducer.js
@@ -1006,6 +1006,42 @@ export const sharedBlocks = combineReducers( {
},
} );
+/**
+ * Reducer that for each block uid stores an object that represents its nested settings.
+ * E.g: what blocks can be nested inside a block.
+ *
+ * @param {Object} state Current state.
+ * @param {Object} action Dispatched action.
+ *
+ * @return {Object} Updated state.
+ */
+export const blockListSettings = ( state = {}, action ) => {
+ switch ( action.type ) {
+ // even if the replaced blocks have the same uid our logic should correct the state.
+ case 'REPLACE_BLOCKS' :
+ case 'REMOVE_BLOCKS': {
+ return omit( state, action.uids );
+ }
+ case 'UPDATE_BLOCK_LIST_SETTINGS': {
+ const { id, settings } = action;
+ if ( id && ! settings ) {
+ return omit( state, id );
+ }
+ const blockSettings = state[ id ];
+ const updateIsRequired = ! isEqual( blockSettings, settings );
+ if ( updateIsRequired ) {
+ return {
+ ...state,
+ [ id ]: {
+ ...settings,
+ },
+ };
+ }
+ }
+ }
+ return state;
+};
+
export default optimist( combineReducers( {
editor,
currentPost,
@@ -1013,6 +1049,7 @@ export default optimist( combineReducers( {
blockSelection,
provisionalBlockUID,
blocksMode,
+ blockListSettings,
isInsertionPointVisible,
preferences,
saving,
diff --git a/editor/store/selectors.js b/editor/store/selectors.js
index 8d58898ce37597..5c7978d7459972 100644
--- a/editor/store/selectors.js
+++ b/editor/store/selectors.js
@@ -6,6 +6,7 @@ import {
first,
get,
has,
+ intersection,
last,
reduce,
size,
@@ -1590,3 +1591,44 @@ export function inSomeHistory( state, predicate ) {
beforeState && predicate( beforeState )
) );
}
+
+/**
+ * Returns the Block List settings of a block if any.
+ *
+ * @param {Object} state Editor state.
+ * @param {?string} uid Block UID.
+ *
+ * @return {?Object} Block settings of the block if set.
+ */
+export function getBlockListSettings( state, uid ) {
+ return state.blockListSettings[ uid ];
+}
+
+/**
+ * Determines the blocks that can be nested inside a given block. Or globally if a block is not specified.
+ *
+ * @param {Object} state Global application state.
+ * @param {?string} uid Block UID.
+ * @param {string[]|boolean} globallyEnabledBlockTypes Globally enabled block types, or true/false to enable/disable all types.
+ *
+ * @return {string[]|boolean} Blocks that can be nested inside the block with the specified uid, or true/false to enable/disable all types.
+ */
+export function getSupportedBlocks( state, uid, globallyEnabledBlockTypes ) {
+ if ( ! globallyEnabledBlockTypes ) {
+ return false;
+ }
+
+ const supportedNestedBlocks = get( getBlockListSettings( state, uid ), [ 'supportedBlocks' ] );
+ if ( supportedNestedBlocks === true || supportedNestedBlocks === undefined ) {
+ return globallyEnabledBlockTypes;
+ }
+
+ if ( ! supportedNestedBlocks ) {
+ return false;
+ }
+
+ if ( globallyEnabledBlockTypes === true ) {
+ return supportedNestedBlocks;
+ }
+ return intersection( globallyEnabledBlockTypes, supportedNestedBlocks );
+}
diff --git a/editor/store/test/actions.js b/editor/store/test/actions.js
index 97a0291b9f23db..12462f8d91f7e5 100644
--- a/editor/store/test/actions.js
+++ b/editor/store/test/actions.js
@@ -42,6 +42,7 @@ import {
createErrorNotice,
createWarningNotice,
removeNotice,
+ updateBlockListSettings,
} from '../actions';
describe( 'actions', () => {
@@ -530,4 +531,22 @@ describe( 'actions', () => {
} );
} );
} );
+
+ describe( 'updateBlockListSettings', () => {
+ it( 'should return the UPDATE_BLOCK_LIST_SETTINGS with undefined settings', () => {
+ expect( updateBlockListSettings( 'chicken' ) ).toEqual( {
+ type: 'UPDATE_BLOCK_LIST_SETTINGS',
+ id: 'chicken',
+ settings: undefined,
+ } );
+ } );
+
+ it( 'should return the UPDATE_BLOCK_LIST_SETTINGS action with the passed settings', () => {
+ expect( updateBlockListSettings( 'chicken', { chicken: 'ribs' } ) ).toEqual( {
+ type: 'UPDATE_BLOCK_LIST_SETTINGS',
+ id: 'chicken',
+ settings: { chicken: 'ribs' },
+ } );
+ } );
+ } );
} );
diff --git a/editor/store/test/reducer.js b/editor/store/test/reducer.js
index 005173b127b791..26b0354cd02034 100644
--- a/editor/store/test/reducer.js
+++ b/editor/store/test/reducer.js
@@ -35,6 +35,7 @@ import {
isInsertionPointVisible,
sharedBlocks,
template,
+ blockListSettings,
} from '../reducer';
describe( 'state', () => {
@@ -2189,4 +2190,81 @@ describe( 'state', () => {
expect( state ).toEqual( { isValid: true, template: [] } );
} );
} );
+
+ describe( 'blockListSettings', () => {
+ it( 'should add new settings', () => {
+ const original = deepFreeze( {} );
+ const state = blockListSettings( original, {
+ type: 'UPDATE_BLOCK_LIST_SETTINGS',
+ id: 'chicken',
+ settings: {
+ chicken: 'ribs',
+ },
+ } );
+ expect( state ).toEqual( {
+ chicken: {
+ chicken: 'ribs',
+ },
+ } );
+ } );
+
+ it( 'should update the settings of a block', () => {
+ const original = deepFreeze( {
+ chicken: {
+ chicken: 'ribs',
+ },
+ otherBlock: {
+ setting1: true,
+ },
+ } );
+ const state = blockListSettings( original, {
+ type: 'UPDATE_BLOCK_LIST_SETTINGS',
+ id: 'chicken',
+ settings: {
+ ribs: 'not-chicken',
+ },
+ } );
+ expect( state ).toEqual( {
+ chicken: {
+ ribs: 'not-chicken',
+ },
+ otherBlock: {
+ setting1: true,
+ },
+ } );
+ } );
+
+ it( 'should remove the settings of a block when it is replaced', () => {
+ const original = deepFreeze( {
+ chicken: {
+ chicken: 'ribs',
+ },
+ otherBlock: {
+ setting1: true,
+ },
+ } );
+ const state = blockListSettings( original, {
+ type: 'REPLACE_BLOCKS',
+ uids: [ 'otherBlock' ],
+ } );
+ expect( state ).toEqual( {
+ chicken: {
+ chicken: 'ribs',
+ },
+ } );
+ } );
+
+ it( 'should remove the settings of a block when it is removed', () => {
+ const original = deepFreeze( {
+ otherBlock: {
+ setting1: true,
+ },
+ } );
+ const state = blockListSettings( original, {
+ type: 'REPLACE_BLOCKS',
+ uids: [ 'otherBlock' ],
+ } );
+ expect( state ).toEqual( {} );
+ } );
+ } );
} );
diff --git a/editor/store/test/selectors.js b/editor/store/test/selectors.js
index df5d72efadb783..5060f28a8111a9 100644
--- a/editor/store/test/selectors.js
+++ b/editor/store/test/selectors.js
@@ -84,6 +84,8 @@ const {
isValidTemplate,
getTemplate,
getTemplateLock,
+ getBlockListSettings,
+ getSupportedBlocks,
POST_UPDATE_TRANSACTION_ID,
isPermalinkEditable,
getPermalink,
@@ -3253,4 +3255,119 @@ describe( 'selectors', () => {
expect( getPermalinkParts( state ) ).toEqual( parts );
} );
} );
+
+ describe( 'getBlockListSettings', () => {
+ it( 'should return the settings of a block', () => {
+ const state = {
+ blockListSettings: {
+ chicken: {
+ setting1: false,
+ },
+ ribs: {
+ setting2: true,
+ },
+ },
+ };
+
+ expect( getBlockListSettings( state, 'chicken' ) ).toEqual( {
+ setting1: false,
+ } );
+ } );
+
+ it( 'should return undefined if settings for the block don\'t exist', () => {
+ const state = {
+ blockListSettings: {},
+ };
+
+ expect( getBlockListSettings( state, 'chicken' ) ).toBe( undefined );
+ } );
+ } );
+
+ describe( 'getSupportedBlocks', () => {
+ it( 'should return false if all blocks are disabled globally', () => {
+ const state = {
+ blockListSettings: {
+ block1: {
+ supportedBlocks: [ 'core/block1' ],
+ },
+ },
+ };
+
+ expect( getSupportedBlocks( state, 'block1', false ) ).toBe( false );
+ } );
+
+ it( 'should return the supportedBlocks of root block if all blocks are supported globally', () => {
+ const state = {
+ blockListSettings: {
+ block1: {
+ supportedBlocks: [ 'core/block1' ],
+ },
+ },
+ };
+
+ expect( getSupportedBlocks( state, 'block1', true ) ).toEqual( [ 'core/block1' ] );
+ } );
+
+ it( 'should return the globally supported blocks if all blocks are enable inside the root block', () => {
+ const state = {
+ blockListSettings: {
+ block1: {
+ supportedBlocks: true,
+ },
+ },
+ };
+
+ expect( getSupportedBlocks( state, 'block1', [ 'core/block1' ] ) ).toEqual( [ 'core/block1' ] );
+ } );
+
+ it( 'should return the globally supported blocks if the root block does not sets the supported blocks', () => {
+ const state = {
+ blockListSettings: {
+ block1: {
+ chicken: 'ribs',
+ },
+ },
+ };
+
+ expect( getSupportedBlocks( state, 'block1', [ 'core/block1' ] ) ).toEqual( [ 'core/block1' ] );
+ } );
+
+ it( 'should return the globally supported blocks if there are no settings for the root block', () => {
+ const state = {
+ blockListSettings: {
+ block1: {
+ supportedBlocks: true,
+ },
+ },
+ };
+
+ expect( getSupportedBlocks( state, 'block2', [ 'core/block1' ] ) ).toEqual( [ 'core/block1' ] );
+ } );
+
+ it( 'should return false if all blocks are disabled inside the root block ', () => {
+ const state = {
+ blockListSettings: {
+ block1: {
+ supportedBlocks: false,
+ },
+ },
+ };
+
+ expect( getSupportedBlocks( state, 'block1', [ 'core/block1' ] ) ).toBe( false );
+ } );
+
+ it( 'should return the intersection of globally supported blocks with the supported blocks of the root block if both sets are defined', () => {
+ const state = {
+ blockListSettings: {
+ block1: {
+ supportedBlocks: [ 'core/block1', 'core/block2', 'core/block3' ],
+ },
+ },
+ };
+
+ expect( getSupportedBlocks( state, 'block1', [ 'core/block2', 'core/block4', 'core/block5' ] ) ).toEqual(
+ [ 'core/block2' ]
+ );
+ } );
+ } );
} );
diff --git a/editor/utils/block-list.js b/editor/utils/block-list.js
index 0f68869c783d8a..2bbfe34cdc326e 100644
--- a/editor/utils/block-list.js
+++ b/editor/utils/block-list.js
@@ -1,12 +1,16 @@
/**
* External dependencies
*/
-import { noop } from 'lodash';
+import { isEqual, noop, omit } from 'lodash';
/**
* WordPress dependencies
*/
-import { Component } from '@wordpress/element';
+import { Component, compose } from '@wordpress/element';
+import {
+ synchronizeBlocksWithTemplate,
+} from '@wordpress/blocks';
+import { withSelect, withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@@ -35,33 +39,87 @@ const INNER_BLOCK_LIST_CACHE = {};
*/
export function createInnerBlockList( uid, renderBlockMenu = noop ) {
if ( ! INNER_BLOCK_LIST_CACHE[ uid ] ) {
- INNER_BLOCK_LIST_CACHE[ uid ] = [
- // The component class:
- class extends Component {
- componentWillMount() {
- INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]++;
+ const InnerBlockListComponent = class extends Component {
+ componentWillReceiveProps( nextProps ) {
+ this.updateNestedSettings( {
+ supportedBlocks: nextProps.allowedBlocks,
+ } );
+ }
+
+ componentWillUnmount() {
+ // If, after decrementing the tracking count, there are no
+ // remaining instances of the component, remove from cache.
+ if ( ! INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]-- ) {
+ delete INNER_BLOCK_LIST_CACHE[ uid ];
}
+ }
+
+ componentDidMount() {
+ INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]++;
+ this.updateNestedSettings( {
+ supportedBlocks: this.props.allowedBlocks,
+ } );
+ this.insertTemplateBlocks( this.props.template );
+ }
- componentWillUnmount() {
- // If, after decrementing the tracking count, there are no
- // remaining instances of the component, remove from cache.
- if ( ! INNER_BLOCK_LIST_CACHE[ uid ][ 1 ]-- ) {
- delete INNER_BLOCK_LIST_CACHE[ uid ];
- }
+ insertTemplateBlocks( template ) {
+ const { block, insertBlocks } = this.props;
+ if ( template && ! block.innerBlocks.length ) {
+ // synchronizeBlocksWithTemplate( [], template ) parses the template structure,
+ // and returns/creates the necessary blocks to represent it.
+ insertBlocks( synchronizeBlocksWithTemplate( [], template ) );
}
+ }
- render() {
- return (
-
- );
+ updateNestedSettings( newSettings ) {
+ if ( ! isEqual( this.props.blockListSettings, newSettings ) ) {
+ this.props.updateNestedSettings( newSettings );
}
- },
+ }
- // A counter tracking active mounted instances:
- 0,
+ render() {
+ return (
+
+ );
+ }
+ };
+
+ const InnerBlockListComponentContainer = compose(
+ withSelect( ( select ) => {
+ const { getBlock, getBlockListSettings } = select( 'core/editor' );
+ return {
+ block: getBlock( uid ),
+ blockListSettings: getBlockListSettings( uid ),
+ };
+ } ),
+ withDispatch( ( dispatch ) => {
+ const { insertBlocks, updateBlockListSettings } = dispatch( 'core/editor' );
+ return {
+ insertBlocks( blocks ) {
+ dispatch( insertBlocks( blocks, undefined, uid ) );
+ },
+ updateNestedSettings( settings ) {
+ dispatch( updateBlockListSettings( uid, settings ) );
+ },
+ };
+ } ),
+ )( InnerBlockListComponent );
+
+ INNER_BLOCK_LIST_CACHE[ uid ] = [
+ InnerBlockListComponentContainer,
+ 0, // A counter tracking active mounted instances:
];
}