diff --git a/blocks/api/registration.js b/blocks/api/registration.js index 77be97707861dd..fe7a862cf5992d 100644 --- a/blocks/api/registration.js +++ b/blocks/api/registration.js @@ -3,7 +3,7 @@ /** * External dependencies */ -import { get, set, isFunction, some } from 'lodash'; +import { get, set, isFunction, some, omit, mapValues, pick } from 'lodash'; /** * WordPress dependencies @@ -168,7 +168,20 @@ export function registerBlockType( name, settings ) { set( settings, [ 'supports', 'multiple' ], ! settings.useOnce ); } - dispatch( 'core/blocks' ).addBlockTypes( settings ); + const implementationOnlyAttributes = [ 'transforms', 'edit', 'save', 'icon', 'getEditWrapperProps' ]; + const blockTypeDefinition = omit( settings, implementationOnlyAttributes ); + blockTypeDefinition.attributes = mapValues( + settings.attributes, + ( attribute ) => pick( attribute, [ 'type', 'default' ] ) + ); + const blockTypeImplementation = pick( settings, [ 'name' ].concat( implementationOnlyAttributes ) ); + blockTypeImplementation.attributes = mapValues( + settings.attributes, + ( attribute ) => omit( attribute, [ 'type', 'default' ] ) + ); + + dispatch( 'core/blocks' ).addBlockTypes( blockTypeDefinition ); + dispatch( 'core/blocks' ).implementBlockTypes( blockTypeImplementation ); return settings; } diff --git a/blocks/api/test/registration.js b/blocks/api/test/registration.js index 99e5e1b866a9da..c113dc0feb0840 100644 --- a/blocks/api/test/registration.js +++ b/blocks/api/test/registration.js @@ -214,6 +214,7 @@ describe( 'blocks', () => { fill="red" stroke="blue" strokeWidth="10" /> ), }, + attributes: {}, } ); } ); @@ -233,6 +234,7 @@ describe( 'blocks', () => { icon: { src: 'foo', }, + attributes: {}, } ); } ); @@ -258,6 +260,7 @@ describe( 'blocks', () => { icon: { src: MyTestIcon, }, + attributes: {}, } ); } ); @@ -289,6 +292,7 @@ describe( 'blocks', () => { fill="red" stroke="blue" strokeWidth="10" /> ), }, + attributes: {}, } ); } ); @@ -305,6 +309,7 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, } ); } ); @@ -345,6 +350,7 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, }, ] ); const oldBlock = unregisterBlockType( 'core/test-block' ); @@ -357,6 +363,7 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, } ); expect( getBlockTypes() ).toEqual( [] ); } ); @@ -401,6 +408,7 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, } ); } ); @@ -416,6 +424,7 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, } ); } ); } ); @@ -438,6 +447,7 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, }, { name: 'core/test-block-with-settings', @@ -448,6 +458,7 @@ describe( 'blocks', () => { icon: { src: 'block-default', }, + attributes: {}, }, ] ); } ); diff --git a/blocks/store/actions.js b/blocks/store/actions.js index a97ab4e1d52e3d..dafdde6fb4b936 100644 --- a/blocks/store/actions.js +++ b/blocks/store/actions.js @@ -17,6 +17,20 @@ export function addBlockTypes( blockTypes ) { }; } +/** + * Returns an action object used in signalling that block types have been implemented. + * + * @param {Array|Object} implementations Block types implementations. + * + * @return {Object} Action object. + */ +export function implementBlockTypes( implementations ) { + return { + type: 'IMPLEMENT_BLOCK_TYPES', + implementations: castArray( implementations ), + }; +} + /** * Returns an action object used to remove a registered block type. * diff --git a/blocks/store/reducer.js b/blocks/store/reducer.js index c1623ae38692db..32581a3fb29148 100644 --- a/blocks/store/reducer.js +++ b/blocks/store/reducer.js @@ -43,6 +43,28 @@ export function blockTypes( state = {}, action ) { return state; } +/** + * Reducer managing the block type implementations + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + * + * @return {Object} Updated state. + */ +export function implementations( state = {}, action ) { + switch ( action.type ) { + case 'IMPLEMENT_BLOCK_TYPES': + return { + ...state, + ...keyBy( action.implementations, 'name' ), + }; + case 'REMOVE_BLOCK_TYPES': + return omit( state, action.names ); + } + + return state; +} + /** * Higher-order Reducer creating a reducer keeping track of given block name. * @@ -91,6 +113,7 @@ export function categories( state = DEFAULT_CATEGORIES, action ) { export default combineReducers( { blockTypes, + implementations, defaultBlockName, fallbackBlockName, categories, diff --git a/blocks/store/selectors.js b/blocks/store/selectors.js index bf6541ae3a1950..f898f692c2af4b 100644 --- a/blocks/store/selectors.js +++ b/blocks/store/selectors.js @@ -2,7 +2,7 @@ * External dependencies */ import createSelector from 'rememo'; -import { filter, includes, map } from 'lodash'; +import { filter, includes, map, mapValues, compact, get } from 'lodash'; /** * Returns all the available block types. @@ -12,9 +12,10 @@ import { filter, includes, map } from 'lodash'; * @return {Array} Block Types. */ export const getBlockTypes = createSelector( - ( state ) => Object.values( state.blockTypes ), + ( state ) => compact( Object.values( state.blockTypes ).map( ( { name } ) => getBlockType( state, name ) ) ), ( state ) => [ state.blockTypes, + state.implementations, ] ); @@ -26,9 +27,32 @@ export const getBlockTypes = createSelector( * * @return {Object?} Block Type. */ -export function getBlockType( state, name ) { - return state.blockTypes[ name ]; -} +export const getBlockType = createSelector( + ( state, name ) => { + const blockTypeDefinition = state.blockTypes[ name ]; + const blockTypeImplementation = state.implementations[ name ]; + + if ( ! blockTypeDefinition || ! blockTypeImplementation ) { + return null; + } + + return { + ...blockTypeDefinition, + ...blockTypeImplementation, + attributes: mapValues( blockTypeDefinition.attributes, ( attribute, key ) => { + const implementationAttribute = get( blockTypeImplementation.attributes, [ key ], {} ); + return { + ...attribute, + ...implementationAttribute, + }; + } ), + }; + }, + ( state ) => [ + state.blockTypes, + state.implementations, + ] +); /** * Returns all the available categories. diff --git a/core-blocks/index.js b/core-blocks/index.js index eb19acfe8b10bf..9b2b48a83ce29a 100644 --- a/core-blocks/index.js +++ b/core-blocks/index.js @@ -6,6 +6,7 @@ import { setDefaultBlockName, setUnknownTypeHandlerName, } from '@wordpress/blocks'; +import { dispatch } from '@wordpress/data'; /** * Internal dependencies @@ -77,11 +78,15 @@ export const registerCoreBlocks = () => { table, textColumns, verse, - video, ].forEach( ( { name, settings } ) => { registerBlockType( name, settings ); } ); + [ video ].forEach( ( { name, definition, implementation } ) => { + dispatch( 'core/blocks' ).addBlockTypes( { name, ...definition } ); + dispatch( 'core/blocks' ).implementBlockTypes( { name, ...implementation } ); + } ); + setDefaultBlockName( paragraph.name ); setUnknownTypeHandlerName( freeform.name ); }; diff --git a/core-blocks/video/index.js b/core-blocks/video/index.js index 0c946bce4e67a2..365b69accf5892 100644 --- a/core-blocks/video/index.js +++ b/core-blocks/video/index.js @@ -16,27 +16,33 @@ import edit from './edit'; export const name = 'core/video'; -export const settings = { +export const definition = { title: __( 'Video' ), - description: __( 'Embed an video file and a simple video player.' ), - - icon: 'format-video', - category: 'common', - attributes: { id: { type: 'number', }, src: { type: 'string', + }, + caption: { + type: 'array', + }, + }, +}; + +export const implementation = { + icon: 'format-video', + + attributes: { + src: { source: 'attribute', selector: 'video', attribute: 'src', }, caption: { - type: 'array', source: 'children', selector: 'figcaption', }, diff --git a/core-blocks/video/test/index.js b/core-blocks/video/test/index.js index a947f278a58826..1319a91177855e 100644 --- a/core-blocks/video/test/index.js +++ b/core-blocks/video/test/index.js @@ -1,11 +1,27 @@ +/** + * External dependencies + */ +import { get, mapValues } from 'lodash'; + /** * Internal dependencies */ -import { name, settings } from '../'; +import { name, definition, implementation } from '../'; import { blockEditRender } from '../../test/helpers'; describe( 'core/video', () => { test( 'block edit matches snapshot', () => { + const settings = { + ...definition, + ...implementation, + attributes: mapValues( definition.attributes, ( attribute, key ) => { + const implementationAttribute = get( implementation.attributes, [ key ], {} ); + return { + ...attribute, + ...implementationAttribute, + }; + } ), + }; const wrapper = blockEditRender( name, settings ); expect( wrapper ).toMatchSnapshot();