Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block API: Support nesting (InnerBlocks) in unknown block types #14443

Merged
merged 4 commits into from
Jul 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 68 additions & 7 deletions packages/blocks/src/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ export function createBlockWithFallback( blockNode ) {
innerBlocks = [],
innerHTML,
} = blockNode;
const { innerContent } = blockNode;
const freeformContentFallbackBlock = getFreeformContentHandlerName();
const unregisteredFallbackBlock = getUnregisteredTypeHandlerName() || freeformContentFallbackBlock;

Expand Down Expand Up @@ -416,17 +417,39 @@ export function createBlockWithFallback( blockNode ) {
let blockType = getBlockType( name );

if ( ! blockType ) {
// Preserve undelimited content for use by the unregistered type handler.
const originalUndelimitedContent = innerHTML;
// Since the constituents of the block node are extracted at the start
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the comments.

// of the present function, construct a new object rather than reuse
// `blockNode`.
const reconstitutedBlockNode = {
attrs: attributes,
blockName: originalName,
innerBlocks,
innerContent,
};

// Preserve undelimited content for use by the unregistered type
// handler. A block node's `innerHTML` isn't enough, as that field only
// carries the block's own HTML and not its nested blocks'.
const originalUndelimitedContent = serializeBlockNode(
reconstitutedBlockNode,
{ isCommentDelimited: false }
);

// Preserve full block content for use by the unregistered type
// handler, block boundaries included.
const originalContent = serializeBlockNode(
reconstitutedBlockNode,
{ isCommentDelimited: true }
);

// If detected as a block which is not registered, preserve comment
// delimiters in content of unregistered type handler.
if ( name ) {
innerHTML = getCommentDelimitedContent( name, attributes, innerHTML );
innerHTML = originalContent;
}

name = unregisteredFallbackBlock;
attributes = { originalName, originalUndelimitedContent };
attributes = { originalName, originalContent, originalUndelimitedContent };
blockType = getBlockType( name );
}

Expand Down Expand Up @@ -457,15 +480,53 @@ export function createBlockWithFallback( blockNode ) {
block.isValid = isValidBlockContent( blockType, block.attributes, innerHTML );
}

// Preserve original content for future use in case the block is parsed as
// invalid, or future serialization attempt results in an error.
block.originalContent = innerHTML;
// Preserve original content for future use in case the block is parsed
// as invalid, or future serialization attempt results in an error.
block.originalContent = block.originalContent || innerHTML;

block = getMigratedBlock( block, attributes );

return block;
}

/**
* Serializes a block node into the native HTML-comment-powered block format.
* CAVEAT: This function is intended for reserializing blocks as parsed by
* valid parsers and skips any validation steps. This is NOT a generic
* serialization function for in-memory blocks. For most purposes, see the
* following functions available in the `@wordpress/blocks` package:
*
* @see serializeBlock
* @see serialize
*
* For more on the format of block nodes as returned by valid parsers:
*
* @see `@wordpress/block-serialization-default-parser` package
* @see `@wordpress/block-serialization-spec-parser` package
*
* @param {Object} blockNode A block node as returned by a valid parser.
* @param {?Object} options Serialization options.
* @param {?boolean} options.isCommentDelimited Whether to output HTML comments around blocks.
*
* @return {string} An HTML string representing a block.
*/
export function serializeBlockNode( blockNode, options = {} ) {
const { isCommentDelimited = true } = options;
const { blockName, attrs = {}, innerBlocks = [], innerContent = [] } = blockNode;

let childIndex = 0;
const content = innerContent.map( ( item ) =>
// `null` denotes a nested block, otherwise we have an HTML fragment
item !== null ?
item :
serializeBlockNode( innerBlocks[ childIndex++ ], options )
).join( '\n' ).replace( /\n+/g, '\n' ).trim();

return isCommentDelimited ?
getCommentDelimitedContent( blockName, attrs, content ) :
content;
}

/**
* Creates a parse implementation for the post content which returns a list of blocks.
*
Expand Down
106 changes: 106 additions & 0 deletions packages/blocks/src/api/test/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
isOfTypes,
isValidByType,
isValidByEnum,
serializeBlockNode,
} from '../parser';
import {
registerBlockType,
Expand Down Expand Up @@ -757,6 +758,111 @@ describe( 'block parser', () => {
} );
} );

describe( 'serializeBlockNode', () => {
it( 'reserializes block nodes', () => {
const expected = `<!-- wp:columns -->
<div class="wp-block-columns has-2-columns">
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:paragraph -->
<p>A</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:column -->
<!-- wp:column -->
<div class="wp-block-column">
<!-- wp:group -->
<div class="wp-block-group"><div class="wp-block-group__inner-container">
<!-- wp:list -->
<ul><li>B</li><li>C</li></ul>
<!-- /wp:list -->
<!-- wp:paragraph -->
<p>D</p>
<!-- /wp:paragraph -->
</div></div>
<!-- /wp:group -->
</div>
<!-- /wp:column -->
</div>
<!-- /wp:columns -->`.replace( /\t/g, '' );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3 thanks for cleaning up.

const input = {
blockName: 'core/columns',
attrs: {},
innerBlocks: [
{
blockName: 'core/column',
attrs: {},
innerBlocks: [
{
blockName: 'core/paragraph',
attrs: {},
innerBlocks: [],
innerHTML: '<p>A</p>',
innerContent: [ '<p>A</p>' ],
},
],
innerHTML: '<div class="wp-block-column"></div>',
innerContent: [
'<div class="wp-block-column">',
null,
'</div>',
],
},
{
blockName: 'core/column',
attrs: {},
innerBlocks: [
{
blockName: 'core/group',
attrs: {},
innerBlocks: [
{
blockName: 'core/list',
attrs: {},
innerBlocks: [],
innerHTML: '<ul><li>B</li><li>C</li></ul>',
innerContent: [ '<ul><li>B</li><li>C</li></ul>' ],
},
{
blockName: 'core/paragraph',
attrs: {},
innerBlocks: [],
innerHTML: '<p>D</p>',
innerContent: [ '<p>D</p>' ],
},
],
innerHTML: '<div class="wp-block-group"><div class="wp-block-group__inner-container"></div></div>',
innerContent: [
'<div class="wp-block-group"><div class="wp-block-group__inner-container">',
null,
'',
null,
'</div></div>' ],
},
],
innerHTML: '<div class="wp-block-column"></div>',
innerContent: [
'<div class="wp-block-column">',
null,
'</div>',
],
},
],
innerHTML: '<div class="wp-block-columns has-2-columns"></div>',
innerContent: [
'<div class="wp-block-columns has-2-columns">',
null,
'',
null,
'</div>',
],
};
const actual = serializeBlockNode( input );

expect( actual ).toEqual( expected );
} );
} );

describe( 'parse() of @wordpress/block-serialization-spec-parser', () => {
// run the test cases using the PegJS defined parser
testCases( parsePegjs );
Expand Down