Skip to content

Commit

Permalink
Add possibility to manage innerBlocks while migrating deprecated bloc…
Browse files Browse the repository at this point in the history
…ks. (#5932)

Before the migrate function received the existing attributes and returned the new attributes now. Now migrate function can also receive the innerBlocks and add or remove them. This allows, for example, to migrate existing cover images to the nested version.
  • Loading branch information
jorgefilipecosta authored Apr 10, 2018
1 parent cddd627 commit ab7a429
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 19 deletions.
34 changes: 23 additions & 11 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 { mapValues, omit } from 'lodash';
import { castArray, mapValues, omit } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -155,13 +155,14 @@ export function getBlockAttributes( blockType, innerHTML, attributes ) {
* Attempt to parse the innerHTML using using a supplied `deprecated`
* definition.
*
* @param {?Object} blockType Block type.
* @param {string} innerHTML Raw block content.
* @param {?Object} attributes Known block attributes (from delimiters).
* @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.
*
* @return {Object} Block attributes.
*/
export function getAttributesFromDeprecatedVersion( blockType, innerHTML, attributes ) {
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 ) {
Expand All @@ -181,12 +182,22 @@ export function getAttributesFromDeprecatedVersion( blockType, innerHTML, attrib
try {
// Handle migration of older attributes into current version if necessary.
const deprecatedBlockAttributes = getBlockAttributes( deprecatedBlockType, innerHTML, attributes );
const migratedBlockAttributes = deprecatedBlockType.migrate ? deprecatedBlockType.migrate( deprecatedBlockAttributes ) : deprecatedBlockAttributes;

// Attempt to validate the parsed block. Ignore if the the validation step fails.
const isValid = isValidBlock( innerHTML, deprecatedBlockType, deprecatedBlockAttributes );
if ( isValid ) {
return migratedBlockAttributes;
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.
Expand Down Expand Up @@ -275,13 +286,14 @@ export function createBlockWithFallback( blockNode ) {
// This enables blocks to modify its attributes and markup structure without
// invalidating content written in previous formats.
if ( ! block.isValid ) {
const attributesParsedWithDeprecatedVersion = getAttributesFromDeprecatedVersion(
blockType, innerHTML, attributes
const attributesAndInnerBlocksParsedWithDeprecatedVersion = getAttributesAndInnerBlocksFromDeprecatedVersion(
blockType, innerHTML, attributes, block.innerBlocks
);

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

Expand Down
86 changes: 78 additions & 8 deletions blocks/api/test/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
getBlockAttributes,
asType,
createBlockWithFallback,
getAttributesFromDeprecatedVersion,
getAttributesAndInnerBlocksFromDeprecatedVersion,
default as parse,
parseWithAttributeSchema,
} from '../parser';
Expand Down Expand Up @@ -185,18 +185,18 @@ describe( 'block parser', () => {
} );
} );

describe( 'getAttributesFromDeprecatedVersion', () => {
describe( 'getAttributesAndInnerBlocksFromDeprecatedVersion', () => {
it( 'should return undefined if the block has no deprecated versions', () => {
const attributes = getAttributesFromDeprecatedVersion(
const attributesAndInnerBlocks = getAttributesAndInnerBlocksFromDeprecatedVersion(
defaultBlockSettings,
'<span class="wp-block-test-block">Bananas</span>',
{},
);
expect( attributes ).toBeUndefined();
expect( attributesAndInnerBlocks ).toBeUndefined();
} );

it( 'should return undefined if no valid deprecated version found', () => {
const attributes = getAttributesFromDeprecatedVersion(
const attributesAndInnerBlocks = getAttributesAndInnerBlocksFromDeprecatedVersion(
{
name: 'core/test-block',
...defaultBlockSettings,
Expand All @@ -211,13 +211,13 @@ describe( 'block parser', () => {
'<span class="wp-block-test-block">Bananas</span>',
{},
);
expect( attributes ).toBeUndefined();
expect( attributesAndInnerBlocks ).toBeUndefined();
expect( console ).toHaveErrored();
expect( console ).toHaveWarned();
} );

it( 'should return the attributes parsed by the deprecated version', () => {
const attributes = getAttributesFromDeprecatedVersion(
const attributesAndInnerBlocks = getAttributesAndInnerBlocksFromDeprecatedVersion(
{
name: 'core/test-block',
...defaultBlockSettings,
Expand All @@ -238,7 +238,77 @@ describe( 'block parser', () => {
'<span class="wp-block-test-block">Bananas</span>',
{},
);
expect( attributes ).toEqual( { fruit: 'Bananas' } );
expect( attributesAndInnerBlocks.attributes ).toEqual( { fruit: 'Bananas' } );
} );

it( 'should return the innerBlocks if they are passed', () => {
const attributesAndInnerBlocks = getAttributesAndInnerBlocksFromDeprecatedVersion(
{
name: 'core/test-block',
...defaultBlockSettings,
save: ( props ) => <div>{ props.attributes.fruit }</div>,
deprecated: [
{
attributes: {
fruit: {
type: 'string',
source: 'text',
selector: 'span',
},
},
save: ( props ) => <span>{ props.attributes.fruit }</span>,
},
],
},
'<span class="wp-block-test-block">Bananas</span>',
{},
[ {
name: 'core/test-block',
attributes: { aaa: 'bbb' },
} ]
);

expect( attributesAndInnerBlocks.innerBlocks ).toHaveLength( 1 );
expect( attributesAndInnerBlocks.innerBlocks[ 0 ].name ).toEqual( 'core/test-block' );
expect( attributesAndInnerBlocks.innerBlocks[ 0 ].attributes ).toEqual( { aaa: 'bbb' } );
} );

it( 'should be able to migrate attributes and innerBlocks', () => {
const attributesAndInnerBlocks = getAttributesAndInnerBlocksFromDeprecatedVersion(
{
name: 'core/test-block',
...defaultBlockSettings,
save: ( props ) => <div>{ props.attributes.fruit }</div>,
deprecated: [
{
attributes: {
fruit: {
type: 'string',
source: 'text',
selector: 'span',
},
},
save: ( props ) => <span>{ props.attributes.fruit }</span>,
migrate: ( attributes ) => {
return [
{ newFruit: attributes.fruit },
[ {
name: 'core/test-block',
attributes: { aaa: 'bbb' },
} ],
];
},
},
],
},
'<span class="wp-block-test-block">Bananas</span>',
{},
);

expect( attributesAndInnerBlocks.attributes ).toEqual( { newFruit: 'Bananas' } );
expect( attributesAndInnerBlocks.innerBlocks ).toHaveLength( 1 );
expect( attributesAndInnerBlocks.innerBlocks[ 0 ].name ).toEqual( 'core/test-block' );
expect( attributesAndInnerBlocks.innerBlocks[ 0 ].attributes ).toEqual( { aaa: 'bbb' } );
} );
} );

Expand Down
93 changes: 93 additions & 0 deletions docs/deprecated-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,96 @@ registerBlockType( 'gutenberg/block-with-deprecated-version', {
{% end %}

In the example above we updated the markup of the block to use a `div` instead of `p` and rename the `text` attribute to `content`.


## Changing the innerBlocks

Situations may exist where when migrating the block we may need to add or remove innerBlocks.
E.g: a block wants to migrate a title attribute to a paragraph innerBlock.

### Example:

{% codetabs %}
{% ES5 %}
```js
var el = wp.element.createElement,
registerBlockType = wp.blocks.registerBlockType;

registerBlockType( 'gutenberg/block-with-deprecated-version', {

// ... block properties go here

deprecated: [
{
attributes: {
title: {
type: 'array',
source: 'children',
selector: 'p',
},
},

migrate: function( attributes, innerBlocks ) {
return [
omit( attributes, 'title' ),
[
createBlock( 'core/paragraph', {
content: attributes.title,
fontSize: 'large',
} ),
].concat( innerBlocks ),
];
},

save: function( props ) {
return el( 'p', {}, props.attributes.title );
},
}
]
} );
```
{% ESNext %}
```js
const { registerBlockType } = wp.blocks;

registerBlockType( 'gutenberg/block-with-deprecated-version', {

// ... block properties go here

save( props ) {
return <p>{ props.attributes.title }</div>;
},

deprecated: [
{
attributes: {
title: {
type: 'array',
source: 'children',
selector: 'p',
},
},

migrate( attributes, innerBlocks ) {
return [
omit( attributes, 'title' ),
[
createBlock( 'core/paragraph', {
content: attributes.title,
fontSize: 'large',
} ),
...innerBlocks,
],
];
},

save( props ) {
return <p>{ props.attributes.title }</div>;
},
}
]
} );
```
{% end %}

In the example above we updated the block to use an inner paragraph block with a title instead of a title attribute.

0 comments on commit ab7a429

Please sign in to comment.