Skip to content

Commit

Permalink
Block API: Add support for valid block eligible deprecation
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Jun 29, 2018
1 parent 63db962 commit 0dda057
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 166 deletions.
126 changes: 68 additions & 58 deletions blocks/api/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import { parse as hpqParse } from 'hpq';
import { flow, castArray, mapValues, omit } from 'lodash';
import { flow, castArray, mapValues, omit, stubFalse } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -193,57 +193,80 @@ export function getBlockAttributes( blockType, innerHTML, attributes ) {
}

/**
* Attempt to parse the innerHTML using using a supplied `deprecated`
* definition.
* Given a block object, returns a new copy of the block with any applicable
* deprecated migrations applied, or the original block if it was both valid
* and no eligible migrations exist.
*
* @param {?Object} blockType Block type.
* @param {string} innerHTML Raw block content.
* @param {?Object} attributes Known block attributes (from delimiters).
* @param {?Array} innerBlocks Array of innerBlocks.
* @param {WPBlock} block Original block object.
*
* @return {Object} Block attributes.
* @return {WPBlock} Migrated block object.
*/
export function getAttributesAndInnerBlocksFromDeprecatedVersion( blockType, innerHTML, attributes, innerBlocks ) {
// Not all blocks need a deprecated definition so avoid unnecessary computational cycles
// as early as possible when `deprecated` property is not supplied.
if ( ! blockType.deprecated || ! blockType.deprecated.length ) {
return;
export function getMigratedBlock( block ) {
const blockType = getBlockType( block.name );

const { deprecated } = blockType;
if ( ! deprecated || ! deprecated.length ) {
return block;
}

// There is no notion of version numbers for blocks. Instead, deprecated versions
// are defined implicitly as successive array entries containing the relevant definitions
// for handling each block variation. In order to validate a provided source, it has
// to attempt to parse each array entry at a time.
for ( let i = 0; i < blockType.deprecated.length; i++ ) {
const deprecatedBlockType = {
...omit( blockType, [ 'attributes', 'save', 'supports' ] ), // Parsing/Serialization properties
...blockType.deprecated[ i ],
const { originalContent, attributes, innerBlocks } = block;

for ( let i = 0; i < deprecated.length; i++ ) {
// A block can opt into a migration even if the block is valid by
// defining isEligible on its deprecation. If the block is both valid
// and does not opt to migrate, skip.
const { isEligible = stubFalse } = deprecated[ i ];
if ( block.isValid && ! isEligible( attributes, innerBlocks ) ) {
continue;
}

// Block type properties which could impact either serialization or
// parsing are not considered in the deprecated block type by default,
// and must be explicitly provided.
const deprecatedBlockType = Object.assign(
omit( blockType, [ 'attributes', 'save', 'supports' ] ),
deprecated[ i ]
);

let migratedAttributes = getBlockAttributes(
deprecatedBlockType,
originalContent,
attributes
);

// Ignore the deprecation if it produces a block which is not valid.
const isValid = isValidBlock(
originalContent,
deprecatedBlockType,
migratedAttributes
);

if ( ! isValid ) {
continue;
}

block = {
...block,
isValid: true,
};

try {
// Handle migration of older attributes into current version if necessary.
const deprecatedBlockAttributes = getBlockAttributes( deprecatedBlockType, innerHTML, attributes );

// Attempt to validate the parsed block. Ignore if the the validation step fails.
const isValid = isValidBlock( innerHTML, deprecatedBlockType, deprecatedBlockAttributes );
if ( isValid ) {
const migratedBlockAttributesAndInnerBlocks = deprecatedBlockType.migrate &&
deprecatedBlockType.migrate( deprecatedBlockAttributes, innerBlocks );

if ( migratedBlockAttributesAndInnerBlocks ) {
const [
migratedAttributes,
migratedInnerBlocks = innerBlocks,
] = castArray( migratedBlockAttributesAndInnerBlocks );
return { attributes: migratedAttributes, innerBlocks: migratedInnerBlocks };
}

return { attributes: deprecatedBlockAttributes, innerBlocks };
}
} catch ( error ) {
// Ignore error, it means this deprecated version is invalid.
let migratedInnerBlocks = innerBlocks;

// A block may provide custom behavior to assign new attributes and/or
// inner blocks.
const { migrate } = deprecatedBlockType;
if ( migrate ) {
( [
migratedAttributes = attributes,
migratedInnerBlocks = innerBlocks,
] = castArray( migrate( migratedAttributes, innerBlocks ) ) );
}

block.attributes = migratedAttributes;
block.innerBlocks = migratedInnerBlocks;
}

return block;
}

/**
Expand Down Expand Up @@ -305,7 +328,7 @@ export function createBlockWithFallback( blockNode ) {
return;
}

const block = createBlock(
let block = createBlock(
name,
getBlockAttributes( blockType, innerHTML, attributes ),
innerBlocks
Expand All @@ -323,20 +346,7 @@ export function createBlockWithFallback( blockNode ) {
// invalid, or future serialization attempt results in an error.
block.originalContent = innerHTML;

// When the block is invalid, attempt to parse it using a deprecated definition.
// This enables blocks to modify its attributes and markup structure without
// invalidating content written in previous formats.
if ( ! block.isValid ) {
const attributesAndInnerBlocksParsedWithDeprecatedVersion = getAttributesAndInnerBlocksFromDeprecatedVersion(
blockType, innerHTML, attributes, block.innerBlocks
);

if ( attributesAndInnerBlocksParsedWithDeprecatedVersion ) {
block.isValid = true;
block.attributes = attributesAndInnerBlocksParsedWithDeprecatedVersion.attributes;
block.innerBlocks = attributesAndInnerBlocksParsedWithDeprecatedVersion.innerBlocks || [];
}
}
block = getMigratedBlock( block );

return block;
}
Expand Down
Loading

0 comments on commit 0dda057

Please sign in to comment.