diff --git a/blocks/inner-blocks/index.js b/blocks/inner-blocks/index.js index 37ae97e13c71a..6b1d56c44932d 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/core-blocks/cover-image/editor.scss b/core-blocks/cover-image/editor.scss index 29db22108e5d9..9774e8fc62bc6 100644 --- a/core-blocks/cover-image/editor.scss +++ b/core-blocks/cover-image/editor.scss @@ -22,10 +22,52 @@ } &.has-left-content .block-rich-text__inline-toolbar { - justify-content: flex-start; + display: inline-block; } &.has-right-content .block-rich-text__inline-toolbar{ - justify-content: flex-end; + display: inline-block; + } + + .editor-block-list__layout { + width: 100%; + } + + .wp-block-cover-image__inner-container { + width: calc( 100% - 70px ); + margin-left: auto; + margin-right: auto; + // avoid text align inherit from cover image align. + text-align: left; + + .editor-inserter-with-shortcuts .components-icon-button, + .editor-block-list__empty-block-inserter .components-icon-button { + color: $white; + } + .editor-default-block-appender { + &:hover { + .components-icon-button { + color: $white; + } + } + + .components-icon-button { + color: $light-gray-900; + } + } + + + .editor-block-list__insertion-point-inserter:before { + background: $white; + } + + p.wp-block-subhead { + color: $light-gray-100; + } + + .components-draggable__clone > .editor-block-list__block > .editor-block-list__block-draggable, + .editor-block-list__block-draggable > .editor-block-list__block-draggable-inner { + background-color: rgba( $white, 0.3 ); + } } } diff --git a/core-blocks/cover-image/index.js b/core-blocks/cover-image/index.js index eb06175ee138f..aed0715b3fff8 100644 --- a/core-blocks/cover-image/index.js +++ b/core-blocks/cover-image/index.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { isEmpty } from 'lodash'; +import { omit } from 'lodash'; /** * WordPress dependencies @@ -17,8 +17,8 @@ import { BlockAlignmentToolbar, ImagePlaceholder, MediaUpload, - AlignmentToolbar, RichText, + InnerBlocks, } from '@wordpress/blocks'; /** @@ -30,21 +30,12 @@ import './style.scss'; const validAlignments = [ 'left', 'center', 'right', 'wide', 'full' ]; const blockAttributes = { - title: { - type: 'array', - source: 'children', - selector: 'p', - }, url: { type: 'string', }, align: { type: 'string', }, - contentAlign: { - type: 'string', - default: 'center', - }, id: { type: 'number', }, @@ -99,8 +90,8 @@ export const settings = { } }, - edit( { attributes, setAttributes, isSelected, className } ) { - const { url, title, align, contentAlign, id, hasParallax, dimRatio } = attributes; + edit( { attributes, setAttributes, className } ) { + const { align, url, id, hasParallax, dimRatio } = attributes; const updateAlignment = ( nextAlign ) => setAttributes( { align: nextAlign } ); const onSelectImage = ( media ) => setAttributes( { url: media.url, id: media.id } ); const toggleParallax = () => setAttributes( { hasParallax: ! hasParallax } ); @@ -109,7 +100,6 @@ export const settings = { const style = backgroundImageStyles( url ); const classes = classnames( className, - contentAlign !== 'center' && `has-${ contentAlign }-content`, dimRatioToClass( dimRatio ), { 'has-background-dim': dimRatio !== 0, @@ -124,12 +114,6 @@ export const settings = { value={ align } onChange={ updateAlignment } /> - { - setAttributes( { contentAlign: nextAlign } ); - } } - /> setAttributes( { title: value } ) } - inlineToolbar - /> - ) : __( 'Cover Image' ); + const icon = 'format-image'; + const label = __( 'Cover Image' ); return ( @@ -198,23 +174,26 @@ export const settings = { style={ style } className={ classes } > - { title || isSelected ? ( - setAttributes( { title: value } ) } - inlineToolbar +
+ - ) : null } +
); }, save( { attributes, className } ) { - const { url, title, hasParallax, dimRatio, align, contentAlign } = attributes; + const { url, hasParallax, dimRatio, align } = attributes; const style = backgroundImageStyles( url ); const classes = classnames( className, @@ -222,21 +201,68 @@ export const settings = { { 'has-background-dim': dimRatio !== 0, 'has-parallax': hasParallax, - [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', }, align ? `align${ align }` : null, ); return (
- { title && title.length > 0 && ( - - ) } +
+ +
); }, deprecated: [ { + attributes: { + ...blockAttributes, + title: { + type: 'array', + source: 'children', + selector: 'p', + }, + contentAlign: { + type: 'string', + default: 'center', + }, + }, + + save( { attributes, className } ) { + const { url, title, hasParallax, dimRatio, align, contentAlign } = attributes; + const style = backgroundImageStyles( url ); + const classes = classnames( + className, + dimRatioToClass( dimRatio ), + { + 'has-background-dim': dimRatio !== 0, + 'has-parallax': hasParallax, + [ `has-${ contentAlign }-content` ]: contentAlign !== 'center', + }, + align ? `align${ align }` : null, + ); + + return ( +
+ { title && title.length > 0 && ( + + ) } +
+ ); + }, + + migrate( attributes ) { + return [ + omit( attributes, [ 'title', 'contentAlign' ] ), + [ createBlock( 'core/paragraph', { + content: attributes.title, + align: attributes.contentAlign, + fontSize: 'large', + placeholder: __( 'Write title…' ), + } ) ], + ]; + }, + }, { attributes: { ...blockAttributes, title: { diff --git a/core-blocks/cover-image/style.scss b/core-blocks/cover-image/style.scss index 244f8dba2bcbe..7edd9f83a8225 100644 --- a/core-blocks/cover-image/style.scss +++ b/core-blocks/cover-image/style.scss @@ -79,4 +79,18 @@ max-width: $content-width / 2; width: 100%; } + + .wp-block-cover-image__inner-container { + width: calc( 100% - 70px ); + color: $light-gray-100; + z-index: z-index( '.wp-block-cover-image__inner-container' ); + p { + margin-top: 0; + margin-bottom: 0; + } + } + + h1, h2, h3, h4, h5, h6, .wp-block-subhead { + color: inherit; + } } diff --git a/core-blocks/test/fixtures/core__cover-image.html b/core-blocks/test/fixtures/core__cover-image.html index 86b5915701b0b..d197c55b6ef46 100644 --- a/core-blocks/test/fixtures/core__cover-image.html +++ b/core-blocks/test/fixtures/core__cover-image.html @@ -1,5 +1,9 @@ - -
-

Guten Berg!

+ +
+
+ +

Paragraph 1

+ +
- + diff --git a/core-blocks/test/fixtures/core__cover-image.json b/core-blocks/test/fixtures/core__cover-image.json index 7f12304be6f8a..e6f1ce08e7e91 100644 --- a/core-blocks/test/fixtures/core__cover-image.json +++ b/core-blocks/test/fixtures/core__cover-image.json @@ -4,15 +4,29 @@ "name": "core/cover-image", "isValid": true, "attributes": { - "title": [ - "Guten Berg!" - ], "url": "https://cldup.com/uuUqE_dXzy.jpg", - "contentAlign": "center", + "id": 8398, "hasParallax": false, "dimRatio": 40 }, - "innerBlocks": [], - "originalContent": "
\n

Guten Berg!

\n
" + "innerBlocks": [ + { + "uid": "_uid_0", + "name": "core/paragraph", + "isValid": true, + "attributes": { + "content": [ + "Paragraph 1" + ], + "align": "center", + "dropCap": false, + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "originalContent": "

Paragraph 1

" + } + ], + "originalContent": "
\n
\n\t\t\n
\n
" } ] diff --git a/core-blocks/test/fixtures/core__cover-image.parsed.json b/core-blocks/test/fixtures/core__cover-image.parsed.json index 25808e4279256..75fd0ba187af1 100644 --- a/core-blocks/test/fixtures/core__cover-image.parsed.json +++ b/core-blocks/test/fixtures/core__cover-image.parsed.json @@ -3,10 +3,22 @@ "blockName": "core/cover-image", "attrs": { "url": "https://cldup.com/uuUqE_dXzy.jpg", + "id": 8398, "dimRatio": 40 }, - "innerBlocks": [], - "innerHTML": "\n
\n

Guten Berg!

\n
\n" + "innerBlocks": [ + { + "blockName": "core/paragraph", + "attrs": { + "align": "center", + "placeholder": "Write title…", + "fontSize": "large" + }, + "innerBlocks": [], + "innerHTML": "\n\t\t

Paragraph 1

\n\t\t" + } + ], + "innerHTML": "\n
\n
\n\t\t\n
\n
\n" }, { "attrs": {}, diff --git a/core-blocks/test/fixtures/core__cover-image.serialized.html b/core-blocks/test/fixtures/core__cover-image.serialized.html index 87e8dc9795084..631a6169b2409 100644 --- a/core-blocks/test/fixtures/core__cover-image.serialized.html +++ b/core-blocks/test/fixtures/core__cover-image.serialized.html @@ -1,5 +1,9 @@ - +
-

Guten Berg!

+
+ +

Paragraph 1

+ +
diff --git a/edit-post/assets/stylesheets/_z-index.scss b/edit-post/assets/stylesheets/_z-index.scss index dd99e2b0b8fc3..eda062b0b9fc0 100644 --- a/edit-post/assets/stylesheets/_z-index.scss +++ b/edit-post/assets/stylesheets/_z-index.scss @@ -72,6 +72,8 @@ $z-layers: ( '.components-autocomplete__results': 1000000, '.skip-to-selected-block': 100000, + + '.wp-block-cover-image__inner-container': 0, ); @function z-index( $key ) { diff --git a/editor/components/block-list/block.js b/editor/components/block-list/block.js index 43d145c4948ad..39fdd04413906 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 009c50056759b..2bbea70352c90 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 6814c79aa598b..b1ddc21ce0025 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 f863ad697f337..1d7c058a1fae2 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 07a22bc3ec3a5..c78cdc25ec9ac 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 ca558159fa15e..ee6dfab934abe 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 8d58898ce3759..5c7978d745997 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 97a0291b9f23d..12462f8d91f7e 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 005173b127b79..26b0354cd0203 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 df5d72efadb78..5060f28a8111a 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 0f68869c783d8..2bbfe34cdc326 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: ]; }