diff --git a/packages/block-serialization-default-parser/CHANGELOG.md b/packages/block-serialization-default-parser/CHANGELOG.md index 21e1ef9aa757ec..8c95e7e2c3ac98 100644 --- a/packages/block-serialization-default-parser/CHANGELOG.md +++ b/packages/block-serialization-default-parser/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.0.1 (unreleased) +- Fix: Include freeform content preceding void blocks (#9984) + ## 1.0.0 - Initial release. diff --git a/packages/block-serialization-default-parser/parser.php b/packages/block-serialization-default-parser/parser.php index 37542aa56b0d5e..9ea5b6ab789190 100644 --- a/packages/block-serialization-default-parser/parser.php +++ b/packages/block-serialization-default-parser/parser.php @@ -197,9 +197,13 @@ function parse( $document ) { * @return bool */ function proceed() { - list( $token_type, $block_name, $attrs, $start_offset, $token_length ) = $this->next_token(); + $next_token = $this->next_token(); + list( $token_type, $block_name, $attrs, $start_offset, $token_length ) = $next_token; $stack_depth = count( $this->stack ); + // we may have some HTML soup before the next block + $leading_html_start = $start_offset > $this->offset ? $this->offset : null; + switch ( $token_type ) { case 'no-more-tokens': // if not in a block then flush output @@ -239,6 +243,17 @@ function proceed() { * in the top-level of the document */ if ( 0 === $stack_depth ) { + if ( isset( $leading_html_start ) ) { + $this->output[] = array( + 'attrs' => array(), + 'innerHTML' => substr( + $this->document, + $leading_html_start, + $start_offset - $leading_html_start + ), + ); + } + $this->output[] = new WP_Block_Parser_Block( $block_name, $attrs, array(), '' ); $this->offset = $start_offset + $token_length; return true; @@ -254,9 +269,6 @@ function proceed() { return true; case 'block-opener': - // we may have some HTML soup before the next block - $leading_html_start = $start_offset > $this->offset ? $this->offset : null; - // track all newly-opened blocks on the stack array_push( $this->stack, new WP_Block_Parser_Frame( new WP_Block_Parser_Block( $block_name, $attrs, array(), '' ), diff --git a/packages/block-serialization-default-parser/src/index.js b/packages/block-serialization-default-parser/src/index.js index 4f4fdb38cd19e1..a4cec7aecd8ef0 100644 --- a/packages/block-serialization-default-parser/src/index.js +++ b/packages/block-serialization-default-parser/src/index.js @@ -42,6 +42,9 @@ function proceed() { const [ tokenType, blockName, attrs, startOffset, tokenLength ] = next; const stackDepth = stack.length; + // we may have some HTML soup before the next block + const leadingHtmlStart = ( startOffset > offset ) ? offset : null; + switch ( tokenType ) { case 'no-more-tokens': // if not in a block then flush output @@ -74,6 +77,12 @@ function proceed() { // easy case is if we stumbled upon a void block // in the top-level of the document if ( 0 === stackDepth ) { + if ( null !== leadingHtmlStart ) { + output.push( { + attrs: {}, + innerHTML: document.substr( leadingHtmlStart, startOffset - leadingHtmlStart ), + } ); + } output.push( Block( blockName, attrs, [], '' ) ); offset = startOffset + tokenLength; return true; @@ -89,9 +98,6 @@ function proceed() { return true; case 'block-opener': - // we may have some HTML soup before the next block - const leadingHtmlStart = ( startOffset > offset ) ? offset : null; - // track all newly-opened blocks on the stack stack.push( Frame( diff --git a/packages/block-serialization-default-parser/test/index.js b/packages/block-serialization-default-parser/test/index.js index 68a46b39c97c45..116a35ce93487b 100644 --- a/packages/block-serialization-default-parser/test/index.js +++ b/packages/block-serialization-default-parser/test/index.js @@ -24,4 +24,50 @@ describe( 'block-serialization-spec-parser', () => { }, ] ); } ); + + test( 'treats void blocks and empty blocks identically', () => { + expect( parse( + '' + ) ).toEqual( parse( + '' + ) ); + + expect( parse( + '' + ) ).toEqual( parse( + '' + ) ); + } ); + + test( 'should grab HTML soup before block openers', () => { + [ + '

Break me

', + '

Break me

', + ].forEach( ( input ) => expect( parse( input ) ).toEqual( [ + expect.objectContaining( { innerHTML: '

Break me

' } ), + expect.objectContaining( { blockName: 'core/block', innerHTML: '' } ), + ] ) ); + } ); + + test( 'should grab HTML soup before inner block openers', () => { + [ + '

Break me

', + '

Break me

', + ].forEach( ( input ) => expect( parse( input ) ).toEqual( [ + expect.objectContaining( { + innerBlocks: [ expect.objectContaining( { blockName: 'core/block', innerHTML: '' } ) ], + innerHTML: '

Break me

', + } ), + ] ) ); + } ); + + test( 'should grab HTML soup after blocks', () => { + [ + '

Break me

', + '

Break me

', + ].forEach( ( input ) => expect( parse( input ) ).toEqual( [ + expect.objectContaining( { blockName: 'core/block', innerHTML: '' } ), + expect.objectContaining( { innerHTML: '

Break me

' } ), + ] ) ); + } ); } );