From 716811f11e24e11cf8a76756a6bba5a13409f971 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Mon, 27 Mar 2017 15:29:12 -0400 Subject: [PATCH 1/9] Render blocks as components --- editor/blocks/text/index.js | 4 ++-- editor/editor/mode/visual.js | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/editor/blocks/text/index.js b/editor/blocks/text/index.js index 863256be4a27d4..6acb2083751216 100644 --- a/editor/blocks/text/index.js +++ b/editor/blocks/text/index.js @@ -10,7 +10,7 @@ wp.blocks.registerBlock( 'core/text', { value: html() }, - edit( attributes, onChange ) { + edit( { attributes, onChange } ) { return ( { attributes.value }

; } } ); diff --git a/editor/editor/mode/visual.js b/editor/editor/mode/visual.js index 945783aecddae1..ca5117b497b0b7 100644 --- a/editor/editor/mode/visual.js +++ b/editor/editor/mode/visual.js @@ -20,11 +20,16 @@ function Blocks( { blocks, onChange } ) { return (
- { blocks.map( ( block, index ) => -
- { wp.blocks.getBlockSettings( block.blockType ).edit( block.attributes, onChangeBlock( index ) ) } -
- ) } + { blocks.map( ( block, index ) => { + const settings = wp.blocks.getBlockSettings( block.blockType ); + + return ( + + ); + } ) }
From 67adf4df84f2c1060aa67b4a134b4da4724b819c Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Tue, 28 Mar 2017 19:32:30 -0400 Subject: [PATCH 2/9] Add missing test for fallback parsed on undefined attributes --- blocks/test/parser.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/blocks/test/parser.js b/blocks/test/parser.js index 907489f7552e50..90b38e1b5d211a 100644 --- a/blocks/test/parser.js +++ b/blocks/test/parser.js @@ -50,14 +50,33 @@ 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()', () => { From ba9635b97a41b7d9598be1cf51b7795a10550bee Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 30 Mar 2017 10:28:04 -0400 Subject: [PATCH 3/9] Disable react/display-name ESLint rule --- .eslintrc.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 } ] } } From 2b6130548301bd23b6ed8fc1f2503a1f01e02f83 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 30 Mar 2017 10:30:42 -0400 Subject: [PATCH 4/9] Include unknown blocks in parse return value --- blocks/parser.js | 34 +++++++++++++++++----------------- blocks/test/parser.js | 20 ++++++++++++++------ 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/blocks/parser.js b/blocks/parser.js index d89ea1fddd5551..171aca649d0fcf 100644 --- a/blocks/parser.js +++ b/blocks/parser.js @@ -12,19 +12,21 @@ import { getBlockSettings } 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; @@ -37,16 +39,14 @@ export function getBlockAttributes( blockNode, blockSettings ) { * @return {Array} Block list */ export default function parse( content ) { - return grammarParse( content ).reduce( ( memo, blockNode ) => { + return grammarParse( content ).map( ( blockNode ) => { const settings = getBlockSettings( blockNode.blockType ); + const { blockType, rawContent } = blockNode; - if ( settings ) { - memo.push( { - blockType: blockNode.blockType, - attributes: getBlockAttributes( blockNode, settings ) - } ); - } - - return memo; - }, [] ); + return { + blockType, + rawContent, + attributes: getBlockAttributes( blockNode, settings ) + }; + } ); } diff --git a/blocks/test/parser.js b/blocks/test/parser.js index 90b38e1b5d211a..eaecdc850c8e24 100644 --- a/blocks/test/parser.js +++ b/blocks/test/parser.js @@ -80,7 +80,7 @@ describe( 'block parser', () => { } ); describe( 'parse()', () => { - it( 'should parse the post content properly and ignore unknown blocks', () => { + it( 'should parse the post content properly', () => { const blockSettings = { attributes: function( rawContent ) { return { @@ -93,12 +93,20 @@ describe( 'block parser', () => { const postContent = 'Ribs' + 'Ribs'; - expect( parse( postContent ) ).to.eql( [ { - blockType: 'core/test-block', - attributes: { - content: 'Ribs & Chicken' + expect( parse( postContent ) ).to.eql( [ + { + blockType: 'core/test-block', + attributes: { + content: 'Ribs & Chicken' + }, + rawContent: 'Ribs' + }, + { + blockType: 'core/unknown-block', + attributes: {}, + rawContent: 'Ribs' } - } ] ); + ] ); } ); } ); } ); From a4e2994b00278584dda5ff5ef531ee2c40d3817a Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Thu, 30 Mar 2017 10:31:14 -0400 Subject: [PATCH 5/9] Fall back to dangerous div rendering with undefined edit --- editor/editor/mode/visual.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/editor/editor/mode/visual.js b/editor/editor/mode/visual.js index ca5117b497b0b7..a635937dcbfbe6 100644 --- a/editor/editor/mode/visual.js +++ b/editor/editor/mode/visual.js @@ -23,8 +23,17 @@ function Blocks( { blocks, onChange } ) { { blocks.map( ( block, index ) => { const settings = wp.blocks.getBlockSettings( block.blockType ); + let Edit; + if ( settings ) { + Edit = settings.edit || settings.save; + } + + if ( ! Edit ) { + Edit = () =>
; + } + return ( - From 9bf268cdfff496e182fa5c9131a48fa77282c2fb Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 31 Mar 2017 10:36:31 -0400 Subject: [PATCH 6/9] Allow assignment of unknown block type handler --- blocks/index.js | 9 +++++++- blocks/parser.js | 34 ++++++++++++++++++--------- blocks/post.pegjs | 1 - blocks/registration.js | 27 +++++++++++++++++++++- blocks/test/parser.js | 45 +++++++++++++++++++++--------------- blocks/test/registration.js | 30 ++++++++++++++++++++---- editor/editor/mode/visual.js | 34 +++++++++++++-------------- 7 files changed, 125 insertions(+), 55 deletions(-) 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 171aca649d0fcf..39d74c6a36b018 100644 --- a/blocks/parser.js +++ b/blocks/parser.js @@ -7,7 +7,7 @@ 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. @@ -39,14 +39,26 @@ export function getBlockAttributes( blockNode, blockSettings ) { * @return {Array} Block list */ export default function parse( content ) { - return grammarParse( content ).map( ( blockNode ) => { - const settings = getBlockSettings( blockNode.blockType ); - const { blockType, rawContent } = blockNode; - - return { - blockType, - rawContent, - attributes: getBlockAttributes( blockNode, settings ) - }; - } ); + return grammarParse( content ).reduce( ( memo, blockNode ) => { + // 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, + rawContent: blockNode.rawContent, + attributes: getBlockAttributes( blockNode, settings ) + } ); + } + + return memo; + }, [] ); } 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..edc182a9d082a0 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,24 @@ 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. + * + * @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 eaecdc850c8e24..d22fc241ce7964 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 ); } ); @@ -80,33 +81,39 @@ describe( 'block parser', () => { } ); describe( 'parse()', () => { - it( 'should parse the post content properly', () => { - 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' + + '

Broccoli

' + 'Ribs'; - expect( parse( postContent ) ).to.eql( [ - { - blockType: 'core/test-block', - attributes: { - content: 'Ribs & Chicken' - }, - rawContent: 'Ribs' + expect( parse( postContent ) ).to.eql( [ { + blockType: 'core/test-block', + attributes: { + content: 'Ribs & Chicken' }, - { - blockType: 'core/unknown-block', - attributes: {}, - rawContent: 'Ribs' - } - ] ); + 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 postContent = 'Ribs' + + '

Broccoli

' + + 'Ribs'; + + expect( parse( postContent ) ).to.have.lengthOf( 3 ); } ); } ); } ); 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/editor/mode/visual.js b/editor/editor/mode/visual.js index a635937dcbfbe6..97346147f12800 100644 --- a/editor/editor/mode/visual.js +++ b/editor/editor/mode/visual.js @@ -19,27 +19,25 @@ function Blocks( { blocks, onChange } ) { return (
-
- { blocks.map( ( block, index ) => { - const settings = wp.blocks.getBlockSettings( block.blockType ); + { blocks.map( ( block, index ) => { + const settings = wp.blocks.getBlockSettings( block.blockType ); - let Edit; - if ( settings ) { - Edit = settings.edit || settings.save; - } + let BlockEdit; + if ( settings ) { + BlockEdit = settings.edit || settings.save; + } - if ( ! Edit ) { - Edit = () =>
; - } + if ( ! BlockEdit ) { + return; + } - return ( - - ); - } ) } -
+ return ( + + ); + } ) }
); From 8ff84e97851f26ec905645ecb313ff7f888b49af Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 31 Mar 2017 10:36:41 -0400 Subject: [PATCH 7/9] Register freeform block as unknown block handler --- editor/blocks/freeform/index.js | 23 +++++++++++++++++++++++ editor/blocks/index.js | 1 + 2 files changed, 24 insertions(+) create mode 100644 editor/blocks/freeform/index.js 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
{ attributes.html }
; + }, + + save( { attributes } ) { + return attributes.html; + } +} ); + +wp.blocks.setUnknownTypeHandler( 'core/freeform' ); diff --git a/editor/blocks/index.js b/editor/blocks/index.js index 7319406b858f04..a31abdee56c194 100644 --- a/editor/blocks/index.js +++ b/editor/blocks/index.js @@ -1 +1,2 @@ +import './freeform'; import './text'; From eaa5dfc1959c2e291311d3c01e50278a4b90de0e Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 31 Mar 2017 10:38:10 -0400 Subject: [PATCH 8/9] Clarify possible undefined return value from unknown type getter --- blocks/registration.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/blocks/registration.js b/blocks/registration.js index edc182a9d082a0..3dcd1f86b356c6 100644 --- a/blocks/registration.js +++ b/blocks/registration.js @@ -77,9 +77,10 @@ export function setUnknownTypeHandler( slug ) { } /** - * Retrieves slug of block handling unknown block types. + * Retrieves slug of block handling unknown block types, or undefined if no + * handler has been defined. * - * @return {string} Blog slug + * @return {?string} Blog slug */ export function getUnknownTypeHandler() { return unknownTypeHandler; From 15db036eb14f71665424c4d79e17b9c08f629123 Mon Sep 17 00:00:00 2001 From: Andrew Duthie Date: Fri, 31 Mar 2017 11:15:31 -0400 Subject: [PATCH 9/9] Test block type of parsed unknown blocks --- blocks/test/parser.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/blocks/test/parser.js b/blocks/test/parser.js index d22fc241ce7964..41dd68a5a45454 100644 --- a/blocks/test/parser.js +++ b/blocks/test/parser.js @@ -90,11 +90,13 @@ describe( 'block parser', () => { } } ); - const postContent = 'Ribs' + + const parsed = parse( + 'Ribs' + '

Broccoli

' + - 'Ribs'; + 'Ribs' + ); - expect( parse( postContent ) ).to.eql( [ { + expect( parsed ).to.eql( [ { blockType: 'core/test-block', attributes: { content: 'Ribs & Chicken' @@ -109,11 +111,18 @@ describe( 'block parser', () => { setUnknownTypeHandler( 'core/unknown-block' ); - const postContent = 'Ribs' + + const parsed = parse( + 'Ribs' + '

Broccoli

' + - 'Ribs'; - - expect( parse( postContent ) ).to.have.lengthOf( 3 ); + 'Ribs' + ); + + expect( parsed ).to.have.lengthOf( 3 ); + expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ + 'core/test-block', + 'core/unknown-block', + 'core/unknown-block' + ] ); } ); } ); } );