diff --git a/blocks/api/parser.js b/blocks/api/parser.js index 648f0bc363c67..fa1a1ad201e49 100644 --- a/blocks/api/parser.js +++ b/blocks/api/parser.js @@ -99,7 +99,7 @@ export function parseWithTinyMCE( content ) { // In : // Out : content = content.replace( - //g, + //g, function( match, closingSlash, slug, attributes ) { if ( closingSlash ) { return ''; @@ -166,15 +166,15 @@ export function parseWithTinyMCE( content ) { }, {} ); // Retrieve the block attributes from the original delimiters - const blockAttributes = unescape( nodeAttributes.attributes || '' ) - .split( /\s+/ ) - .reduce( ( memo, attrString ) => { - const pieces = attrString.match( /^([a-z0-9_-]+):(.*)$/ ); - if ( pieces ) { - memo[ pieces[ 1 ] ] = pieces[ 2 ]; - } - return memo; - }, {} ); + const attributesMatcher = /([a-z0-9_-]+)(="([^"]*)")?/g; + const blockAttributes = {}; + let match; + do { + match = attributesMatcher.exec( unescape( nodeAttributes.attributes || '' ) ); + if ( match ) { + blockAttributes[ match[ 1 ] ] = !! match[ 2 ] ? match[ 3 ] : true; + } + } while ( !! match ); // Try to create the block const block = createBlockWithFallback( diff --git a/blocks/api/post.pegjs b/blocks/api/post.pegjs index 886137c830cf8..1c7f9b56d6a6c 100644 --- a/blocks/api/post.pegjs +++ b/blocks/api/post.pegjs @@ -26,7 +26,7 @@ WP_Block_Html } WP_Block_Start - = "" + = "" { return { blockType: blockType, attrs: attrs @@ -41,22 +41,38 @@ WP_Block_End WP_Block_Type = $(ASCII_Letter WP_Block_Type_Char*) -WP_Block_Attribute_List - = as:(_+ attr:WP_Block_Attribute { return attr })* - { return as.reduce( function( attrs, pair ) { - attrs[ pair.name ] = pair.value; - return attrs; - }, {} ) } - -WP_Block_Attribute - = name:WP_Block_Attribute_Name ":" value:WP_Block_Attribute_Value - { return { name: name, value: value }; } - -WP_Block_Attribute_Name - = $(ASCII_Letter ASCII_AlphaNumeric*) - -WP_Block_Attribute_Value - = $(ASCII_Letter WP_Block_Attribute_Value_Char*) +HTML_Attribute_List + = as:(_+ a:HTML_Attribute_Item { return a })* + { return as.reduce( function( attrs, currentAttribute ) { + var currentAttrs = {}; + currentAttrs[ currentAttribute[ 0 ] ] = currentAttribute[ 1 ]; + return Object.assign( + attrs, + currentAttrs + ); + }, {} ) } + +HTML_Attribute_Item + = HTML_Attribute_Quoted + / HTML_Attribute_Unquoted + / HTML_Attribute_Empty + +HTML_Attribute_Empty + = name:HTML_Attribute_Name + { return [ name, true ] } + +HTML_Attribute_Unquoted + = name:HTML_Attribute_Name _* "=" _* value:$([a-zA-Z0-9]+) + { return [ name, value ] } + +HTML_Attribute_Quoted + = name:HTML_Attribute_Name _* "=" _* '"' value:$((!'"' .)*) '"' + { return [ name, value ] } + / name:HTML_Attribute_Name _* "=" _* "'" value:$((!"'" .)*) "'" + { return [ name, value ] } + +HTML_Attribute_Name + = $([a-zA-Z0-9:.]+) WP_Block_Type_Char = ASCII_AlphaNumeric @@ -67,9 +83,6 @@ ASCII_AlphaNumeric / ASCII_Digit / Special_Chars -WP_Block_Attribute_Value_Char - = [^ \t\r\n] - ASCII_Letter = [a-zA-Z] diff --git a/blocks/api/test/parser.js b/blocks/api/test/parser.js index f752390139fb1..99671d43cbf15 100644 --- a/blocks/api/test/parser.js +++ b/blocks/api/test/parser.js @@ -11,7 +11,8 @@ import { getBlockAttributes, parseBlockAttributes, createBlockWithFallback, - default as parse, + parseWithGrammar, + parseWithTinyMCE } from '../parser'; import { registerBlock, @@ -139,106 +140,115 @@ describe( 'block parser', () => { } ); describe( 'parse()', () => { - it( 'should parse the post content, including block attributes', () => { - registerBlock( 'core/test-block', { - // Currently this is the only way to test block content parsing? - attributes: function( rawContent ) { - return { - content: rawContent, - }; - } - } ); - - const parsed = parse( - '' + - 'Brisket' + - '' - ); - - expect( parsed ).to.have.lengthOf( 1 ); - expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); - expect( parsed[ 0 ].attributes ).to.eql( { - content: 'Brisket', - smoked: 'yes', - } ); - expect( parsed[ 0 ].uid ).to.be.a( 'string' ); - } ); - - it( 'should parse the post content, ignoring unknown blocks', () => { - registerBlock( 'core/test-block', { - attributes: function( rawContent ) { - return { - content: rawContent + ' & Chicken' - }; - } - } ); - - const parsed = parse( - 'Ribs' + - '

Broccoli

' + - 'Ribs' - ); - - expect( parsed ).to.have.lengthOf( 1 ); - expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); - expect( parsed[ 0 ].attributes ).to.eql( { - content: 'Ribs & Chicken' - } ); - expect( parsed[ 0 ].uid ).to.be.a( 'string' ); - } ); - - 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', - ] ); - } ); - - it( 'should parse the post content, including raw HTML at each end', () => { - registerBlock( 'core/test-block', {} ); - registerBlock( 'core/unknown-block', { - // Currently this is the only way to test block content parsing? - attributes: function( rawContent ) { - return { - content: rawContent, - }; - } + const parsers = { parseWithTinyMCE, parseWithGrammar }; + Object.keys( parsers ).forEach( ( parser ) => { + const parse = parsers[ parser ]; + describe( parser, () => { + it( 'should parse the post content, including block attributes', () => { + registerBlock( 'core/test-block', { + // Currently this is the only way to test block content parsing? + attributes: function( rawContent ) { + return { + content: rawContent, + }; + } + } ); + + const parsed = parse( + '' + + 'Brisket' + + '' + ); + + expect( parsed ).to.have.lengthOf( 1 ); + expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); + expect( parsed[ 0 ].attributes ).to.eql( { + content: 'Brisket', + smoked: 'yes', + url: 'http://google.com', + chicken: 'ribs & \'wings\'', + checked: true + } ); + expect( parsed[ 0 ].uid ).to.be.a( 'string' ); + } ); + + it( 'should parse the post content, ignoring unknown blocks', () => { + registerBlock( 'core/test-block', { + attributes: function( rawContent ) { + return { + content: rawContent + ' & Chicken' + }; + } + } ); + + const parsed = parse( + 'Ribs' + + '

Broccoli

' + + 'Ribs' + ); + + expect( parsed ).to.have.lengthOf( 1 ); + expect( parsed[ 0 ].blockType ).to.equal( 'core/test-block' ); + expect( parsed[ 0 ].attributes ).to.eql( { + content: 'Ribs & Chicken' + } ); + expect( parsed[ 0 ].uid ).to.be.a( 'string' ); + } ); + + 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', + ] ); + } ); + + it( 'should parse the post content, including raw HTML at each end', () => { + registerBlock( 'core/test-block', {} ); + registerBlock( 'core/unknown-block', { + // Currently this is the only way to test block content parsing? + attributes: function( rawContent ) { + return { + content: rawContent, + }; + } + } ); + + setUnknownTypeHandler( 'core/unknown-block' ); + + const parsed = parse( + '

Cauliflower

' + + 'Ribs' + + '

Broccoli

' + + 'Ribs' + + '

Romanesco

' + ); + + expect( parsed ).to.have.lengthOf( 5 ); + expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ + 'core/unknown-block', + 'core/test-block', + 'core/unknown-block', + 'core/test-block', + 'core/unknown-block', + ] ); + expect( parsed[ 0 ].attributes.content ).to.eql( '

Cauliflower

' ); + expect( parsed[ 2 ].attributes.content ).to.eql( '

Broccoli

' ); + expect( parsed[ 4 ].attributes.content ).to.eql( '

Romanesco

' ); + } ); } ); - - setUnknownTypeHandler( 'core/unknown-block' ); - - const parsed = parse( - '

Cauliflower

' + - 'Ribs' + - '

Broccoli

' + - 'Ribs' + - '

Romanesco

' - ); - - expect( parsed ).to.have.lengthOf( 5 ); - expect( parsed.map( ( { blockType } ) => blockType ) ).to.eql( [ - 'core/unknown-block', - 'core/test-block', - 'core/unknown-block', - 'core/test-block', - 'core/unknown-block', - ] ); - expect( parsed[ 0 ].attributes.content ).to.eql( '

Cauliflower

' ); - expect( parsed[ 2 ].attributes.content ).to.eql( '

Broccoli

' ); - expect( parsed[ 4 ].attributes.content ).to.eql( '

Romanesco

' ); } ); } ); } ); diff --git a/post-content.js b/post-content.js index bbadbcddb7b80..dc4371e22288b 100644 --- a/post-content.js +++ b/post-content.js @@ -41,7 +41,7 @@ window._wpGutenbergPost = { '', '', - '', + '', '', '', ].join( '' ),