Skip to content

Commit

Permalink
Blocks: Adding versioning and migrations to the Block API
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Nov 22, 2017
1 parent e56a73f commit ed66989
Show file tree
Hide file tree
Showing 85 changed files with 409 additions and 149 deletions.
120 changes: 77 additions & 43 deletions blocks/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
* External dependencies
*/
import { parse as hpqParse } from 'hpq';
import { mapValues } from 'lodash';
import { mapValues, find } from 'lodash';

/**
* Internal dependencies
*/
import { parse as grammarParse } from './post.pegjs';
import { getBlockType, getUnknownTypeHandlerName } from './registration';
import { createBlock } from './factory';
import { isValidBlock } from './validation';
import { getCommentDelimitedContent } from './serializer';
import { isEquivalentHTML } from './validation';
import { getCommentDelimitedContent, getSaveContent } from './serializer';
import { attr, prop, html, text, query, node, children } from './matchers';

/**
Expand Down Expand Up @@ -114,15 +114,15 @@ export function getBlockAttribute( attributeKey, attributeSchema, innerHTML, com
}

/**
* Returns the block attributes of a registered block node given its type.
* Returns the block attributes given its attributes schema
*
* @param {?Object} blockType Block type
* @param {?Object} schema Block attributes schema
* @param {string} innerHTML Raw block content
* @param {?Object} attributes Known block attributes (from delimiters)
* @return {Object} All block attributes
*/
export function getBlockAttributes( blockType, innerHTML, attributes ) {
const blockAttributes = mapValues( blockType.attributes, ( attributeSchema, attributeKey ) => {
export function getBlockAttributes( schema, innerHTML, attributes ) {
const blockAttributes = mapValues( schema, ( attributeSchema, attributeKey ) => {
return getBlockAttribute( attributeKey, attributeSchema, innerHTML, attributes );
} );

Expand All @@ -135,54 +135,87 @@ export function getBlockAttributes( blockType, innerHTML, attributes ) {
* @param {?String} name Block type name
* @param {String} innerHTML Raw block content
* @param {?Object} attributes Attributes obtained from block delimiters
* @param {?Number} version The block version
* @return {?Object} An initialized block object (if possible)
*/
export function createBlockWithFallback( name, innerHTML, attributes ) {
// Use type from block content, otherwise find unknown handler.
name = name || getUnknownTypeHandlerName();
export function createBlockWithFallback( name, innerHTML, attributes, version ) {
let originalContent = innerHTML; // originalContent before parsing
let contentToValidate; // Content serialized after parsing or after migration from old block
let parsedAttributes; // Parsed block attributes or migrated to

// Convert 'core/text' blocks in existing content to the new
// 'core/paragraph'.
if ( 'core/text' === name || 'core/cover-text' === name ) {
name = 'core/paragraph';
}
let shouldFallback = false;

// Checking The BlockType
const blockType = getBlockType( name );
const fallbackBlockName = getUnknownTypeHandlerName();
if ( blockType ) {
const blockTypeVersion = blockType.version || 1;
if ( blockTypeVersion !== version ) {
const migration = find( blockType.migrations, ( mig ) => mig.version === version );
if ( ! migration ) {
shouldFallback = true;
} else {
// Needs to pass the migration.attributes instead to do the parsing
const oldAttributes = getBlockAttributes( migration.attributes, innerHTML, attributes );

// Serialize using the old save
contentToValidate = getSaveContent( {
...blockType,
attributes: migration.attributes,
save: migration.save,
}, oldAttributes );

// Migrate the old attributes
parsedAttributes = migration.migrate( oldAttributes );
}
} else {
parsedAttributes = getBlockAttributes( blockType.attributes, innerHTML, attributes );
contentToValidate = getSaveContent( blockType, parsedAttributes );
}
} else {
shouldFallback = true;
}

// Fallback to the fallback block type
if ( shouldFallback ) {
// Explicit empty fallback blocks are ignored
if ( ! name && ! innerHTML ) {
return;
}

const fallbackBlockType = getBlockType( fallbackBlockName );
if ( ! fallbackBlockType ) {
// eslint-disable-next-line no-console
console.warn( `Block ${ name } ignored, no fallback block` );
return;
}

// Try finding type for known block name, else fall back again.
let blockType = getBlockType( name );
const fallbackBlock = getUnknownTypeHandlerName();
if ( ! blockType ) {
// If detected as a block which is not registered, preserve comment
// delimiters in content of unknown type handler.
if ( name ) {
innerHTML = getCommentDelimitedContent( name, attributes, innerHTML );
originalContent = getCommentDelimitedContent( name, attributes, innerHTML, version );
}

name = fallbackBlock;
blockType = getBlockType( name );
name = fallbackBlockName;
parsedAttributes = getBlockAttributes( fallbackBlockType.attributes, originalContent, attributes );
contentToValidate = getSaveContent( fallbackBlockType, parsedAttributes );
}

// Include in set only if type were determined.
// TODO do we ever expect there to not be an unknown type handler?
if ( blockType && ( innerHTML || name !== fallbackBlock ) ) {
// TODO allow blocks to opt-in to receiving a tree instead of a string.
// Gradually convert all blocks to this new format, then remove the
// string serialization.
const block = createBlock(
name,
getBlockAttributes( blockType, innerHTML, attributes )
);

// Validate that the parsed block is valid, meaning that if we were to
// reserialize it given the assumed attributes, the markup matches the
// original value.
block.isValid = isValidBlock( innerHTML, blockType, block.attributes );

// 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;

return block;
}
const block = createBlock(
name,
parsedAttributes
);

block.isValid = isEquivalentHTML( originalContent, contentToValidate );

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

return block;
}

/**
Expand All @@ -193,8 +226,9 @@ export function createBlockWithFallback( name, innerHTML, attributes ) {
*/
export function parseWithGrammar( content ) {
return grammarParse( content ).reduce( ( memo, blockNode ) => {
const { blockName, innerHTML, attrs } = blockNode;
const block = createBlockWithFallback( blockName, innerHTML.trim(), attrs );
const { blockName, innerHTML, attrs, version } = blockNode;
const block = createBlockWithFallback( blockName, innerHTML.trim(), attrs, version );

if ( block ) {
memo.push( block );
}
Expand Down
93 changes: 78 additions & 15 deletions blocks/api/post.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,14 @@ Tag_More
$attrs['customText'] = $customText;
}
return array(
'version' => 1,
'blockName' => 'core/more',
'attrs' => $attrs,
'innerHTML' => ''
);
?> **/
return {
version: 1,
blockName: 'core/more',
attrs: {
customText: customText || undefined,
Expand All @@ -197,23 +199,22 @@ Tag_More
}

Block_Void
= "<!--" WS+ "wp:" blockName:Block_Name WS+ attrs:(a:Block_Attributes WS+ {
/** <?php return $a; ?> **/
return a;
})? "/-->"
= "<!--" WS+ header:Block_Header WS+ "/-->"
{
/** <?php
return array(
'blockName' => $blockName,
'attrs' => $attrs,
'blockName' => $header['blockName'],
'attrs' => $header['attrs'],
'version' => $header['version'],
'innerBlocks' => array(),
'innerHTML' => '',
);
?> **/

return {
blockName: blockName,
attrs: attrs,
blockName: header.blockName,
attrs: header.attrs,
version: header.version,
innerBlocks: [],
innerHTML: ''
};
Expand All @@ -228,6 +229,7 @@ Block_Balanced
return array(
'blockName' => $s['blockName'],
'attrs' => $s['attrs'],
'version' => $s['version'],
'innerBlocks' => $innerBlocks,
'innerHTML' => implode( '', $innerHTML ),
);
Expand All @@ -240,27 +242,81 @@ Block_Balanced
return {
blockName: s.blockName,
attrs: s.attrs,
version: s.version,
innerBlocks: innerBlocks,
innerHTML: innerHTML.join( '' )
};
}

Block_Start
= "<!--" WS+ "wp:" blockName:Block_Name WS+ attrs:(a:Block_Attributes WS+ {
/** <?php return $a; ?> **/
return a;
})? "-->"
= "<!--" WS+ header:Block_Header WS+ "-->"
{
/** <?php
return $header;
?> **/

return header;
}

Block_Header
= "wp:" blockName:Block_Name WS+ attrs:Block_Attributes WS+ version:Block_Version
{
/** <?php
return array(
'blockName' => $blockName,
'attrs' => $attrs ? $attrs : {},
'version' => $version ? $version : 1,
);
?> **/
return {
blockName: blockName,
attrs: attrs,
version: version
};
}
/ "wp:" blockName:Block_Name WS+ attrs:Block_Attributes
{
/** <?php
return array(
'blockName' => $blockName,
'attrs' => $attrs,
'version' => 1,
);
?> **/

return {
blockName: blockName,
attrs: attrs
attrs: attrs,
version: 1
};
}
/ "wp:" blockName:Block_Name WS+ version:Block_Version
{
/** <?php
return array(
'blockName' => $blockName,
'attrs' => null,
'version' => $version,
);
?> **/
return {
blockName: blockName,
attrs: null,
version: version
};
}
/ "wp:" blockName:Block_Name
{
/** <?php
return array(
'blockName' => $blockName,
'attrs' => null,
'version' => 1,
);
?> **/
return {
blockName: blockName,
attrs: null,
version: 1
};
}

Expand Down Expand Up @@ -296,12 +352,19 @@ Block_Name_Part
= $( [a-z][a-z0-9_-]* )

Block_Attributes
= attrs:$("{" (!("}" WS+ """/"? "-->") .)* "}")
= attrs:$("{" (!("}") .)* "}")
{
/** <?php return json_decode( $attrs, true ); ?> **/
return maybeJSON( attrs );
}

Block_Version
= "v" v:$( [0-9]* )
{
/** <?php return (int) $v; ?> **/
return parseInt( v, 10 );
}

WS
= [ \t\r\n]

Expand Down
2 changes: 1 addition & 1 deletion blocks/api/raw-handling/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default function rawHandler( { HTML, plainText = '', mode = 'AUTO' } ) {
return createBlock(
blockType.name,
getBlockAttributes(
blockType,
blockType.attributes,
node.outerHTML
)
);
Expand Down
5 changes: 1 addition & 4 deletions blocks/api/raw-handling/shortcode-converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ export default function( HTML ) {
const block = createBlock(
blockType.name,
getBlockAttributes(
{
...blockType,
attributes: transform.attributes,
},
transform.attributes,
match.shortcode.content,
attributes,
)
Expand Down
Loading

0 comments on commit ed66989

Please sign in to comment.