diff --git a/bin/create-php-parser.js b/bin/create-php-parser.js index f5cd5327fd0008..8961c38f85cf6f 100755 --- a/bin/create-php-parser.js +++ b/bin/create-php-parser.js @@ -5,7 +5,7 @@ const phpegjs = require( 'phpegjs' ); const fs = require( 'fs' ); const path = require( 'path' ); -const peg = fs.readFileSync( 'blocks/api/post.pegjs', 'utf8' ); +const peg = fs.readFileSync( 'block-api/post.pegjs', 'utf8' ); const parser = pegjs.generate( peg, diff --git a/block-api/config.js b/block-api/config.js new file mode 100644 index 00000000000000..98c4c707d0d925 --- /dev/null +++ b/block-api/config.js @@ -0,0 +1,16 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + +/** + * Retrieves the blockType from the block types config + * + * @param {String} name Block name + * @param {Object} config The block types config + * + * @return {?Object} Block type + */ +export function getBlockType( name, config ) { + return find( config.blockTypes, ( blockType ) => blockType.name === name ); +} diff --git a/blocks/api/factory.js b/block-api/factory.js similarity index 79% rename from blocks/api/factory.js rename to block-api/factory.js index 004cfb115c35bf..45cfddd1cccf32 100644 --- a/blocks/api/factory.js +++ b/block-api/factory.js @@ -14,19 +14,16 @@ import { /** * Internal dependencies */ -import { getBlockType } from './registration'; +import { getBlockType } from './config'; /** * Returns a block object given its type and attributes. * - * @param {String} name Block name + * @param {String} blockType Block type * @param {Object} attributes Block attributes * @return {Object} Block object */ -export function createBlock( name, attributes = {} ) { - // Get the type definition associated with a registered block. - const blockType = getBlockType( name ); - +export function createBlock( blockType, attributes = {} ) { // Ensure attributes contains only values defined by block type, and merge // default values for missing attributes. attributes = reduce( blockType.attributes, ( result, source, key ) => { @@ -44,7 +41,7 @@ export function createBlock( name, attributes = {} ) { // and the block attributes. return { uid: uuid(), - name, + name: blockType.name, isValid: true, attributes, }; @@ -54,14 +51,15 @@ export function createBlock( name, attributes = {} ) { * Switch a block into one or more blocks of the new block type. * * @param {Object} block Block object - * @param {string} name Block name + * @param {String} name Block name + * @param {Object} config Block Types config * @return {Array} Block object */ -export function switchToBlockType( block, name ) { +export function switchToBlockType( block, name, config ) { // Find the right transformation by giving priority to the "to" // transformation. - const destinationType = getBlockType( name ); - const sourceType = getBlockType( block.name ); + const destinationType = getBlockType( name, config ); + const sourceType = getBlockType( block.name, config ); const transformationsFrom = get( destinationType, 'transforms.from', [] ); const transformationsTo = get( sourceType, 'transforms.to', [] ); const transformation = @@ -87,10 +85,16 @@ export function switchToBlockType( block, name ) { // Ensure that every block object returned by the transformation has a // valid block type. - if ( transformationResults.some( ( result ) => ! getBlockType( result.name ) ) ) { + if ( transformationResults.some( ( result ) => ! getBlockType( result.name, config ) ) ) { return null; } + // Pass the results through the createBlockHelper + transformationResults = transformationResults.map( ( result ) => { + const blockType = getBlockType( result.name, config ); + return createBlock( blockType, result.attributes ); + } ); + const firstSwitchedBlock = findIndex( transformationResults, ( result ) => result.name === name ); // Ensure that at least one block object returned by the transformation has diff --git a/block-api/index.js b/block-api/index.js new file mode 100644 index 00000000000000..87ca08cca2a09c --- /dev/null +++ b/block-api/index.js @@ -0,0 +1,12 @@ +/** + * External dependencies + */ +import * as source from './source'; + +export { source }; +export { createBlock, switchToBlockType } from './factory'; +export { default as parse } from './parser'; +export { default as pasteHandler } from './paste'; +export { default as serialize, getBlockDefaultClassname } from './serializer'; +export { parse as grammarParse } from './post.pegjs'; +export { getBlockType } from './config'; diff --git a/blocks/api/parser.js b/block-api/parser.js similarity index 87% rename from blocks/api/parser.js rename to block-api/parser.js index c908414a775974..c766e6dc4c7d2f 100644 --- a/blocks/api/parser.js +++ b/block-api/parser.js @@ -8,9 +8,9 @@ import { mapValues, reduce, pickBy } from 'lodash'; * Internal dependencies */ import { parse as grammarParse } from './post.pegjs'; -import { getBlockType, getUnknownTypeHandlerName } from './registration'; import { createBlock } from './factory'; import { isValidBlock } from './validation'; +import { getBlockType } from './config'; /** * Returns true if the provided function is a valid attribute source, or false @@ -151,14 +151,15 @@ export function getBlockAttributes( blockType, rawContent, attributes ) { /** * Creates a block with fallback to the unknown type handler. * - * @param {?String} name Block type name - * @param {String} rawContent Raw block content - * @param {?Object} attributes Attributes obtained from block delimiters - * @return {?Object} An initialized block object (if possible) + * @param {?String} originalName Block type name + * @param {String} rawContent Raw block content + * @param {?Object} attributes Attributes obtained from block delimiters + * @param {Object} config Block Types config + * @return {?Object} An initialized block object (if possible) */ -export function createBlockWithFallback( name, rawContent, attributes ) { +export function createBlockWithFallback( originalName, rawContent, attributes, config ) { // Use type from block content, otherwise find unknown handler. - name = name || getUnknownTypeHandlerName(); + let name = originalName || config.fallbackBlockName; // Convert 'core/text' blocks in existing content to the new // 'core/paragraph'. @@ -167,21 +168,20 @@ export function createBlockWithFallback( name, rawContent, attributes ) { } // Try finding type for known block name, else fall back again. - let blockType = getBlockType( name ); - const fallbackBlock = getUnknownTypeHandlerName(); + let blockType = getBlockType( name, config ); if ( ! blockType ) { - name = fallbackBlock; - blockType = getBlockType( name ); + name = config.fallbackBlockName; + blockType = getBlockType( name, config ); } // Include in set only if type were determined. // TODO do we ever expect there to not be an unknown type handler? - if ( blockType && ( rawContent || name !== fallbackBlock ) ) { + if ( blockType && ( rawContent || name !== config.fallbackBlockName ) ) { // TODO allow blocks to opt-in to receiving a tree instead of a string. // Gradually convert all blocks to this new format, then remove the // string serialization. const block = createBlock( - name, + blockType, getBlockAttributes( blockType, rawContent, attributes ) ); @@ -201,12 +201,13 @@ export function createBlockWithFallback( name, rawContent, attributes ) { * Parses the post content with a PegJS grammar and returns a list of blocks. * * @param {String} content The post content + * @param {Object} config Block Types config * @return {Array} Block list */ -export function parseWithGrammar( content ) { +export function parse( content, config ) { return grammarParse( content ).reduce( ( memo, blockNode ) => { const { blockName, rawContent, attrs } = blockNode; - const block = createBlockWithFallback( blockName, rawContent.trim(), attrs ); + const block = createBlockWithFallback( blockName, rawContent.trim(), attrs, config ); if ( block ) { memo.push( block ); } @@ -214,4 +215,4 @@ export function parseWithGrammar( content ) { }, [] ); } -export default parseWithGrammar; +export default parse; diff --git a/blocks/api/paste.js b/block-api/paste.js similarity index 90% rename from blocks/api/paste.js rename to block-api/paste.js index 5342f34fa85ee3..82b01d46bf3dc3 100644 --- a/blocks/api/paste.js +++ b/block-api/paste.js @@ -12,8 +12,8 @@ import { nodetypes } from '@wordpress/utils'; * Internal dependencies */ import { createBlock } from './factory'; -import { getBlockTypes, getUnknownTypeHandlerName } from './registration'; import { getBlockAttributes } from './parser'; +import { getBlockType } from './config'; import stripAttributes from './paste/strip-attributes'; import removeSpans from './paste/remove-spans'; @@ -78,11 +78,11 @@ export function normaliseToBlockLevelNodes( nodes ) { return Array.from( accu.childNodes ); } -export default function( nodes ) { +export default function( nodes, config ) { const prepare = compose( [ normaliseToBlockLevelNodes, removeSpans, stripAttributes ] ); return prepare( nodes ).map( ( node ) => { - const block = getBlockTypes().reduce( ( acc, blockType ) => { + const block = config.blockTypes.reduce( ( acc, blockType ) => { if ( acc ) { return acc; } @@ -95,7 +95,7 @@ export default function( nodes ) { } return createBlock( - blockType.name, + blockType, getBlockAttributes( blockType, node.outerHTML @@ -107,7 +107,8 @@ export default function( nodes ) { return block; } - return createBlock( getUnknownTypeHandlerName(), { + const fallbackBlockType = getBlockType( config.fallbackBlockName, config ); + return createBlock( fallbackBlockType, { content: node.outerHTML, } ); } ); diff --git a/blocks/api/paste/remove-spans.js b/block-api/paste/remove-spans.js similarity index 100% rename from blocks/api/paste/remove-spans.js rename to block-api/paste/remove-spans.js diff --git a/blocks/api/paste/strip-attributes.js b/block-api/paste/strip-attributes.js similarity index 100% rename from blocks/api/paste/strip-attributes.js rename to block-api/paste/strip-attributes.js diff --git a/blocks/api/post.pegjs b/block-api/post.pegjs similarity index 100% rename from blocks/api/post.pegjs rename to block-api/post.pegjs diff --git a/blocks/api/serializer.js b/block-api/serializer.js similarity index 93% rename from blocks/api/serializer.js rename to block-api/serializer.js index c5aed9e25d3220..29b0df970f621f 100644 --- a/blocks/api/serializer.js +++ b/block-api/serializer.js @@ -13,7 +13,7 @@ import { Component, createElement, renderToString, cloneElement, Children } from /** * Internal dependencies */ -import { getBlockType } from './registration'; +import { getBlockType } from './config'; /** * Returns the block's default classname from its name @@ -131,9 +131,8 @@ export function getBeautifulContent( content ) { } ); } -export function serializeBlock( block ) { +export function serializeBlock( block, blockType ) { const blockName = block.name; - const blockType = getBlockType( blockName ); let saveContent; if ( block.isValid ) { @@ -169,8 +168,11 @@ export function serializeBlock( block ) { * Takes a block or set of blocks and returns the serialized post content. * * @param {Array} blocks Block(s) to serialize + * @param {Object} config Block Types config * @return {String} The post content */ -export default function serialize( blocks ) { - return castArray( blocks ).map( serializeBlock ).join( '\n\n' ); +export default function serialize( blocks, config ) { + return castArray( blocks ) + .map( ( block ) => serializeBlock( block, getBlockType( block.name, config ) ) ) + .join( '\n\n' ); } diff --git a/blocks/api/source.js b/block-api/source.js similarity index 100% rename from blocks/api/source.js rename to block-api/source.js diff --git a/block-api/test/config.js b/block-api/test/config.js new file mode 100644 index 00000000000000..1e76698603b288 --- /dev/null +++ b/block-api/test/config.js @@ -0,0 +1,25 @@ +/** + * Internal dependencies + */ +import { getBlockType } from '../config'; + +describe( 'block types config', () => { + describe( 'getBlockType', () => { + it( 'should return undefined if the block type was not found', () => { + const blockTypes = [ + { name: 'core/text' }, + ]; + const blockType = getBlockType( 'core/image', { blockTypes } ); + + expect( blockType ).toBeUndefined(); + } ); + + it( 'should return the correponding blockType', () => { + const textBlockType = { name: 'core/text' }; + const blockTypes = [ textBlockType ]; + const blockType = getBlockType( 'core/text', { blockTypes } ); + + expect( blockType ).toBe( textBlockType ); + } ); + } ); +} ); diff --git a/blocks/api/test/factory.js b/block-api/test/factory.js similarity index 64% rename from blocks/api/test/factory.js rename to block-api/test/factory.js index 8d1ea343252a88..cd521156b1081c 100644 --- a/blocks/api/test/factory.js +++ b/block-api/test/factory.js @@ -7,29 +7,12 @@ import { noop } from 'lodash'; * Internal dependencies */ import { createBlock, switchToBlockType } from '../factory'; -import { getBlockTypes, unregisterBlockType, setUnknownTypeHandlerName, registerBlockType } from '../registration'; describe( 'block factory', () => { - const defaultBlockSettings = { - attributes: { - value: { - type: 'string', - }, - }, - save: noop, - category: 'common', - }; - - afterEach( () => { - setUnknownTypeHandlerName( undefined ); - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); - } ); - describe( 'createBlock()', () => { it( 'should create a block given its blockType and attributes', () => { - registerBlockType( 'core/test-block', { + const blockType = { + name: 'core/test-block', attributes: { align: { type: 'string', @@ -40,9 +23,8 @@ describe( 'block factory', () => { }, }, save: noop, - category: 'common', - } ); - const block = createBlock( 'core/test-block', { + }; + const block = createBlock( blockType, { align: 'left', } ); @@ -57,8 +39,18 @@ describe( 'block factory', () => { } ); describe( 'switchToBlockType()', () => { + const defaultBlockType = { + name: 'core/text-block', + attributes: { + value: { + type: 'string', + }, + }, + save: noop, + }; it( 'should switch the blockType of a block using the "transform form"', () => { - registerBlockType( 'core/updated-text-block', { + const updatedTextBlock = { + name: 'core/updated-text-block', attributes: { value: { type: 'string', @@ -68,22 +60,27 @@ describe( 'block factory', () => { from: [ { blocks: [ 'core/text-block' ], transform: ( { value } ) => { - return createBlock( 'core/updated-text-block', { - value: 'chicken ' + value, - } ); + return { + name: 'core/updated-text-block', + attributes: { + value: 'chicken ' + value, + }, + }; }, } ], }, save: noop, category: 'common', - } ); - registerBlockType( 'core/text-block', defaultBlockSettings ); + }; + const config = { + blockTypes: [ updatedTextBlock, defaultBlockType ], + }; - const block = createBlock( 'core/text-block', { + const block = createBlock( defaultBlockType, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toHaveLength( 1 ); expect( transformedBlocks[ 0 ] ).toHaveProperty( 'uid' ); @@ -95,8 +92,12 @@ describe( 'block factory', () => { } ); it( 'should switch the blockType of a block using the "transform to"', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); - registerBlockType( 'core/text-block', { + const updatedTextBlock = { + ...defaultBlockType, + name: 'core/updated-text-block', + }; + const textBlock = { + name: 'core/text-block', attributes: { value: { type: 'string', @@ -106,21 +107,26 @@ describe( 'block factory', () => { to: [ { blocks: [ 'core/updated-text-block' ], transform: ( { value } ) => { - return createBlock( 'core/updated-text-block', { - value: 'chicken ' + value, - } ); + return { + name: 'core/updated-text-block', + attributes: { + value: 'chicken ' + value, + }, + }; }, } ], }, save: noop, category: 'common', - } ); - - const block = createBlock( 'core/text-block', { + }; + const config = { + blockTypes: [ updatedTextBlock, textBlock ], + }; + const block = createBlock( textBlock, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toHaveLength( 1 ); expect( transformedBlocks[ 0 ] ).toHaveProperty( 'uid' ); @@ -132,20 +138,25 @@ describe( 'block factory', () => { } ); it( 'should return null if no transformation is found', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); - registerBlockType( 'core/text-block', defaultBlockSettings ); - - const block = createBlock( 'core/text-block', { + const updatedTextBlock = { + ...defaultBlockType, + name: 'core/updated-text-block', + }; + const config = { + blockTypes: [ updatedTextBlock, defaultBlockType ], + }; + const block = createBlock( defaultBlockType, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject transformations that return null', () => { - registerBlockType( 'core/updated-text-block', { + const updatedTextBlock = { + name: 'core/updated-text-block', attributes: { value: { type: 'string', @@ -159,20 +170,22 @@ describe( 'block factory', () => { }, save: noop, category: 'common', - } ); - registerBlockType( 'core/text-block', defaultBlockSettings ); - - const block = createBlock( 'core/text-block', { + }; + const config = { + blockTypes: [ updatedTextBlock, defaultBlockType ], + }; + const block = createBlock( defaultBlockType, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject transformations that return an empty array', () => { - registerBlockType( 'core/updated-text-block', { + const updatedTextBlock = { + name: 'core/updated-text-block', attributes: { value: { type: 'string', @@ -186,20 +199,22 @@ describe( 'block factory', () => { }, save: noop, category: 'common', - } ); - registerBlockType( 'core/text-block', defaultBlockSettings ); - - const block = createBlock( 'core/text-block', { + }; + const config = { + blockTypes: [ updatedTextBlock, defaultBlockType ], + }; + const block = createBlock( defaultBlockType, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject single transformations that do not include block types', () => { - registerBlockType( 'core/updated-text-block', { + const updatedTextBlock = { + name: 'core/updated-text-block', attributes: { value: { type: 'string', @@ -219,20 +234,23 @@ describe( 'block factory', () => { }, save: noop, category: 'common', - } ); - registerBlockType( 'core/text-block', defaultBlockSettings ); + }; + const config = { + blockTypes: [ updatedTextBlock, defaultBlockType ], + }; - const block = createBlock( 'core/text-block', { + const block = createBlock( defaultBlockType, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject array transformations that do not include block types', () => { - registerBlockType( 'core/updated-text-block', { + const updatedTextBlock = { + name: 'core/updated-text-block', attributes: { value: { type: 'string', @@ -243,9 +261,12 @@ describe( 'block factory', () => { blocks: [ 'core/text-block' ], transform: ( { value } ) => { return [ - createBlock( 'core/updated-text-block', { - value: 'chicken ' + value, - } ), + { + name: 'core/updated-text-block', + attributes: { + value: 'chicken ' + value, + }, + }, { attributes: { value: 'smoked ' + value, @@ -257,21 +278,27 @@ describe( 'block factory', () => { }, save: noop, category: 'common', - } ); - registerBlockType( 'core/text-block', defaultBlockSettings ); + }; + const config = { + blockTypes: [ updatedTextBlock, defaultBlockType ], + }; - const block = createBlock( 'core/text-block', { + const block = createBlock( defaultBlockType, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject single transformations with unexpected block types', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); - registerBlockType( 'core/text-block', { + const updatedTextBlock = { + ...defaultBlockType, + name: 'core/updated-text-block', + }; + const textBlock = { + name: 'core/text-block', attributes: { value: { type: 'string', @@ -281,28 +308,38 @@ describe( 'block factory', () => { to: [ { blocks: [ 'core/updated-text-block' ], transform: ( { value } ) => { - return createBlock( 'core/text-block', { - value: 'chicken ' + value, - } ); + return { + name: 'core/text-block', + attributes: { + value: 'chicken ' + value, + }, + }; }, } ], }, save: noop, category: 'common', - } ); + }; + const config = { + blockTypes: [ updatedTextBlock, textBlock ], + }; - const block = createBlock( 'core/text-block', { + const block = createBlock( textBlock, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toBeNull(); } ); it( 'should reject array transformations with unexpected block types', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); - registerBlockType( 'core/text-block', { + const updatedTextBlock = { + ...defaultBlockType, + name: 'core/updated-text-block', + }; + const textBlock = { + name: 'core/text-block', attributes: { value: { type: 'string', @@ -313,32 +350,44 @@ describe( 'block factory', () => { blocks: [ 'core/updated-text-block' ], transform: ( { value } ) => { return [ - createBlock( 'core/text-block', { - value: 'chicken ' + value, - } ), - createBlock( 'core/text-block', { - value: 'smoked ' + value, - } ), + { + name: 'core/text-block', + attributes: { + value: 'chicken ' + value, + }, + }, + { + name: 'core/text-block', + attributes: { + value: 'smoked ' + value, + }, + }, ]; }, } ], }, save: noop, category: 'common', - } ); - - const block = createBlock( 'core/text-block', { + }; + const config = { + blockTypes: [ updatedTextBlock, textBlock ], + }; + const block = createBlock( textBlock, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); expect( transformedBlocks ).toEqual( null ); } ); it( 'should accept valid array transformations', () => { - registerBlockType( 'core/updated-text-block', defaultBlockSettings ); - registerBlockType( 'core/text-block', { + const updatedTextBlock = { + ...defaultBlockType, + name: 'core/updated-text-block', + }; + const textBlock = { + name: 'core/text-block', attributes: { value: { type: 'string', @@ -349,25 +398,33 @@ describe( 'block factory', () => { blocks: [ 'core/updated-text-block' ], transform: ( { value } ) => { return [ - createBlock( 'core/text-block', { - value: 'chicken ' + value, - } ), - createBlock( 'core/updated-text-block', { - value: 'smoked ' + value, - } ), + { + name: 'core/text-block', + attributes: { + value: 'chicken ' + value, + }, + }, + { + name: 'core/updated-text-block', + attributes: { + value: 'smoked ' + value, + }, + }, ]; }, } ], }, save: noop, category: 'common', - } ); - - const block = createBlock( 'core/text-block', { + }; + const config = { + blockTypes: [ updatedTextBlock, textBlock ], + }; + const block = createBlock( textBlock, { value: 'ribs', } ); - const transformedBlocks = switchToBlockType( block, 'core/updated-text-block' ); + const transformedBlocks = switchToBlockType( block, 'core/updated-text-block', config ); // Make sure the block UIDs are set as expected: the first // transformed block whose type matches the "destination" type gets diff --git a/blocks/api/test/parser.js b/block-api/test/parser.js similarity index 80% rename from blocks/api/test/parser.js rename to block-api/test/parser.js index 5445f57233a8cb..ed104d29582a84 100644 --- a/blocks/api/test/parser.js +++ b/block-api/test/parser.js @@ -15,15 +15,10 @@ import { createBlockWithFallback, default as parse, } from '../parser'; -import { - registerBlockType, - unregisterBlockType, - getBlockTypes, - setUnknownTypeHandlerName, -} from '../registration'; describe( 'block parser', () => { - const defaultBlockSettings = { + const defaultBlockType = { + name: 'core/test-block', attributes: { fruit: { type: 'string', @@ -33,13 +28,6 @@ describe( 'block parser', () => { category: 'common', }; - afterEach( () => { - setUnknownTypeHandlerName( undefined ); - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); - } ); - describe( 'isValidSource()', () => { it( 'returns false if falsey argument', () => { expect( isValidSource() ).toBe( false ); @@ -159,56 +147,75 @@ describe( 'block parser', () => { describe( 'createBlockWithFallback', () => { it( 'should create the requested block if it exists', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - const block = createBlockWithFallback( 'core/test-block', 'content', - { fruit: 'Bananas' } + { fruit: 'Bananas' }, + { blockTypes: [ defaultBlockType ] } ); expect( block.name ).toEqual( 'core/test-block' ); expect( block.attributes ).toEqual( { fruit: 'Bananas' } ); } ); it( 'should create the requested block with no attributes if it exists', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - - const block = createBlockWithFallback( 'core/test-block', 'content' ); + const block = createBlockWithFallback( + 'core/test-block', + 'content', + undefined, + { blockTypes: [ defaultBlockType ] } + ); expect( block.name ).toEqual( 'core/test-block' ); expect( block.attributes ).toEqual( {} ); } ); it( 'should fall back to the unknown type handler for unknown blocks if present', () => { - registerBlockType( 'core/unknown-block', defaultBlockSettings ); - setUnknownTypeHandlerName( 'core/unknown-block' ); + const unknownBlockType = { + ...defaultBlockType, + name: 'core/unknown-block', + }; + const config = { + blockTypes: [ unknownBlockType ], + fallbackBlockName: 'core/unknown-block', + }; const block = createBlockWithFallback( 'core/test-block', 'content', - { fruit: 'Bananas' } + { fruit: 'Bananas' }, + config ); expect( block.name ).toEqual( 'core/unknown-block' ); expect( block.attributes ).toEqual( { fruit: 'Bananas' } ); } ); it( 'should fall back to the unknown type handler if block type not specified', () => { - registerBlockType( 'core/unknown-block', defaultBlockSettings ); - setUnknownTypeHandlerName( 'core/unknown-block' ); + const unknownBlockType = { + ...defaultBlockType, + name: 'core/unknown-block', + }; + const config = { + blockTypes: [ unknownBlockType ], + fallbackBlockName: 'core/unknown-block', + }; - const block = createBlockWithFallback( null, 'content' ); + const block = createBlockWithFallback( null, 'content', undefined, config ); expect( block.name ).toEqual( 'core/unknown-block' ); expect( block.attributes ).toEqual( {} ); } ); it( 'should not create a block if no unknown type handler', () => { - const block = createBlockWithFallback( 'core/test-block', 'content' ); + const config = { + blockTypes: [], + }; + const block = createBlockWithFallback( 'core/test-block', 'content', undefined, config ); expect( block ).toBeUndefined(); } ); } ); describe( 'parse()', () => { it( 'should parse the post content, including block attributes', () => { - registerBlockType( 'core/test-block', { + const testBlockType = { + name: 'core/test-block', attributes: { content: { type: 'string', @@ -220,12 +227,16 @@ describe( 'block parser', () => { }, save: noop, category: 'common', - } ); + }; + const config = { + blockTypes: [ testBlockType ], + }; const parsed = parse( '' + 'Brisket' + - '' + '', + config ); expect( parsed ).toHaveLength( 1 ); @@ -240,13 +251,14 @@ describe( 'block parser', () => { } ); it( 'should parse empty post content', () => { - const parsed = parse( '' ); + const parsed = parse( '', { blockTypes: [] } ); expect( parsed ).toEqual( [] ); } ); it( 'should parse the post content, ignoring unknown blocks', () => { - registerBlockType( 'core/test-block', { + const blockType = { + name: 'core/test-block', attributes: { content: { type: 'string', @@ -255,12 +267,16 @@ describe( 'block parser', () => { }, save: noop, category: 'common', - } ); + }; + const config = { + blockTypes: [ blockType ], + }; const parsed = parse( '\nRibs\n' + '

Broccoli

' + - 'Ribs' + 'Ribs', + config ); expect( parsed ).toHaveLength( 1 ); @@ -272,15 +288,21 @@ describe( 'block parser', () => { } ); it( 'should parse the post content, using unknown block handler', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - registerBlockType( 'core/unknown-block', defaultBlockSettings ); + const unknownBlockType = { + ...defaultBlockType, + name: 'core/unknown-block', + }; - setUnknownTypeHandlerName( 'core/unknown-block' ); + const config = { + blockTypes: [ defaultBlockType, unknownBlockType ], + fallbackBlockName: 'core/unknown-block', + }; const parsed = parse( 'Ribs' + '

Broccoli

' + - 'Ribs' + 'Ribs', + config ); expect( parsed ).toHaveLength( 3 ); @@ -292,8 +314,8 @@ describe( 'block parser', () => { } ); it( 'should parse the post content, including raw HTML at each end', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - registerBlockType( 'core/unknown-block', { + const unknownBlockType = { + name: 'core/unknown-block', attributes: { content: { type: 'string', @@ -302,16 +324,20 @@ describe( 'block parser', () => { }, save: noop, category: 'common', - } ); + }; - setUnknownTypeHandlerName( 'core/unknown-block' ); + const config = { + blockTypes: [ defaultBlockType, unknownBlockType ], + fallbackBlockName: 'core/unknown-block', + }; const parsed = parse( '

Cauliflower

' + 'Ribs' + '\n

Broccoli

\n' + 'Ribs' + - '

Romanesco

' + '

Romanesco

', + config ); expect( parsed ).toHaveLength( 5 ); @@ -328,9 +354,12 @@ describe( 'block parser', () => { } ); it( 'should parse blocks with empty content', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); + const config = { + blockTypes: [ defaultBlockType ], + }; const parsed = parse( - '' + '', + config ); expect( parsed ).toHaveLength( 1 ); @@ -340,11 +369,17 @@ describe( 'block parser', () => { } ); it( 'should parse void blocks', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - registerBlockType( 'core/void-block', defaultBlockSettings ); + const voidBlockType = { + ...defaultBlockType, + name: 'core/void-block', + }; + const config = { + blockTypes: [ defaultBlockType, voidBlockType ], + }; const parsed = parse( '' + - '' + '', + config ); expect( parsed ).toHaveLength( 2 ); diff --git a/blocks/api/test/paste.js b/block-api/test/paste.js similarity index 65% rename from blocks/api/test/paste.js rename to block-api/test/paste.js index 02a925a142e1d4..d74abfcce2c813 100644 --- a/blocks/api/test/paste.js +++ b/block-api/test/paste.js @@ -7,7 +7,6 @@ import { equal, deepEqual } from 'assert'; * Internal dependencies */ import paste, { normaliseToBlockLevelNodes } from '../paste'; -import { registerBlockType, unregisterBlockType, setUnknownTypeHandlerName } from '../registration'; import { createBlock } from '../factory'; import { children, prop } from '../source'; @@ -49,56 +48,53 @@ describe( 'normaliseToBlockLevelNodes', () => { } ); describe( 'paste', () => { - beforeAll( () => { - registerBlockType( 'test/small', { - category: 'common', - attributes: { - content: { - type: 'array', - source: children( 'small' ), - }, - }, - transforms: { - from: [ - { - type: 'raw', - isMatch: ( node ) => node.nodeName === 'SMALL', - }, - ], + const smallBlockType = { + name: 'text/small', + category: 'common', + attributes: { + content: { + type: 'array', + source: children( 'small' ), }, - save: () => {}, - } ); - - registerBlockType( 'test/unknown', { - category: 'common', - attributes: { - content: { - type: 'string', - source: prop( 'innerHTML' ), + }, + transforms: { + from: [ + { + type: 'raw', + isMatch: ( node ) => node.nodeName === 'SMALL', }, + ], + }, + save: () => {}, + }; + + const unknownBlockType = { + name: 'test/unknown', + category: 'common', + attributes: { + content: { + type: 'string', + source: prop( 'innerHTML' ), }, - save: () => {}, - } ); + }, + save: () => {}, + }; - setUnknownTypeHandlerName( 'test/unknown' ); - } ); - - afterAll( () => { - unregisterBlockType( 'test/small' ); - unregisterBlockType( 'test/unknown' ); - setUnknownTypeHandlerName( undefined ); - } ); + const editorConfig = { + blockTypes: [ smallBlockType, unknownBlockType ], + fallbackBlockName: 'test/unknown', + }; it( 'should convert recognised pasted content', () => { - const pastedBlock = paste( createNodes( 'test' ) )[ 0 ]; - const block = createBlock( 'test/small', { content: [ 'test' ] } ); + const pastedBlock = paste( createNodes( 'test' ), editorConfig )[ 0 ]; + const block = createBlock( smallBlockType, { content: [ 'test' ] } ); equal( pastedBlock.name, block.name ); deepEqual( pastedBlock.attributes, block.attributes ); } ); it( 'should handle unknown pasted content', () => { - const pastedBlock = paste( createNodes( 'test' ) )[ 0 ]; + const pastedBlock = paste( createNodes( 'test' ), editorConfig )[ 0 ]; equal( pastedBlock.name, 'test/unknown' ); equal( pastedBlock.attributes.content, 'test' ); diff --git a/blocks/api/test/serializer.js b/block-api/test/serializer.js similarity index 92% rename from blocks/api/test/serializer.js rename to block-api/test/serializer.js index 23c3d4b54faacc..a10050044ea24e 100644 --- a/blocks/api/test/serializer.js +++ b/block-api/test/serializer.js @@ -13,16 +13,9 @@ import serialize, { getSaveContent, serializeAttributes, } from '../serializer'; -import { getBlockTypes, registerBlockType, unregisterBlockType } from '../registration'; -import { createBlock } from '../'; +import { createBlock } from '../factory'; describe( 'block serializer', () => { - afterEach( () => { - getBlockTypes().forEach( block => { - unregisterBlockType( block.name ); - } ); - } ); - describe( 'getBeautifulContent()', () => { it( 'returns beautiful content', () => { const content = getBeautifulContent( '
Beautiful
' ); @@ -216,22 +209,22 @@ describe( 'block serializer', () => { type: 'string', }, }, + name: 'core/test-block', save( { attributes } ) { return

; }, category: 'common', }; - registerBlockType( 'core/test-block', blockType ); - const block = createBlock( 'core/test-block', { + const block = createBlock( blockType, { foo: false, content: 'Ribs & Chicken', stuff: 'left & right -- but ', } ); const expectedPostContent = '\n

Ribs & Chicken

\n'; - expect( serialize( [ block ] ) ).toEqual( expectedPostContent ); - expect( serialize( block ) ).toEqual( expectedPostContent ); + expect( serialize( [ block ], { blockTypes: [ blockType ] } ) ).toEqual( expectedPostContent ); + expect( serialize( block, { blockTypes: [ blockType ] } ) ).toEqual( expectedPostContent ); } ); } ); } ); diff --git a/blocks/api/test/source.js b/block-api/test/source.js similarity index 100% rename from blocks/api/test/source.js rename to block-api/test/source.js diff --git a/blocks/api/test/validation.js b/block-api/test/validation.js similarity index 93% rename from blocks/api/test/validation.js rename to block-api/test/validation.js index d5de25057b2fe1..16d88c481807a6 100644 --- a/blocks/api/test/validation.js +++ b/block-api/test/validation.js @@ -14,27 +14,14 @@ import { isEquivalentHTML, isValidBlock, } from '../validation'; -import { - registerBlockType, - unregisterBlockType, - getBlockTypes, - getBlockType, - setUnknownTypeHandlerName, -} from '../registration'; describe( 'validation', () => { - const defaultBlockSettings = { + const defaultBlockType = { + name: 'core/test-block', save: ( { attributes } ) => attributes.fruit, category: 'common', }; - afterEach( () => { - setUnknownTypeHandlerName( undefined ); - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); - } ); - describe( 'getTextPiecesSplitOnWhitespace()', () => { it( 'returns text pieces spilt on whitespace', () => { const pieces = getTextPiecesSplitOnWhitespace( ' a \t b \n c' ); @@ -313,21 +300,17 @@ describe( 'validation', () => { describe( 'isValidBlock()', () => { it( 'returns false is block is not valid', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - expect( isValidBlock( 'Apples', - getBlockType( 'core/test-block' ), + defaultBlockType, { fruit: 'Bananas' } ) ).toBe( false ); } ); it( 'returns true is block is valid', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); - expect( isValidBlock( 'Bananas', - getBlockType( 'core/test-block' ), + defaultBlockType, { fruit: 'Bananas' } ) ).toBe( true ); } ); diff --git a/blocks/api/validation.js b/block-api/validation.js similarity index 100% rename from blocks/api/validation.js rename to block-api/validation.js diff --git a/blocks/api/index.js b/blocks/api/index.js index b25e28735a5c84..6f96958674522f 100644 --- a/blocks/api/index.js +++ b/blocks/api/index.js @@ -1,13 +1,3 @@ -/** - * External dependencies - */ -import * as source from './source'; - -export { source }; -export { createBlock, switchToBlockType } from './factory'; -export { default as parse } from './parser'; -export { default as pasteHandler } from './paste'; -export { default as serialize, getBlockDefaultClassname } from './serializer'; export { getCategories } from './categories'; export { registerBlockType, diff --git a/blocks/editable/index.js b/blocks/editable/index.js index 1c97d23d98d9bb..5c1d3173c7e854 100644 --- a/blocks/editable/index.js +++ b/blocks/editable/index.js @@ -22,16 +22,17 @@ import 'element-closest'; * WordPress dependencies */ import { createElement, Component, renderToString } from '@wordpress/element'; +import { parse, pasteHandler } from '@wordpress/block-api'; import { keycodes } from '@wordpress/utils'; /** * Internal dependencies */ import './style.scss'; -import { parse, pasteHandler } from '../api'; import FormatToolbar from './format-toolbar'; import TinyMCE from './tinymce'; import patterns from './patterns'; +import withEditorSettings from '../with-editor-settings'; const { BACKSPACE, DELETE, ENTER } = keycodes; @@ -51,7 +52,7 @@ function createTinyMCEElement( type, props, ...children ) { ); } -export default class Editable extends Component { +export class Editable extends Component { constructor( props ) { super( ...arguments ); @@ -108,7 +109,7 @@ export default class Editable extends Component { editor.on( 'BeforeExecCommand', this.maybePropagateUndo ); editor.on( 'PastePostProcess', this.onPastePostProcess ); - patterns.apply( this, [ editor ] ); + patterns.apply( this, [ editor, this.props.settings ] ); if ( this.props.onSetup ) { this.props.onSetup( editor ); @@ -188,11 +189,11 @@ export default class Editable extends Component { // Internal paste, so parse. if ( childNodes.some( isBlockDelimiter ) ) { - blocks = parse( event.node.innerHTML.replace( /]+>/, '' ) ); + blocks = parse( event.node.innerHTML.replace( /]+>/, '' ), this.props.settings ); // External paste with block level content, so attempt to assign // blocks. } else if ( childNodes.some( isBlockPart ) ) { - blocks = pasteHandler( childNodes ); + blocks = pasteHandler( childNodes, this.props.settings ); } if ( blocks.length ) { @@ -603,3 +604,5 @@ export default class Editable extends Component { Editable.contextTypes = { onUndo: noop, }; + +export default withEditorSettings()( Editable ); diff --git a/blocks/editable/patterns.js b/blocks/editable/patterns.js index e7adec6d41228b..d307ea62f343fb 100644 --- a/blocks/editable/patterns.js +++ b/blocks/editable/patterns.js @@ -9,11 +9,6 @@ import { find, get, escapeRegExp, groupBy, drop } from 'lodash'; */ import { keycodes } from '@wordpress/utils'; -/** - * Internal dependencies - */ -import { getBlockTypes } from '../api/registration'; - /** * Browser dependencies */ @@ -21,7 +16,7 @@ const { setTimeout } = window; const { ESCAPE, ENTER, SPACE, BACKSPACE } = keycodes; -export default function( editor ) { +export default function( editor, editorSettings ) { const getContent = this.getContent.bind( this ); const { onReplace } = this.props; @@ -32,7 +27,7 @@ export default function( editor ) { paste: pastePatterns, enter: enterPatterns, undefined: spacePatterns, - } = groupBy( getBlockTypes().reduce( ( acc, blockType ) => { + } = groupBy( editorSettings.blockTypes.reduce( ( acc, blockType ) => { const transformsFrom = get( blockType, 'transforms.from', [] ); const transforms = transformsFrom.filter( ( { type } ) => type === 'pattern' ); return [ ...acc, ...transforms ]; diff --git a/blocks/editable/test/index.js b/blocks/editable/test/index.js index 8afea0651166ee..cba16c956d435e 100644 --- a/blocks/editable/test/index.js +++ b/blocks/editable/test/index.js @@ -6,7 +6,7 @@ import { shallow } from 'enzyme'; /** * Internal dependencies */ -import Editable from '../'; +import { Editable } from '../'; describe( 'Editable', () => { describe( '.propTypes', () => { diff --git a/blocks/index.js b/blocks/index.js index be044588a67365..8c87a16d84c9b2 100644 --- a/blocks/index.js +++ b/blocks/index.js @@ -22,3 +22,4 @@ export { default as Editable } from './editable'; export { default as EditableProvider } from './editable/provider'; export { default as InspectorControls } from './inspector-controls'; export { default as MediaUploadButton } from './media-upload-button'; +export { default as withEditorSettings } from './with-editor-settings'; diff --git a/blocks/library/audio/index.js b/blocks/library/audio/index.js index 5b4052e7ce4d7a..75dc73e28c558a 100644 --- a/blocks/library/audio/index.js +++ b/blocks/library/audio/index.js @@ -7,12 +7,13 @@ */ import { __ } from '@wordpress/i18n'; import { Placeholder } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './style.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import MediaUploadButton from '../../media-upload-button'; const { attr } = source; diff --git a/blocks/library/button/index.js b/blocks/library/button/index.js index 3f84af2b106b49..fd74542642b633 100644 --- a/blocks/library/button/index.js +++ b/blocks/library/button/index.js @@ -3,13 +3,14 @@ */ import { __ } from '@wordpress/i18n'; import { IconButton } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; import './style.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import UrlInput from '../../url-input'; import BlockControls from '../../block-controls'; diff --git a/blocks/library/code/index.js b/blocks/library/code/index.js index 0c6ac022796977..ded406d0e0a0b0 100644 --- a/blocks/library/code/index.js +++ b/blocks/library/code/index.js @@ -7,12 +7,13 @@ import TextareaAutosize from 'react-autosize-textarea'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, source, createBlock } from '../../api'; +import { registerBlockType } from '../../api'; const { prop } = source; @@ -36,7 +37,7 @@ registerBlockType( 'core/code', { type: 'pattern', trigger: 'enter', regExp: /^```$/, - transform: () => createBlock( 'core/code' ), + transform: () => ( { name: 'core/code', attributes: {} } ), }, ], }, diff --git a/blocks/library/cover-image/index.js b/blocks/library/cover-image/index.js index 043fa2f40c1c7d..72b01be056317a 100644 --- a/blocks/library/cover-image/index.js +++ b/blocks/library/cover-image/index.js @@ -1,16 +1,21 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ import { Placeholder, Toolbar, Dashicon } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import classnames from 'classnames'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; import './style.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import MediaUploadButton from '../../media-upload-button'; import BlockControls from '../../block-controls'; diff --git a/blocks/library/cover-text/index.js b/blocks/library/cover-text/index.js index 773e32d966731c..b4580d9ecc391c 100644 --- a/blocks/library/cover-text/index.js +++ b/blocks/library/cover-text/index.js @@ -8,12 +8,13 @@ import classnames from 'classnames'; */ import { __ } from '@wordpress/i18n'; import { concatChildren } from '@wordpress/element'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './style.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import AlignmentToolbar from '../../alignment-toolbar'; import BlockControls from '../../block-controls'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; diff --git a/blocks/library/embed/index.js b/blocks/library/embed/index.js index e494ef645ac3f6..deb0a942e74a5b 100644 --- a/blocks/library/embed/index.js +++ b/blocks/library/embed/index.js @@ -10,6 +10,7 @@ import { includes } from 'lodash'; import { __, sprintf } from '@wordpress/i18n'; import { Component, renderToString } from '@wordpress/element'; import { Button, Placeholder, Spinner, SandBox } from '@wordpress/components'; +import { source, createBlock } from '@wordpress/block-api'; import { addQueryArgs } from '@wordpress/url'; /** @@ -17,7 +18,7 @@ import { addQueryArgs } from '@wordpress/url'; */ import './style.scss'; import './editor.scss'; -import { registerBlockType, source, createBlock } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import BlockControls from '../../block-controls'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; @@ -229,7 +230,7 @@ function getEmbedBlockSettings( { title, icon, category = 'embed', transforms, k }; } -registerBlockType( +const embedBlockType = registerBlockType( 'core/embed', getEmbedBlockSettings( { title: 'Embed', @@ -241,7 +242,7 @@ registerBlockType( trigger: 'paste', regExp: /^\s*(https?:\/\/\S+)\s*/i, transform: ( { match } ) => { - return createBlock( 'core/embed', { + return createBlock( embedBlockType, { url: match[ 1 ], } ); }, diff --git a/blocks/library/freeform/index.js b/blocks/library/freeform/index.js index 1df0c44a58d46e..720a8e7763bf62 100644 --- a/blocks/library/freeform/index.js +++ b/blocks/library/freeform/index.js @@ -2,12 +2,13 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, source, setUnknownTypeHandlerName } from '../../api'; +import { registerBlockType, setUnknownTypeHandlerName } from '../../api'; import OldEditor from './old-editor'; const { prop } = source; diff --git a/blocks/library/gallery/index.js b/blocks/library/gallery/index.js index 2faaeef761e311..966bdc84f760de 100644 --- a/blocks/library/gallery/index.js +++ b/blocks/library/gallery/index.js @@ -4,13 +4,14 @@ import { __ } from '@wordpress/i18n'; import { mediaUpload } from '@wordpress/utils'; import { Dashicon, Toolbar, Placeholder, FormFileUpload } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; import './style.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import MediaUploadButton from '../../media-upload-button'; import InspectorControls from '../../inspector-controls'; import RangeControl from '../../inspector-controls/range-control'; diff --git a/blocks/library/heading/index.js b/blocks/library/heading/index.js index a3f50f8878c433..70004eb358e00d 100644 --- a/blocks/library/heading/index.js +++ b/blocks/library/heading/index.js @@ -4,12 +4,13 @@ import { __, sprintf } from '@wordpress/i18n'; import { concatChildren } from '@wordpress/element'; import { Toolbar } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, createBlock, source } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import BlockControls from '../../block-controls'; import InspectorControls from '../../inspector-controls'; @@ -17,6 +18,7 @@ import AlignmentToolbar from '../../alignment-toolbar'; import BlockDescription from '../../block-description'; const { children, prop } = source; +const createTransformationBlock = ( name, attributes ) => ( { name, attributes } ); registerBlockType( 'core/heading', { title: __( 'Heading' ), @@ -53,7 +55,7 @@ registerBlockType( 'core/heading', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( { content } ) => { - return createBlock( 'core/heading', { + return createTransformationBlock( 'core/heading', { content, } ); }, @@ -68,7 +70,7 @@ registerBlockType( 'core/heading', { transform: ( { content, match } ) => { const level = match[ 1 ].length; - return createBlock( 'core/heading', { + return createTransformationBlock( 'core/heading', { nodeName: `H${ level }`, content, } ); @@ -80,7 +82,7 @@ registerBlockType( 'core/heading', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( { content } ) => { - return createBlock( 'core/paragraph', { + return createTransformationBlock( 'core/paragraph', { content, } ); }, @@ -94,7 +96,7 @@ registerBlockType( 'core/heading', { }; }, - edit( { attributes, setAttributes, focus, setFocus, mergeBlocks, insertBlocksAfter } ) { + edit( { attributes, setAttributes, focus, setFocus, mergeBlocks } ) { const { align, content, nodeName, placeholder } = attributes; return [ @@ -147,13 +149,6 @@ registerBlockType( 'core/heading', { onFocus={ setFocus } onChange={ ( value ) => setAttributes( { content: value } ) } onMerge={ mergeBlocks } - onSplit={ ( before, after, ...blocks ) => { - setAttributes( { content: before } ); - insertBlocksAfter( [ - ...blocks, - createBlock( 'core/paragraph', { content: after } ), - ] ); - } } style={ { textAlign: align } } placeholder={ placeholder || __( 'Write heading…' ) } />, diff --git a/blocks/library/html/index.js b/blocks/library/html/index.js index 1b520767647a05..98fe1640fad16d 100644 --- a/blocks/library/html/index.js +++ b/blocks/library/html/index.js @@ -8,12 +8,13 @@ import TextareaAutosize from 'react-autosize-textarea'; */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import BlockControls from '../../block-controls'; const { html } = source; diff --git a/blocks/library/image/index.js b/blocks/library/image/index.js index 2097dd3f859ff6..27f23898f64337 100644 --- a/blocks/library/image/index.js +++ b/blocks/library/image/index.js @@ -10,14 +10,15 @@ import ResizableBox from 'react-resizable-box'; import { __ } from '@wordpress/i18n'; import { mediaUpload } from '@wordpress/utils'; import { Placeholder, Dashicon, Toolbar, DropZone, FormFileUpload } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './style.scss'; import './editor.scss'; -import { registerBlockType, source } from '../../api'; import withEditorSettings from '../../with-editor-settings'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import MediaUploadButton from '../../media-upload-button'; import InspectorControls from '../../inspector-controls'; diff --git a/blocks/library/list/index.js b/blocks/library/list/index.js index bbdb512680ed13..056d0aed587503 100644 --- a/blocks/library/list/index.js +++ b/blocks/library/list/index.js @@ -8,16 +8,19 @@ import { find } from 'lodash'; */ import { Component, createElement, Children, concatChildren } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { source, getBlockType, createBlock } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, source, createBlock } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import BlockControls from '../../block-controls'; +import withEditorSettings from '../../with-editor-settings'; const { children, prop } = source; +const createTransformationBlock = ( name, attributes ) => ( { name, attributes } ); const fromBrDelimitedContent = ( content ) => { if ( undefined === content ) { @@ -100,7 +103,7 @@ registerBlockType( 'core/list', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( { content } ) => { - return createBlock( 'core/list', { + return createTransformationBlock( 'core/list', { nodeName: 'UL', values: fromBrDelimitedContent( content ), } ); @@ -114,7 +117,7 @@ registerBlockType( 'core/list', { const values = citation ? concatChildren( listItems,
  • { citation }
  • ) : listItems; - return createBlock( 'core/list', { + return createTransformationBlock( 'core/list', { nodeName: 'UL', values, } ); @@ -128,7 +131,7 @@ registerBlockType( 'core/list', { type: 'pattern', regExp: /^[*-]\s/, transform: ( { content } ) => { - return createBlock( 'core/list', { + return createTransformationBlock( 'core/list', { nodeName: 'UL', values: fromBrDelimitedContent( content ), } ); @@ -138,7 +141,7 @@ registerBlockType( 'core/list', { type: 'pattern', regExp: /^1[.)]\s/, transform: ( { content } ) => { - return createBlock( 'core/list', { + return createTransformationBlock( 'core/list', { nodeName: 'OL', values: fromBrDelimitedContent( content ), } ); @@ -150,7 +153,7 @@ registerBlockType( 'core/list', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( { values } ) => { - return createBlock( 'core/paragraph', { + return createTransformationBlock( 'core/paragraph', { content: toBrDelimitedContent( values ), } ); }, @@ -159,7 +162,7 @@ registerBlockType( 'core/list', { type: 'block', blocks: [ 'core/quote' ], transform: ( { values } ) => { - return createBlock( 'core/quote', { + return createTransformationBlock( 'core/quote', { value: [

    { toBrDelimitedContent( values ) }

    ], } ); }, @@ -184,7 +187,7 @@ registerBlockType( 'core/list', { }; }, - edit: class extends Component { + edit: withEditorSettings()( class extends Component { constructor() { super( ...arguments ); @@ -275,6 +278,7 @@ registerBlockType( 'core/list', { insertBlocksAfter, setAttributes, mergeBlocks, + settings, } = this.props; const { nodeName, values } = attributes; @@ -323,11 +327,11 @@ registerBlockType( 'core/list', { onMerge={ mergeBlocks } onSplit={ ( before, after, ...blocks ) => { if ( ! blocks.length ) { - blocks.push( createBlock( 'core/paragraph' ) ); + blocks.push( createBlock( getBlockType( 'core/paragraph', settings ) ) ); } if ( after.length ) { - blocks.push( createBlock( 'core/list', { + blocks.push( createBlock( getBlockType( 'core/list', settings ), { nodeName, values: after, } ) ); @@ -339,7 +343,7 @@ registerBlockType( 'core/list', { />, ]; } - }, + } ), save( { attributes } ) { const { nodeName, values } = attributes; diff --git a/blocks/library/paragraph/index.js b/blocks/library/paragraph/index.js index 1061d81e7f4256..263a2c08721ca1 100644 --- a/blocks/library/paragraph/index.js +++ b/blocks/library/paragraph/index.js @@ -3,12 +3,13 @@ */ import { __ } from '@wordpress/i18n'; import { concatChildren } from '@wordpress/element'; +import { source, createBlock } from '@wordpress/block-api'; /** * Internal dependencies */ import './style.scss'; -import { registerBlockType, createBlock, source, setDefaultBlock } from '../../api'; +import { registerBlockType, setDefaultBlock } from '../../api'; import AlignmentToolbar from '../../alignment-toolbar'; import BlockControls from '../../block-controls'; import Editable from '../../editable'; @@ -18,7 +19,7 @@ import BlockDescription from '../../block-description'; const { children } = source; -registerBlockType( 'core/paragraph', { +const textBlockType = registerBlockType( 'core/paragraph', { title: __( 'Paragraph' ), icon: 'editor-paragraph', @@ -107,7 +108,7 @@ registerBlockType( 'core/paragraph', { setAttributes( { content: before } ); insertBlocksAfter( [ ...blocks, - createBlock( 'core/paragraph', { content: after } ), + createBlock( textBlockType, { content: after } ), ] ); } } onMerge={ mergeBlocks } diff --git a/blocks/library/preformatted/index.js b/blocks/library/preformatted/index.js index bbd9f12483ae5b..bb570359732452 100644 --- a/blocks/library/preformatted/index.js +++ b/blocks/library/preformatted/index.js @@ -1,16 +1,18 @@ /** - * WordPress + * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, createBlock, source } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; const { children } = source; +const createTransformationBlock = ( name, attributes ) => ( { name, attributes } ); registerBlockType( 'core/preformatted', { title: __( 'Preformatted' ), @@ -32,7 +34,7 @@ registerBlockType( 'core/preformatted', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( attributes ) => - createBlock( 'core/preformatted', attributes ), + createTransformationBlock( 'core/preformatted', attributes ), }, ], to: [ @@ -40,7 +42,7 @@ registerBlockType( 'core/preformatted', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( attributes ) => - createBlock( 'core/paragraph', attributes ), + createTransformationBlock( 'core/paragraph', attributes ), }, ], }, diff --git a/blocks/library/pullquote/index.js b/blocks/library/pullquote/index.js index a4223575338e2d..cbeb6b8c4bca22 100644 --- a/blocks/library/pullquote/index.js +++ b/blocks/library/pullquote/index.js @@ -2,13 +2,14 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; import './style.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import BlockControls from '../../block-controls'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; diff --git a/blocks/library/quote/index.js b/blocks/library/quote/index.js index 88b44b58322880..85a8e4d4195fd7 100644 --- a/blocks/library/quote/index.js +++ b/blocks/library/quote/index.js @@ -8,17 +8,19 @@ import { isString, isObject } from 'lodash'; */ import { __, sprintf } from '@wordpress/i18n'; import { Toolbar } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './style.scss'; -import { registerBlockType, createBlock, source } from '../../api'; +import { registerBlockType } from '../../api'; import AlignmentToolbar from '../../alignment-toolbar'; import BlockControls from '../../block-controls'; import Editable from '../../editable'; const { children, node, query } = source; +const createTransformationBlock = ( name, attributes ) => ( { name, attributes } ); registerBlockType( 'core/quote', { title: __( 'Quote' ), @@ -50,7 +52,7 @@ registerBlockType( 'core/quote', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( { content } ) => { - return createBlock( 'core/quote', { + return createTransformationBlock( 'core/quote', { value: [

    { content }

    , ], @@ -61,7 +63,7 @@ registerBlockType( 'core/quote', { type: 'block', blocks: [ 'core/heading' ], transform: ( { content } ) => { - return createBlock( 'core/quote', { + return createTransformationBlock( 'core/quote', { value: [

    { content }

    , ], @@ -72,7 +74,7 @@ registerBlockType( 'core/quote', { type: 'pattern', regExp: /^>\s/, transform: ( { content } ) => { - return createBlock( 'core/quote', { + return createTransformationBlock( 'core/quote', { value: [

    { content }

    , ], @@ -87,16 +89,16 @@ registerBlockType( 'core/quote', { transform: ( { value, citation, ...attrs } ) => { const textElement = value[ 0 ]; if ( ! textElement ) { - return createBlock( 'core/paragraph', { + return createTransformationBlock( 'core/paragraph', { content: citation, } ); } const textContent = isString( textElement ) ? textElement : textElement.props.children; if ( Array.isArray( value ) || citation ) { - const text = createBlock( 'core/paragraph', { + const text = createTransformationBlock( 'core/paragraph', { content: textContent, } ); - const quote = createBlock( 'core/quote', { + const quote = createTransformationBlock( 'core/quote', { ...attrs, citation, value: Array.isArray( value ) ? value.slice( 1 ) : '', @@ -104,7 +106,7 @@ registerBlockType( 'core/quote', { return [ text, quote ]; } - return createBlock( 'core/paragraph', { + return createTransformationBlock( 'core/paragraph', { content: textContent, } ); }, @@ -119,10 +121,10 @@ registerBlockType( 'core/quote', { ? headingElement.props.children : headingElement; if ( isMultiParagraph || citation ) { - const heading = createBlock( 'core/heading', { + const heading = createTransformationBlock( 'core/heading', { content: headingContent, } ); - const quote = createBlock( 'core/quote', { + const quote = createTransformationBlock( 'core/quote', { ...attrs, citation, value: Array.isArray( value ) ? value.slice( 1 ) : '', @@ -130,7 +132,7 @@ registerBlockType( 'core/quote', { return [ heading, quote ]; } - return createBlock( 'core/heading', { + return createTransformationBlock( 'core/heading', { content: headingContent, } ); }, diff --git a/blocks/library/separator/index.js b/blocks/library/separator/index.js index 6bb4e2cc58c835..50bca59b0bce5c 100644 --- a/blocks/library/separator/index.js +++ b/blocks/library/separator/index.js @@ -7,7 +7,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import './style.scss'; -import { registerBlockType, createBlock } from '../../api'; +import { registerBlockType } from '../../api'; registerBlockType( 'core/separator', { title: __( 'Separator' ), @@ -24,7 +24,7 @@ registerBlockType( 'core/separator', { type: 'pattern', trigger: 'enter', regExp: /^-{3,}$/, - transform: () => createBlock( 'core/separator' ), + transform: () => ( { name: 'core/separator', attributes: {} } ), }, ], }, diff --git a/blocks/library/shortcode/index.js b/blocks/library/shortcode/index.js index cfa474fe4f2e86..63c836701e5375 100644 --- a/blocks/library/shortcode/index.js +++ b/blocks/library/shortcode/index.js @@ -3,12 +3,13 @@ */ import { __ } from '@wordpress/i18n'; import { withInstanceId, Dashicon } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import InspectorControls from '../../inspector-controls'; import BlockDescription from '../../block-description'; diff --git a/blocks/library/table/index.js b/blocks/library/table/index.js index b147a050503d29..8351b0d07e9cc3 100644 --- a/blocks/library/table/index.js +++ b/blocks/library/table/index.js @@ -1,14 +1,15 @@ /** - * External dependencies + * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; import './style.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import TableBlock from './table-block'; import BlockControls from '../../block-controls'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; diff --git a/blocks/library/text-columns/index.js b/blocks/library/text-columns/index.js index 82e92e67120459..66b8cfd85bde0c 100644 --- a/blocks/library/text-columns/index.js +++ b/blocks/library/text-columns/index.js @@ -7,13 +7,14 @@ import { times } from 'lodash'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './style.scss'; import './editor.scss'; -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import BlockControls from '../../block-controls'; import BlockAlignmentToolbar from '../../block-alignment-toolbar'; import RangeControl from '../../inspector-controls/range-control'; diff --git a/blocks/library/verse/index.js b/blocks/library/verse/index.js index 37ccce66f3013a..7ecd5852f2be2b 100644 --- a/blocks/library/verse/index.js +++ b/blocks/library/verse/index.js @@ -2,17 +2,19 @@ * WordPress */ import { __ } from '@wordpress/i18n'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ import './editor.scss'; -import { registerBlockType, createBlock, source } from '../../api'; +import { registerBlockType } from '../../api'; import Editable from '../../editable'; import InspectorControls from '../../inspector-controls'; import BlockDescription from '../../block-description'; const { children } = source; +const createTransformationBlock = ( name, attributes ) => ( { name, attributes } ); registerBlockType( 'core/verse', { title: __( 'Verse' ), @@ -36,7 +38,7 @@ registerBlockType( 'core/verse', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( attributes ) => - createBlock( 'core/verse', attributes ), + createTransformationBlock( 'core/verse', attributes ), }, ], to: [ @@ -44,7 +46,7 @@ registerBlockType( 'core/verse', { type: 'block', blocks: [ 'core/paragraph' ], transform: ( attributes ) => - createBlock( 'core/paragraph', attributes ), + createTransformationBlock( 'core/paragraph', attributes ), }, ], }, diff --git a/blocks/library/video/index.js b/blocks/library/video/index.js index 969763595e8304..7823dafc2284d3 100644 --- a/blocks/library/video/index.js +++ b/blocks/library/video/index.js @@ -7,11 +7,12 @@ */ import { __ } from '@wordpress/i18n'; import { Placeholder } from '@wordpress/components'; +import { source } from '@wordpress/block-api'; /** * Internal dependencies */ -import { registerBlockType, source } from '../../api'; +import { registerBlockType } from '../../api'; import MediaUploadButton from '../../media-upload-button'; import Editable from '../../editable'; diff --git a/blocks/test/full-content.js b/blocks/test/full-content.js index daf002e8c9ccc1..0bd3406e1a85ba 100644 --- a/blocks/test/full-content.js +++ b/blocks/test/full-content.js @@ -9,12 +9,16 @@ import { format } from 'util'; /** * Internal dependencies */ -import parse from '../api/parser'; -import { parse as grammarParse } from '../api/post.pegjs'; -import serialize from '../api/serializer'; +import { parse, serialize, grammarParse } from '@wordpress/block-api'; import { getBlockTypes } from '../api/registration'; +// This import registers the blocks +import '../library'; + const fixturesDir = path.join( __dirname, 'fixtures' ); +const settings = { + blockTypes: getBlockTypes(), +}; // We expect 4 different types of files for each fixture: // - fixture.html : original content @@ -133,7 +137,7 @@ describe( 'full post content fixture', () => { ) ); } - const blocksActual = parse( content ); + const blocksActual = parse( content, settings ); const blocksActualNormalized = normalizeParsedBlocks( blocksActual ); let blocksExpectedString = readFixtureFile( f + '.json' ); @@ -167,7 +171,7 @@ describe( 'full post content fixture', () => { // `serialize` doesn't have a trailing newline, but the fixture // files should. - const serializedActual = serialize( blocksActual ) + '\n'; + const serializedActual = serialize( blocksActual, settings ) + '\n'; let serializedExpected = readFixtureFile( f + '.serialized.html' ); if ( ! serializedExpected ) { diff --git a/editor/block-mover/index.js b/editor/block-mover/index.js index d127eea6b03baa..771481157572e3 100644 --- a/editor/block-mover/index.js +++ b/editor/block-mover/index.js @@ -9,7 +9,8 @@ import { first, last } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { IconButton } from '@wordpress/components'; -import { getBlockType } from '@wordpress/blocks'; +import { withEditorSettings } from '@wordpress/blocks'; +import { getBlockType } from '@wordpress/block-api'; /** * Internal dependencies @@ -59,12 +60,12 @@ function BlockMover( { onMoveUp, onMoveDown, isFirst, isLast, uids, blockType, f ); } -export default connect( +const connectComponent = connect( ( state, ownProps ) => ( { isFirst: isFirstBlock( state, first( ownProps.uids ) ), isLast: isLastBlock( state, last( ownProps.uids ) ), firstIndex: getBlockIndex( state, first( ownProps.uids ) ), - blockType: getBlockType( getBlock( state, first( ownProps.uids ) ).name ), + name: getBlock( state, first( ownProps.uids ) ).name, } ), ( dispatch, ownProps ) => ( { onMoveDown() { @@ -80,4 +81,12 @@ export default connect( } ); }, } ) -)( BlockMover ); +); + +const getEditorSettings = withEditorSettings( ( settings, ownProps ) => { + return { + blockType: getBlockType( ownProps.name, settings ), + }; +} ); + +export default connectComponent( getEditorSettings( BlockMover ) ); diff --git a/editor/block-switcher/index.js b/editor/block-switcher/index.js index 1dc86eccf5a670..e64b9344d5c1a2 100644 --- a/editor/block-switcher/index.js +++ b/editor/block-switcher/index.js @@ -2,7 +2,7 @@ * External dependencies */ import { connect } from 'react-redux'; -import { uniq, get, reduce, find } from 'lodash'; +import { uniq, get, reduce, find, flow } from 'lodash'; import clickOutside from 'react-click-outside'; /** @@ -11,7 +11,8 @@ import clickOutside from 'react-click-outside'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { Dashicon, IconButton } from '@wordpress/components'; -import { getBlockType, getBlockTypes, switchToBlockType } from '@wordpress/blocks'; +import { withEditorSettings } from '@wordpress/blocks'; +import { switchToBlockType, getBlockType } from '@wordpress/block-api'; /** * Internal dependencies @@ -48,13 +49,13 @@ class BlockSwitcher extends Component { this.setState( { open: false, } ); - this.props.onTransform( this.props.block, name ); + this.props.onTransform( this.props.block, name, this.props.settings ); }; } render() { - const blockType = getBlockType( this.props.block.name ); - const blocksToBeTransformedFrom = reduce( getBlockTypes(), ( memo, block ) => { + const blockType = getBlockType( this.props.block.name, this.props.settings ); + const blocksToBeTransformedFrom = reduce( this.props.settings.blockTypes, ( memo, block ) => { const transformFrom = get( block, 'transforms.from', [] ); const transformation = find( transformFrom, t => t.type === 'block' && t.blocks.indexOf( this.props.block.name ) !== -1 ); return transformation ? memo.concat( [ block.name ] ) : memo; @@ -63,7 +64,7 @@ class BlockSwitcher extends Component { .reduce( ( memo, transformation ) => memo.concat( transformation.blocks ), [] ); const allowedBlocks = uniq( blocksToBeTransformedFrom.concat( blocksToBeTransformedTo ) ) .reduce( ( memo, name ) => { - const block = getBlockType( name ); + const block = getBlockType( name, this.props.settings ); return !! block ? memo.concat( block ) : memo; }, [] ); @@ -108,16 +109,22 @@ class BlockSwitcher extends Component { } } -export default connect( +const connectComponent = connect( ( state, ownProps ) => ( { block: getBlock( state, ownProps.uid ), } ), ( dispatch, ownProps ) => ( { - onTransform( block, name ) { + onTransform( block, name, settings ) { dispatch( replaceBlocks( [ ownProps.uid ], - switchToBlockType( block, name ) + switchToBlockType( block, name, settings ) ) ); }, } ) -)( clickOutside( BlockSwitcher ) ); +); + +export default flow( + clickOutside, + withEditorSettings(), + connectComponent, +)( BlockSwitcher ); diff --git a/editor/effects.js b/editor/effects.js index 163982fba0e503..b37ef6044a8332 100644 --- a/editor/effects.js +++ b/editor/effects.js @@ -7,7 +7,7 @@ import { get, uniqueId } from 'lodash'; /** * WordPress dependencies */ -import { parse, getBlockType, switchToBlockType } from '@wordpress/blocks'; +import { parse, switchToBlockType, getBlockType } from '@wordpress/block-api'; import { __ } from '@wordpress/i18n'; /** @@ -35,6 +35,7 @@ import { isEditedPostDirty, isEditedPostNew, isEditedPostSaveable, + getEditorSettings, } from './selectors'; const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID'; @@ -182,9 +183,11 @@ export default { store.dispatch( createErrorNotice( message, { id: TRASH_POST_NOTICE_ID } ) ); }, MERGE_BLOCKS( action, store ) { - const { dispatch } = store; + const { dispatch, getState } = store; + const state = getState(); + const editorSettings = getEditorSettings( state ); const [ blockA, blockB ] = action.blocks; - const blockType = getBlockType( blockA.name ); + const blockType = getBlockType( blockA.name, editorSettings ); // Only focus the previous block if it's not mergeable if ( ! blockType.merge ) { @@ -196,7 +199,7 @@ export default { // thus, we transform the block to merge first const blocksWithTheSameType = blockA.name === blockB.name ? [ blockB ] - : switchToBlockType( blockB, blockA.name ); + : switchToBlockType( blockB, blockA.name, editorSettings ); // If the block types can not match, do nothing if ( ! blocksWithTheSameType || ! blocksWithTheSameType.length ) { @@ -251,13 +254,14 @@ export default { dispatch( savePost() ); }, - SET_INITIAL_POST( action ) { + SET_INITIAL_POST( action, { getState } ) { const { post } = action; const effects = []; // Parse content as blocks if ( post.content.raw ) { - effects.push( resetBlocks( parse( post.content.raw ) ) ); + const editorSettings = getEditorSettings( getState() ); + effects.push( resetBlocks( parse( post.content.raw, editorSettings ) ) ); } // Resetting post should occur after blocks have been reset, since it's diff --git a/editor/index.js b/editor/index.js index 6d62c169567b78..be1c46b36efa95 100644 --- a/editor/index.js +++ b/editor/index.js @@ -11,7 +11,7 @@ import 'moment-timezone/moment-timezone-utils'; /** * WordPress dependencies */ -import { EditableProvider } from '@wordpress/blocks'; +import { EditableProvider, getBlockTypes, getUnknownTypeHandlerName, getDefaultBlock, getCategories } from '@wordpress/blocks'; import { createElement, render } from '@wordpress/element'; import { PopoverProvider } from '@wordpress/components'; import { settings as dateSettings } from '@wordpress/date'; @@ -71,9 +71,16 @@ export function createEditorInstance( id, post, settings ) { settings = { ...DEFAULT_SETTINGS, ...settings, + blockTypes: getBlockTypes(), + fallbackBlockName: getUnknownTypeHandlerName(), + defaultBlockName: getDefaultBlock(), + categories: getCategories(), }; - store.dispatch( { type: 'SETUP_EDITOR' } ); + store.dispatch( { + type: 'SETUP_EDITOR', + settings, + } ); store.dispatch( setInitialPost( post ) ); diff --git a/editor/inserter/index.js b/editor/inserter/index.js index 6bc3df1d8696fc..04cc267d37f406 100644 --- a/editor/inserter/index.js +++ b/editor/inserter/index.js @@ -2,6 +2,7 @@ * External dependencies */ import { connect } from 'react-redux'; +import { flow } from 'lodash'; /** * WordPress dependencies @@ -9,7 +10,8 @@ import { connect } from 'react-redux'; import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { Popover, IconButton } from '@wordpress/components'; -import { createBlock } from '@wordpress/blocks'; +import { createBlock, getBlockType } from '@wordpress/block-api'; +import { withEditorSettings } from '@wordpress/blocks'; /** * Internal dependencies @@ -60,9 +62,10 @@ class Inserter extends Component { const { insertionPoint, onInsertBlock, + settings, } = this.props; onInsertBlock( - name, + getBlockType( name, settings ), insertionPoint ); } @@ -98,7 +101,7 @@ class Inserter extends Component { } } -export default connect( +const connectComponent = connect( ( state ) => { return { insertionPoint: getBlockInsertionPoint( state ), @@ -106,12 +109,17 @@ export default connect( }; }, ( dispatch ) => ( { - onInsertBlock( name, after ) { + onInsertBlock( blockType, after ) { dispatch( hideInsertionPoint() ); dispatch( insertBlock( - createBlock( name ), + createBlock( blockType ), after ) ); }, } ) +); + +export default flow( + withEditorSettings(), + connectComponent, )( Inserter ); diff --git a/editor/inserter/menu.js b/editor/inserter/menu.js index 525ac4ae3320c5..0a3028147ac500 100644 --- a/editor/inserter/menu.js +++ b/editor/inserter/menu.js @@ -11,7 +11,7 @@ import { __, _n, sprintf } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import { withFocusReturn, withInstanceId, withSpokenMessages } from '@wordpress/components'; import { keycodes } from '@wordpress/utils'; -import { getCategories, getBlockTypes, BlockIcon } from '@wordpress/blocks'; +import { withEditorSettings, BlockIcon } from '@wordpress/blocks'; /** * Internal dependencies @@ -60,7 +60,7 @@ export class InserterMenu extends Component { } componentDidUpdate( prevProps, prevState ) { - const searchResults = this.searchBlocks( getBlockTypes() ); + const searchResults = this.searchBlocks( this.props.settings.blockTypes ); // Announce the blocks search results to screen readers. if ( !! searchResults.length ) { this.props.debouncedSpeak( sprintf( _n( @@ -108,15 +108,15 @@ export class InserterMenu extends Component { getBlocksForCurrentTab() { // if we're searching, use everything, otherwise just get the blocks visible in this tab if ( this.state.filterValue ) { - return getBlockTypes(); + return this.props.settings.blockTypes; } switch ( this.state.tab ) { case 'recent': return this.props.recentlyUsedBlocks; case 'blocks': - return filter( getBlockTypes(), ( block ) => block.category !== 'embed' ); + return filter( this.props.settings.blockTypes, ( block ) => block.category !== 'embed' ); case 'embeds': - return filter( getBlockTypes(), ( block ) => block.category === 'embed' ); + return filter( this.props.settings.blockTypes, ( block ) => block.category === 'embed' ); } } @@ -126,7 +126,7 @@ export class InserterMenu extends Component { } const getCategoryIndex = ( item ) => { - return findIndex( getCategories(), ( category ) => category.slug === item.category ); + return findIndex( this.props.settings.categories, ( category ) => category.slug === item.category ); }; return sortBy( blockTypes, getCategoryIndex ); @@ -351,7 +351,7 @@ export class InserterMenu extends Component { } { this.state.tab === 'blocks' && ! isSearching && - getCategories() + this.props.settings.categories .map( ( category ) => !! visibleBlocksByCategory[ category.slug ] && (
    !! visibleBlocksByCategory[ category.slug ] && (
    !! visibleBlocksByCategory[ category.slug ] && (
    { - const unregisterAllBlocks = () => { - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); - }; - - afterEach( () => { - unregisterAllBlocks(); - } ); - - beforeEach( () => { - unregisterAllBlocks(); - registerBlockType( textBlock.name, textBlock ); - registerBlockType( advancedTextBlock.name, advancedTextBlock ); - registerBlockType( someOtherBlock.name, someOtherBlock ); - registerBlockType( moreBlock.name, moreBlock ); - registerBlockType( youtubeBlock.name, youtubeBlock ); - registerBlockType( textEmbedBlock.name, textEmbedBlock ); - } ); +const editorSettings = { + blockTypes: [ + textBlock, + advancedTextBlock, + someOtherBlock, + moreBlock, + youtubeBlock, + textEmbedBlock, + ], + categories: [ + { slug: 'common', title: 'Common Blocks' }, + { slug: 'layout', title: 'Layout Blocks' }, + { slug: 'embed', title: 'Embed' }, + ], +}; +describe( 'InserterMenu', () => { it( 'should show the recent tab by default', () => { const wrapper = mount( { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + settings={ editorSettings } /> ); @@ -111,6 +103,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [ advancedTextBlock ] } debouncedSpeak={ noop } + settings={ editorSettings } /> ); @@ -128,6 +121,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + settings={ editorSettings } /> ); const embedTab = wrapper.find( '.editor-inserter__tab' ) @@ -151,6 +145,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + settings={ editorSettings } /> ); const blocksTab = wrapper.find( '.editor-inserter__tab' ) @@ -176,6 +171,7 @@ describe( 'InserterMenu', () => { blocks={ [ { name: moreBlock.name } ] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + settings={ editorSettings } /> ); const blocksTab = wrapper.find( '.editor-inserter__tab' ) @@ -195,6 +191,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + settings={ editorSettings } /> ); wrapper.setState( { filterValue: 'text' } ); @@ -217,6 +214,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + settings={ editorSettings } /> ); wrapper.setState( { filterValue: ' text' } ); diff --git a/editor/modes/text-editor/index.js b/editor/modes/text-editor/index.js index 1961ae5c8f1b5e..6d2e90905f799b 100644 --- a/editor/modes/text-editor/index.js +++ b/editor/modes/text-editor/index.js @@ -8,7 +8,8 @@ import Textarea from 'react-autosize-textarea'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { parse } from '@wordpress/blocks'; +import { parse } from '@wordpress/block-api'; +import { withEditorSettings } from '@wordpress/blocks'; /** * Internal dependencies @@ -37,7 +38,7 @@ class TextEditor extends Component { onPersist( event ) { const { value } = event.target; if ( value !== this.state.initialValue ) { - this.props.onPersist( value ); + this.props.onPersist( value, this.props.settings ); this.setState( { initialValue: value, @@ -90,8 +91,8 @@ export default connect( onChange( content ) { return editPost( { content } ); }, - onPersist( content ) { - return resetBlocks( parse( content ) ); + onPersist( content, settings ) { + return resetBlocks( parse( content, settings ) ); }, } -)( TextEditor ); +)( withEditorSettings()( TextEditor ) ); diff --git a/editor/modes/visual-editor/block-list.js b/editor/modes/visual-editor/block-list.js index 21a2a0dfe2b686..4eba722fce4f98 100644 --- a/editor/modes/visual-editor/block-list.js +++ b/editor/modes/visual-editor/block-list.js @@ -10,7 +10,8 @@ import { throttle, reduce, noop } from 'lodash'; */ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import { serialize, getDefaultBlock, createBlock } from '@wordpress/blocks'; +import { serialize, createBlock, getBlockType } from '@wordpress/block-api'; +import { withEditorSettings } from '@wordpress/blocks'; import { IconButton } from '@wordpress/components'; import { keycodes } from '@wordpress/utils'; @@ -108,10 +109,10 @@ class VisualEditorBlockList extends Component { } onCopy( event ) { - const { multiSelectedBlocks } = this.props; + const { multiSelectedBlocks, settings } = this.props; if ( multiSelectedBlocks.length ) { - const serialized = serialize( multiSelectedBlocks ); + const serialized = serialize( multiSelectedBlocks, settings ); event.clipboardData.setData( 'text/plain', serialized ); event.clipboardData.setData( 'text/html', serialized ); @@ -194,12 +195,16 @@ class VisualEditorBlockList extends Component { } appendDefaultBlock() { - const newBlock = createBlock( getDefaultBlock() ); + const { settings } = this.props; + const defaultBlockType = getBlockType( settings.defaultBlockName, settings ); + const newBlock = createBlock( defaultBlockType ); this.props.onInsertBlock( newBlock ); } insertBlock( name ) { - const newBlock = createBlock( name ); + const { settings } = this.props; + const blockType = getBlockType( name, settings ); + const newBlock = createBlock( blockType ); this.props.onInsertBlock( newBlock ); } @@ -308,4 +313,4 @@ export default connect( dispatch( { type: 'REMOVE_BLOCKS', uids } ); }, } ) -)( VisualEditorBlockList ); +)( withEditorSettings()( VisualEditorBlockList ) ); diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js index e0f6e830e79ce5..8fad212945f8a7 100644 --- a/editor/modes/visual-editor/block.js +++ b/editor/modes/visual-editor/block.js @@ -12,8 +12,9 @@ import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup'; */ import { Children, Component } from '@wordpress/element'; import { IconButton, Toolbar } from '@wordpress/components'; +import { getBlockDefaultClassname, getBlockType, createBlock } from '@wordpress/block-api'; +import { withEditorSettings } from '@wordpress/blocks'; import { keycodes } from '@wordpress/utils'; -import { getBlockType, getBlockDefaultClassname, createBlock } from '@wordpress/blocks'; import { __, sprintf } from '@wordpress/i18n'; /** @@ -263,7 +264,7 @@ class VisualEditorBlock extends Component { event.preventDefault(); this.props.onInsertBlocksAfter( [ - createBlock( 'core/paragraph' ), + createBlock( getBlockType( 'core/paragraph', this.props.settings ) ), ] ); } } @@ -326,9 +327,8 @@ class VisualEditorBlock extends Component { } render() { - const { block, multiSelectedBlockUids } = this.props; - const { name: blockName, isValid } = block; - const blockType = getBlockType( blockName ); + const { block, multiSelectedBlockUids, blockType } = this.props; + const { isValid } = block; // translators: %s: Type of block (i.e. Text, Image etc) const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); // The block as rendered in the editor is composed of general block UI @@ -458,7 +458,7 @@ class VisualEditorBlock extends Component { } } -export default connect( +const connectComponent = connect( ( state, ownProps ) => { return { previousBlock: getPreviousBlock( state, ownProps.uid ), @@ -529,4 +529,13 @@ export default connect( dispatch( replaceBlocks( [ ownProps.uid ], blocks ) ); }, } ) -)( VisualEditorBlock ); +); + +const getEditorSettings = withEditorSettings( ( settings, ownProps ) => { + return { + blockType: getBlockType( ownProps.block.name, settings ), + settings, + }; +} ); + +export default connectComponent( getEditorSettings( VisualEditorBlock ) ); diff --git a/editor/selectors.js b/editor/selectors.js index 6d9716418cc81e..ecd91f809fd925 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -8,7 +8,7 @@ import createSelector from 'rememo'; /** * WordPress dependencies */ -import { serialize, getBlockType } from '@wordpress/blocks'; +import { serialize, getBlockType } from '@wordpress/block-api'; import { __ } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; @@ -736,17 +736,19 @@ export function getSuggestedPostFormat( state ) { */ export const getEditedPostContent = createSelector( ( state ) => { + const editorSettings = getEditorSettings( state ); const edits = getPostEdits( state ); if ( 'content' in edits ) { return edits.content; } - return serialize( getBlocks( state ) ); + return serialize( getBlocks( state ), editorSettings ); }, ( state ) => [ state.editor.edits.content, state.editor.blocksByUid, state.editor.blockOrder, + state.editorSettings, ], ); @@ -760,6 +762,17 @@ export function getNotices( state ) { return values( state.notices ); } +/** + * + * Returns the editor settings + * + * @param {Object} state Global application state + * @return {Object} The editor settings + */ +export function getEditorSettings( state ) { + return state.editorSettings; +} + /** * Resolves the list of recently used block names into a list of block type settings. * @@ -768,5 +781,6 @@ export function getNotices( state ) { */ export function getRecentlyUsedBlocks( state ) { // resolves the block names in the state to the block type settings - return state.userData.recentlyUsedBlocks.map( blockType => getBlockType( blockType ) ); + return state.userData.recentlyUsedBlocks + .map( blockName => getBlockType( blockName, getEditorSettings( state ) ) ); } diff --git a/editor/sidebar/block-inspector/class-name.js b/editor/sidebar/block-inspector/class-name.js index 3e7b871c5c63c5..0bd560b1804db1 100644 --- a/editor/sidebar/block-inspector/class-name.js +++ b/editor/sidebar/block-inspector/class-name.js @@ -7,7 +7,8 @@ import { connect } from 'react-redux'; * WordPress dependencies */ import { Component } from '@wordpress/element'; -import { getBlockType, InspectorControls } from '@wordpress/blocks'; +import { getBlockType } from '@wordpress/block-api'; +import { InspectorControls, withEditorSettings } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; /** @@ -29,8 +30,7 @@ class BlockInspectorClassName extends Component { } render() { - const { selectedBlock } = this.props; - const blockType = getBlockType( selectedBlock.name ); + const { selectedBlock, blockType } = this.props; if ( false === blockType.className ) { return null; } @@ -47,7 +47,7 @@ class BlockInspectorClassName extends Component { } } -export default connect( +const connectComponent = connect( ( state ) => { return { selectedBlock: getSelectedBlock( state ), @@ -56,4 +56,12 @@ export default connect( { setAttributes: updateBlockAttributes, } -)( BlockInspectorClassName ); +); + +const getEditorSettings = withEditorSettings( ( settings, ownProps ) => { + return { + blockType: getBlockType( ownProps.selectedBlock.name, settings ), + }; +} ); + +export default connectComponent( getEditorSettings( BlockInspectorClassName ) ); diff --git a/editor/state.js b/editor/state.js index e298dde66ed468..1af0d1bc69ee4c 100644 --- a/editor/state.js +++ b/editor/state.js @@ -7,11 +7,6 @@ import refx from 'refx'; import multi from 'redux-multi'; import { reduce, keyBy, first, last, omit, without, flowRight, mapValues } from 'lodash'; -/** - * WordPress dependencies - */ -import { getBlockTypes } from '@wordpress/blocks'; - /** * Internal dependencies */ @@ -251,7 +246,7 @@ export const userData = combineReducers( { // This is where we initially populate the recently used blocks, // for now this inserts blocks from the common category, but will // load this from an API in the future. - return getBlockTypes() + return action.settings.blockTypes .filter( ( blockType ) => 'common' === blockType.category ) .slice( 0, maxRecent ) .map( ( blockType ) => blockType.name ); @@ -512,6 +507,15 @@ export function notices( state = {}, action ) { return state; } +export function editorSettings( state = { blockTypes: [] }, action ) { + switch ( action.type ) { + case 'SETUP_EDITOR': + return action.settings; + } + + return state; +} + /** * Creates a new instance of a Redux store. * @@ -531,6 +535,7 @@ export function createReduxStore() { saving, notices, userData, + editorSettings, } ) ); const enhancers = [ applyMiddleware( multi, refx( effects ) ) ]; diff --git a/editor/test/effects.js b/editor/test/effects.js index cff639382b0f1a..65057810335711 100644 --- a/editor/test/effects.js +++ b/editor/test/effects.js @@ -3,11 +3,6 @@ */ import { noop } from 'lodash'; -/** - * WordPress dependencies - */ -import { getBlockTypes, unregisterBlockType, registerBlockType, createBlock } from '@wordpress/blocks'; - /** * Internal dependencies */ @@ -26,21 +21,14 @@ import * as selectors from '../selectors'; jest.mock( '../selectors' ); describe( 'effects', () => { - const defaultBlockSettings = { save: () => 'Saved', category: 'common' }; + const defaultBlockType = { name: 'core/test-block', save: () => 'Saved', category: 'common' }; beforeEach( () => jest.resetAllMocks() ); describe( '.MERGE_BLOCKS', () => { const handler = effects.MERGE_BLOCKS; - afterEach( () => { - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); - } ); - it( 'should only focus the blockA if the blockA has no merge function', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); const blockA = { uid: 'chicken', name: 'core/test-block', @@ -49,15 +37,23 @@ describe( 'effects', () => { uid: 'ribs', name: 'core/test-block', }; + const state = { + editorSettings: { + blockTypes: [ defaultBlockType ], + }, + }; const dispatch = jest.fn(); - handler( mergeBlocks( blockA, blockB ), { dispatch } ); + const getState = () => state; + selectors.getEditorSettings.mockReturnValue( state.editorSettings ); + handler( mergeBlocks( blockA, blockB ), { dispatch, getState } ); expect( dispatch ).toHaveBeenCalledTimes( 1 ); expect( dispatch ).toHaveBeenCalledWith( focusBlock( 'chicken' ) ); } ); it( 'should merge the blocks if blocks of the same type', () => { - registerBlockType( 'core/test-block', { + const testBlockType = { + name: 'core/test-block', merge( attributes, attributesToMerge ) { return { content: attributes.content + ' ' + attributesToMerge.content, @@ -65,7 +61,7 @@ describe( 'effects', () => { }, save: noop, category: 'common', - } ); + }; const blockA = { uid: 'chicken', name: 'core/test-block', @@ -77,7 +73,14 @@ describe( 'effects', () => { attributes: { content: 'ribs' }, }; const dispatch = jest.fn(); - handler( mergeBlocks( blockA, blockB ), { dispatch } ); + const state = { + editorSettings: { + blockTypes: [ testBlockType ], + }, + }; + const getState = () => state; + selectors.getEditorSettings.mockReturnValue( state.editorSettings ); + handler( mergeBlocks( blockA, blockB ), { dispatch, getState } ); expect( dispatch ).toHaveBeenCalledTimes( 2 ); expect( dispatch ).toHaveBeenCalledWith( focusBlock( 'chicken', { offset: -1 } ) ); @@ -89,7 +92,8 @@ describe( 'effects', () => { } ); it( 'should not merge the blocks have different types without transformation', () => { - registerBlockType( 'core/test-block', { + const testBlockType = { + name: 'core/test-block', merge( attributes, attributesToMerge ) { return { content: attributes.content + ' ' + attributesToMerge.content, @@ -97,8 +101,11 @@ describe( 'effects', () => { }, save: noop, category: 'common', - } ); - registerBlockType( 'core/test-block-2', defaultBlockSettings ); + }; + const testBlockType2 = { + ...defaultBlockType, + name: 'core/test-block-2', + }; const blockA = { uid: 'chicken', name: 'core/test-block', @@ -110,13 +117,21 @@ describe( 'effects', () => { attributes: { content: 'ribs' }, }; const dispatch = jest.fn(); - handler( mergeBlocks( blockA, blockB ), { dispatch } ); + const state = { + editorSettings: { + blockTypes: [ testBlockType, testBlockType2 ], + }, + }; + const getState = () => state; + selectors.getEditorSettings.mockReturnValue( state.editorSettings ); + handler( mergeBlocks( blockA, blockB ), { dispatch, getState } ); expect( dispatch ).not.toHaveBeenCalled(); } ); it( 'should transform and merge the blocks', () => { - registerBlockType( 'core/test-block', { + const testBlockType = { + name: 'core/test-block', attributes: { content: { type: 'string', @@ -129,8 +144,9 @@ describe( 'effects', () => { }, save: noop, category: 'common', - } ); - registerBlockType( 'core/test-block-2', { + }; + const testBlockType2 = { + name: 'core/test-block-2', attributes: { content: { type: 'string', @@ -141,15 +157,18 @@ describe( 'effects', () => { type: 'blocks', blocks: [ 'core/test-block' ], transform: ( { content2 } ) => { - return createBlock( 'core/test-block', { - content: content2, - } ); + return { + name: 'core/test-block', + attributes: { + content: content2, + }, + }; }, } ], }, save: noop, category: 'common', - } ); + }; const blockA = { uid: 'chicken', name: 'core/test-block', @@ -161,7 +180,14 @@ describe( 'effects', () => { attributes: { content2: 'ribs' }, }; const dispatch = jest.fn(); - handler( mergeBlocks( blockA, blockB ), { dispatch } ); + const state = { + editorSettings: { + blockTypes: [ testBlockType, testBlockType2 ], + }, + }; + const getState = () => state; + selectors.getEditorSettings.mockReturnValue( state.editorSettings ); + handler( mergeBlocks( blockA, blockB ), { dispatch, getState } ); expect( dispatch ).toHaveBeenCalledTimes( 2 ); expect( dispatch ).toHaveBeenCalledWith( focusBlock( 'chicken', { offset: -1 } ) ); @@ -247,8 +273,14 @@ describe( 'effects', () => { describe( '.SET_INITIAL_POST', () => { const handler = effects.SET_INITIAL_POST; + const getState = () => ( { + editorSettings: { + blockTypes: [ defaultBlockType ], + }, + } ); it( 'should return post reset action', () => { + selectors.getEditorSettings.mockReturnValue( getState().editorSettings ); const post = { id: 1, title: { @@ -260,7 +292,7 @@ describe( 'effects', () => { status: 'draft', }; - const result = handler( { post } ); + const result = handler( { post }, { getState } ); expect( result ).toEqual( [ resetPost( post ), @@ -268,7 +300,7 @@ describe( 'effects', () => { } ); it( 'should return block reset with non-empty content', () => { - registerBlockType( 'core/test-block', defaultBlockSettings ); + selectors.getEditorSettings.mockReturnValue( getState().editorSettings ); const post = { id: 1, title: { @@ -280,7 +312,7 @@ describe( 'effects', () => { status: 'draft', }; - const result = handler( { post } ); + const result = handler( { post }, { getState } ); expect( result ).toHaveLength( 2 ); expect( result ).toContainEqual( resetPost( post ) ); @@ -290,6 +322,7 @@ describe( 'effects', () => { } ); it( 'should return post setup action only if auto-draft', () => { + selectors.getEditorSettings.mockReturnValue( getState().editorSettings ); const post = { id: 1, title: { @@ -301,7 +334,7 @@ describe( 'effects', () => { status: 'auto-draft', }; - const result = handler( { post } ); + const result = handler( { post }, { getState } ); expect( result ).toEqual( [ resetPost( post ), diff --git a/editor/test/selectors.js b/editor/test/selectors.js index 2630476304ce5c..b81310cb2112a4 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -894,6 +894,9 @@ describe( 'selectors', () => { blockOrder: [], edits: {}, }, + editorSettings: { + blockTypes: [], + }, currentPost: {}, }; @@ -907,6 +910,9 @@ describe( 'selectors', () => { blockOrder: [], edits: {}, }, + editorSettings: { + blockTypes: [], + }, currentPost: { title: 'sassel', }, @@ -922,6 +928,9 @@ describe( 'selectors', () => { blockOrder: [], edits: {}, }, + editorSettings: { + blockTypes: [], + }, currentPost: { excerpt: 'sassel', }, @@ -945,6 +954,16 @@ describe( 'selectors', () => { blockOrder: [ 123 ], edits: {}, }, + editorSettings: { + blockTypes: [ { + name: 'core/test-block', + attributes: { + text: { + type: 'string', + }, + }, + } ], + }, currentPost: {}, }; diff --git a/editor/test/state.js b/editor/test/state.js index 6b7dcc786aa8eb..228d5d817b3e15 100644 --- a/editor/test/state.js +++ b/editor/test/state.js @@ -7,7 +7,7 @@ import deepFreeze from 'deep-freeze'; /** * WordPress dependencies */ -import { registerBlockType, unregisterBlockType, getBlockType } from '@wordpress/blocks'; +import { registerBlockType, unregisterBlockType, getBlockTypes, getBlockType } from '@wordpress/blocks'; /** * Internal dependencies @@ -967,10 +967,15 @@ describe( 'state', () => { it( 'should populate recently used blocks with blocks from the common category', () => { const initial = userData( undefined, { type: 'SETUP_EDITOR', + settings: { + blockTypes: getBlockTypes(), + }, } ); initial.recentlyUsedBlocks.forEach( - block => expect( getBlockType( block ).category ).toEqual( 'common' ) + block => expect( getBlockType( block, { + blockTypes: getBlockTypes(), + } ).category ).toEqual( 'common' ) ); expect( initial.recentlyUsedBlocks ).toHaveLength( 8 ); } ); diff --git a/lib/client-assets.php b/lib/client-assets.php index 390599864885e9..a4a8e78d1a6fbe 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -132,10 +132,16 @@ function gutenberg_register_scripts_and_styles() { array( 'wp-element', 'wp-a11y', 'wp-i18n', 'wp-utils' ), filemtime( gutenberg_dir_path() . 'components/build/index.js' ) ); + wp_register_script( + 'wp-block-api', + gutenberg_url( 'block-api/build/index.js' ), + array( 'wp-element', 'wp-utils' ), + filemtime( gutenberg_dir_path() . 'blocks/build/index.js' ) + ); wp_register_script( 'wp-blocks', gutenberg_url( 'blocks/build/index.js' ), - array( 'wp-element', 'wp-components', 'wp-utils', 'wp-i18n', 'tinymce-nightly', 'tinymce-nightly-lists', 'tinymce-nightly-paste', 'tinymce-nightly-table', 'media-views', 'media-models' ), + array( 'wp-block-api', 'wp-element', 'wp-components', 'wp-utils', 'wp-i18n', 'tinymce-nightly', 'tinymce-nightly-lists', 'tinymce-nightly-paste', 'tinymce-nightly-table', 'media-views', 'media-models' ), filemtime( gutenberg_dir_path() . 'blocks/build/index.js' ) ); wp_add_inline_script( @@ -532,7 +538,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { wp_enqueue_script( 'wp-editor', gutenberg_url( 'editor/build/index.js' ), - array( 'wp-api', 'wp-date', 'wp-i18n', 'wp-blocks', 'wp-element', 'wp-components', 'wp-utils', 'editor' ), + array( 'wp-api', 'wp-date', 'wp-i18n', 'wp-blocks', 'wp-block-api', 'wp-element', 'wp-components', 'wp-utils', 'editor' ), filemtime( gutenberg_dir_path() . 'editor/build/index.js' ), true // enqueue in the footer. ); diff --git a/package.json b/package.json index c9d6de94448399..d436e8df4a0a76 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ }, "jest": { "collectCoverageFrom": [ + "block-api/**/*.js", "blocks/**/*.js", "components/**/*.js", "date/**/*.js", @@ -105,7 +106,7 @@ "coverageDirectory": "coverage", "moduleNameMapper": { "\\.(scss|css)$": "/test/style-mock.js", - "@wordpress\\/(blocks|components|date|editor|element|i18n|utils)": "$1" + "@wordpress\\/(block-api|blocks|components|date|editor|element|i18n|utils)": "$1" }, "modulePaths": [ "" @@ -116,7 +117,7 @@ ], "setupTestFrameworkScriptFile": "/test/setup-test-framework.js", "testMatch": [ - "/(blocks|components|date|editor|element|i18n|utils)/**/test/*.js" + "/(blocks|components|date|editor|element|i18n|utils|block-api)/**/test/*.js" ], "timers": "fake", "transform": { diff --git a/test/setup-wp-aliases.js b/test/setup-wp-aliases.js index baf885f07c08d4..0519f93b785d16 100644 --- a/test/setup-wp-aliases.js +++ b/test/setup-wp-aliases.js @@ -5,6 +5,7 @@ 'components', 'utils', 'blocks', + 'block-api', 'date', 'editor', ].forEach( entryPointName => { diff --git a/webpack.config.js b/webpack.config.js index 4d605d6ab2197f..d30398e04d9b7b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -39,6 +39,7 @@ const extractConfig = { const entryPointNames = [ 'blocks', 'components', + 'block-api', 'date', 'editor', 'element',