diff --git a/.eslintrc.json b/.eslintrc.json index 2cb6635e3c1330..339e9ac6111fe4 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -63,6 +63,8 @@ "one-var": "off", "padded-blocks": [ "error", "never" ], "quote-props": [ "error", "as-needed", { "keywords": true } ], + "react/prop-types": "off", + "react/display-name": "off", "semi": "error", "semi-spacing": "error", "space-before-blocks": [ "error", "always" ], @@ -74,7 +76,6 @@ "!": true } } ], - "valid-jsdoc": [ "error", { "requireReturn": false } ], - "react/prop-types": "off" + "valid-jsdoc": [ "error", { "requireReturn": false } ] } } diff --git a/blocks/index.js b/blocks/index.js index 83d496a96f7aa3..714407089947f0 100644 --- a/blocks/index.js +++ b/blocks/index.js @@ -7,4 +7,11 @@ export { query }; export { default as Editable } from './components/editable'; export { default as parse } from './parser'; export { getCategories } from './categories'; -export { registerBlock, unregisterBlock, getBlockSettings, getBlocks } from './registration'; +export { + registerBlock, + unregisterBlock, + setUnknownTypeHandler, + getUnknownTypeHandler, + getBlockSettings, + getBlocks +} from './registration'; diff --git a/blocks/parser.js b/blocks/parser.js index d89ea1fddd5551..39d74c6a36b018 100644 --- a/blocks/parser.js +++ b/blocks/parser.js @@ -7,24 +7,26 @@ import * as query from 'hpq'; * Internal dependencies */ import { parse as grammarParse } from './post.pegjs'; -import { getBlockSettings } from './registration'; +import { getBlockSettings, getUnknownTypeHandler } from './registration'; /** * Returns the block attributes of a registered block node given its settings. * - * @param {Object} blockNode Parsed block node - * @param {Object} blockSettings Block settings - * @return {Object} Block state, or undefined if type unknown + * @param {Object} blockNode Parsed block node + * @param {Object} blockSettings Block settings + * @return {Object} Block attributes */ export function getBlockAttributes( blockNode, blockSettings ) { const { rawContent } = blockNode; // Merge attributes from parse with block implementation let { attrs } = blockNode; - if ( 'function' === typeof blockSettings.attributes ) { - attrs = { ...attrs, ...blockSettings.attributes( rawContent ) }; - } else if ( blockSettings.attributes ) { - attrs = { ...attrs, ...query.parse( rawContent, blockSettings.attributes ) }; + if ( blockSettings ) { + if ( 'function' === typeof blockSettings.attributes ) { + attrs = { ...attrs, ...blockSettings.attributes( rawContent ) }; + } else if ( blockSettings.attributes ) { + attrs = { ...attrs, ...query.parse( rawContent, blockSettings.attributes ) }; + } } return attrs; @@ -38,11 +40,21 @@ export function getBlockAttributes( blockNode, blockSettings ) { */ export default function parse( content ) { return grammarParse( content ).reduce( ( memo, blockNode ) => { - const settings = getBlockSettings( blockNode.blockType ); + // Use type from block node, otherwise find unknown handler + let { blockType = getUnknownTypeHandler() } = blockNode; + + // Try finding settings for known block type, else again fall back + let settings = getBlockSettings( blockType ); + if ( ! settings ) { + blockType = getUnknownTypeHandler(); + settings = getBlockSettings( blockType ); + } + // Include in set only if settings were determined if ( settings ) { memo.push( { - blockType: blockNode.blockType, + blockType, + rawContent: blockNode.rawContent, attributes: getBlockAttributes( blockNode, settings ) } ); } diff --git a/blocks/post.pegjs b/blocks/post.pegjs index af19f0f396a418..b77b8a82ac14b3 100644 --- a/blocks/post.pegjs +++ b/blocks/post.pegjs @@ -20,7 +20,6 @@ WP_Block_Html = ts:(!WP_Block_Balanced c:Any { return c })+ { return { - blockType: 'html', attrs: {}, rawContent: ts.join( '' ) } diff --git a/blocks/registration.js b/blocks/registration.js index f3593ce9b09f8e..3dcd1f86b356c6 100644 --- a/blocks/registration.js +++ b/blocks/registration.js @@ -3,10 +3,17 @@ /** * Block settings keyed by block slug. * - * @var {Object} blocks + * @type {Object} */ const blocks = {}; +/** + * Slug of block handling unknown types. + * + * @type {?string} + */ +let unknownTypeHandler; + /** * Registers a new block provided a unique slug and an object defining its * behavior. Once registered, the block is made available as an option to any @@ -60,6 +67,25 @@ export function unregisterBlock( slug ) { return oldBlock; } +/** + * Assigns slug of block handling unknown block types. + * + * @param {string} slug Block slug + */ +export function setUnknownTypeHandler( slug ) { + unknownTypeHandler = slug; +} + +/** + * Retrieves slug of block handling unknown block types, or undefined if no + * handler has been defined. + * + * @return {?string} Blog slug + */ +export function getUnknownTypeHandler() { + return unknownTypeHandler; +} + /** * Returns settings associated with a registered block. * diff --git a/blocks/test/parser.js b/blocks/test/parser.js index 907489f7552e50..41dd68a5a45454 100644 --- a/blocks/test/parser.js +++ b/blocks/test/parser.js @@ -8,10 +8,11 @@ import { text } from 'hpq'; * Internal dependencies */ import { default as parse, getBlockAttributes } from '../parser'; -import { getBlocks, unregisterBlock, registerBlock } from '../registration'; +import { getBlocks, unregisterBlock, setUnknownTypeHandler, registerBlock } from '../registration'; describe( 'block parser', () => { - beforeEach( () => { + afterEach( () => { + setUnknownTypeHandler( undefined ); getBlocks().forEach( ( block ) => { unregisterBlock( block.slug ); } ); @@ -50,36 +51,78 @@ describe( 'block parser', () => { const blockNode = { blockType: 'core/test-block', - attrs: {}, + attrs: { + align: 'left' + }, rawContent: 'Ribs & Chicken' }; expect( getBlockAttributes( blockNode, blockSettings ) ).to.eql( { + align: 'left', emphasis: '& Chicken' } ); } ); + + it( 'should return parsed attributes for block without attributes defined', () => { + const blockSettings = {}; + + const blockNode = { + blockType: 'core/test-block', + attrs: { + align: 'left' + }, + rawContent: 'Ribs & Chicken' + }; + + expect( getBlockAttributes( blockNode, blockSettings ) ).to.eql( { + align: 'left' + } ); + } ); } ); describe( 'parse()', () => { - it( 'should parse the post content properly and ignore unknown blocks', () => { - const blockSettings = { + it( 'should parse the post content, ignoring unknown blocks', () => { + registerBlock( 'core/test-block', { attributes: function( rawContent ) { return { content: rawContent + ' & Chicken' }; } - }; - registerBlock( 'core/test-block', blockSettings ); + } ); - const postContent = 'Ribs' + - 'Ribs'; + const parsed = parse( + 'Ribs' + + '
Broccoli
' + + 'Ribs' + ); - expect( parse( postContent ) ).to.eql( [ { + expect( parsed ).to.eql( [ { blockType: 'core/test-block', attributes: { content: 'Ribs & Chicken' - } + }, + rawContent: 'Ribs' } ] ); } ); + + it( 'should parse the post content, using unknown block handler', () => { + registerBlock( 'core/test-block', {} ); + registerBlock( 'core/unknown-block', {} ); + + setUnknownTypeHandler( 'core/unknown-block' ); + + const parsed = parse( + 'Ribs' + + 'Broccoli
' + + 'Ribs' + ); + + expect( parsed ).to.have.lengthOf( 3 ); + expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ + 'core/test-block', + 'core/unknown-block', + 'core/unknown-block' + ] ); + } ); } ); } ); diff --git a/blocks/test/registration.js b/blocks/test/registration.js index ee6e15137c17d3..3af2d10dfe6c18 100644 --- a/blocks/test/registration.js +++ b/blocks/test/registration.js @@ -9,18 +9,26 @@ import sinon from 'sinon'; /** * Internal dependencies */ -import { getBlocks, unregisterBlock, registerBlock, getBlockSettings } from '../registration'; +import { + registerBlock, + unregisterBlock, + setUnknownTypeHandler, + getUnknownTypeHandler, + getBlockSettings, + getBlocks +} from '../registration'; describe( 'blocks', () => { // Reset block state before each test. beforeEach( () => { - getBlocks().forEach( block => { - unregisterBlock( block.slug ); - } ); sinon.stub( console, 'error' ); } ); afterEach( () => { + getBlocks().forEach( block => { + unregisterBlock( block.slug ); + } ); + setUnknownTypeHandler( undefined ); console.error.restore(); } ); @@ -86,6 +94,20 @@ describe( 'blocks', () => { } ); } ); + describe( 'setUnknownTypeHandler()', () => { + it( 'assigns unknown type handler', () => { + setUnknownTypeHandler( 'core/test-block' ); + + expect( getUnknownTypeHandler() ).to.equal( 'core/test-block' ); + } ); + } ); + + describe( 'getUnknownTypeHandler()', () => { + it( 'defaults to undefined', () => { + expect( getUnknownTypeHandler() ).to.be.undefined(); + } ); + } ); + describe( 'getBlockSettings()', () => { it( 'should return { slug } for blocks with no settings', () => { registerBlock( 'core/test-block' ); diff --git a/editor/blocks/freeform/index.js b/editor/blocks/freeform/index.js new file mode 100644 index 00000000000000..b31c419aaf2d6e --- /dev/null +++ b/editor/blocks/freeform/index.js @@ -0,0 +1,23 @@ +const { html } = wp.blocks.query; + +wp.blocks.registerBlock( 'core/freeform', { + title: 'Freeform', + + icon: 'text', + + category: 'common', + + attributes: { + html: html() + }, + + edit( { attributes } ) { + return