Skip to content

Latest commit

 

History

History
434 lines (382 loc) · 16.6 KB

block-transforms.md

File metadata and controls

434 lines (382 loc) · 16.6 KB

Block Transforms

Block Transforms is the API that allows a block to be transformed from and to other blocks, as well as from other entities. Existing entities that work with this API include shortcodes, files, regular expressions, and raw DOM nodes.

Transform direction: to and from

A block declares which transformations it supports via the optional transforms key of the block configuration, whose subkeys to and from hold an array of available transforms for every direction. Example:

export const settings = {
    title: 'My Block Title',
    description: 'My block description',
    /* ... */
    transforms: {
        from: [ /* supported from transforms */ ],
        to: [ /* supported to transforms */ ],
    }
}

Transformations Types

This section goes through the existing types of transformations blocks support:

  • block
  • enter
  • files
  • prefix
  • raw
  • shortcode

Block

This type of transformations support both from and to directions, allowing blocks to be converted into a different one. It has a corresponding UI control within the block toolbar.

A transformation of type block is an object that takes the following parameters:

  • type (string): the value block.
  • blocks (array): a list of known block types. It also accepts the wildcard value ("*"), meaning that the transform is available to all block types (eg: all blocks can transform into core/group).
  • transform (function): a callback that receives the attributes and inner blocks of the block being processed. It should return a block object or an array of block objects.
  • isMatch (function, optional): a callback that receives the block attributes and should return a boolean. Returning false from this function will prevent the transform from being available and displayed as an option to the user.
  • isMultiblock (boolean, optional): whether the transformation can be applied when multiple blocks are selected. If true, the transform function's first parameter will be an array containing each selected block's attributes, and the second an array of each selected block's inner blocks. False by default.
  • priority (number, optional): controls the priority with which a transformation is applied, where a lower value will take precedence over higher values. This behaves much like a WordPress hook. Like hooks, the default priority is 10 when not otherwise set.

Example: from Paragraph block to Heading block

To declare this transformation we add the following code into the heading block configuration, which uses the createBlock function from the wp-blocks package.

{% codetabs %} {% ESNext %}

transforms: {
    from: [
        {
            type: 'block',
            blocks: [ 'core/paragraph' ],
            transform: ( { content } ) => {
                return createBlock( 'core/heading', {
                    content,
                } );
            },
        },
    ]
},

{% ES5 %}

transforms: {
    from: [
        {
            type: 'block',
            blocks: [ 'core/paragraph' ],
            transform: function ( attributes ) {
                return createBlock( 'core/heading', {
                    content: attributes.content,
                } );
            },
        },
    ]
},

{% end %}

Example: blocks that have InnerBlocks

A block with InnerBlocks can also be transformed from and to another block with InnerBlocks.

{% codetabs %} {% ESNext %}

transforms: {
    to: [
        {
            type: 'block',
            blocks: [ 'some/block-with-innerblocks' ],
            transform: ( attributes, innerBlocks ) => {
                return createBlock(
                    'some/other-block-with-innerblocks',
                    attributes,
                    innerBlocks
                );
            },
        },
    ],
},

{% ES5 %}

transforms: {
    to: [
        {
            type: 'block',
            blocks: [ 'some/block-with-innerblocks' ],
            transform: function( attributes, innerBlocks ) {
                return createBlock(
                    'some/other-block-with-innerblocks',
                    attributes,
                    innerBlocks
                );
            },
        },
    ],
},

{% end %}

Enter

This type of transformations support the from direction, allowing blocks to be created from some content introduced by the user. They're applied in a new block line after the user has introduced some content and hit the ENTER key.

A transformation of type enter is an object that takes the following parameters:

  • type (string): the value enter.
  • regExp (RegExp): the Regular Expression to use as a matcher. If the value matches, the transformation will be applied.
  • transform (function): a callback that receives the value that has been entered. It should return a block object or an array of block objects.
  • priority (number, optional): controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a WordPress hook. Like hooks, the default priority is 10 when not otherwise set.

Example: from --- to Separator block

To create a separator block when the user types the hypen three times and then hits the ENTER key we can use the following code:

{% codetabs %} {% ESNext %}

transforms = {
    from: [
        {
            type: 'enter',
            regExp: /^-{3,}$/,
            transform: () => createBlock( 'core/separator' ),
        },
    ]
}

{% ES5 %}

transforms = {
    from: [
        {
            type: 'enter',
            regExp: /^-{3,}$/,
            transform: function( value ) {
                return createBlock( 'core/separator' );
            },
        },
    ]
}

{% end %}

Files

This type of transformations support the from direction, allowing blocks to be created from files dropped into the editor.

A transformation of type files is an object that takes the following parameters:

  • type (string): the value files.
  • transform (function): a callback that receives the array of files being processed. It should return a block object or an array of block objects.
  • isMatch (function, optional): a callback that receives the array of files being processed and should return a boolean. Returning false from this function will prevent the transform from being applied.
  • priority (number, optional): controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a WordPress hook. Like hooks, the default priority is 10 when not otherwise set.

Example: from file to File block

To create a File block when the user drops a file into the editor we can use the following code:

{% codetabs %} {% ESNext %}

transforms: {
    from: [
        {
            type: 'files',
            isMatch: ( files ) => files.length === 1,
            // By defining a lower priority than the default of 10,
            // we make that the File block to be created as a fallback,
            // if no other transform is found.
            priority: 15,
            transform: ( files ) => {
                const file = files[ 0 ];
                const blobURL = createBlobURL( file );
                // File will be uploaded in componentDidMount()
                return createBlock( 'core/file', {
                    href: blobURL,
                    fileName: file.name,
                    textLinkHref: blobURL,
                } );
            },
        },
    ];
}

{% ES5 %}

transforms: {
    from: [
        {
            type: 'files',
            isMatch: function( files ) {
                return files.length === 1;
            },
            // By defining a lower priority than the default of 10,
            // we make that the File block to be created as a fallback,
            // if no other transform is found.
            priority: 15,
            transform: function( files ) {
                var file = files[ 0 ];
                var blobURL = createBlobURL( file );
                // File will be uploaded in componentDidMount()
                return createBlock( 'core/file', {
                    href: blobURL,
                    fileName: file.name,
                    textLinkHref: blobURL,
                } );
            },
        },
    ];
}

{% end %}

Prefix

This type of transformations support the from direction, allowing blocks to be created from some text typed by the user. They're applied when, in a new block line, the user types some text and then adds a trailing space.

A transformation of type prefix is an object that takes the following parameters:

  • type (string): the value files.
  • prefix (string): the character or sequence of characters that match this transfrom.
  • transform (function): a callback that receives the content introduced. It should return a block object or an array of block objects.
  • priority (number, optional): controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a WordPress hook. Like hooks, the default priority is 10 when not otherwise set.

Example: from text to custom block

If we want to create a custom block when the user types the question mark, we could use this code:

{% codetabs %} {% ESNext %}

transforms: {
    from: [
        {
            type: 'prefix',
            prefix: '?',
            transform( content ) {
                return createBlock( 'my-plugin/question', {
                    content,
                } );
            },
        },
    ];
}

{% ES5 %}

transforms: {
    from: [
        {
            type: 'prefix',
            prefix: '?',
            transform: function( content ) {
                return createBlock( 'my-plugin/question', {
                    content,
                } );
            },
        },
    ];
}

{% end %}

Raw

This type of transformations support the from direction, allowing blocks to be created from raw HTML nodes. They're applied when the user executes the "Convert to Blocks" action frow within the block setting UI menu, as well as when some content is pasted or dropped into the editor.

A transformation of type raw is an object that takes the following parameters:

  • type (string): the value raw.
  • transform (function, optional): a callback that receives the node being processed. It should return a block object or an array of block objects.
  • schema (object|function, optional): it defines the attributes and children of the node that will be preserved on paste, according to its HTML content model. Take a look at pasteHandler for more info.
  • selector (string, optional): a CSS selector string to determine whether the element matches according to the element.matches method. The transform won't be executed if the element doesn't match. This is a shorthand and alternative to using isMatch, which, if present, will take precedence.
  • isMatch (function, optional): a callback that receives the node being processed and should return a boolean. Returning false from this function will prevent the transform from being applied.
  • priority (number, optional): controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a WordPress hook. Like hooks, the default priority is 10 when not otherwise set.

Example: from URLs to Embed block

If we want to create an Embed block when the user pastes some URL in the editor, we could use this code:

{% codetabs %} {% ESNext %}

transforms: {
    from: [
        {
            type: 'raw',
            isMatch: ( node ) =>
                node.nodeName === 'P' &&
                /^\s*(https?:\/\/\S+)\s*$/i.test( node.textContent ),
            transform: ( node ) => {
                return createBlock( 'core/embed', {
                    url: node.textContent.trim(),
                } );
            },
        },
    ],
}

{% ES5 %}

transforms: {
    from: [
        {
            type: 'raw',
            isMatch: function( node ) {
                return node.nodeName === 'P' &&
                    /^\s*(https?:\/\/\S+)\s*$/i.test( node.textContent );
            },
            transform: function( node ) {
                return createBlock( 'core/embed', {
                    url: node.textContent.trim(),
                } );
            },
        },
    ],
}

{% end %}

Shortcode

This type of transformations support the from direction, allowing blocks to be created from shortcodes. It's applied as part of the raw transformation process.

A transformation of type shortcode is an object that takes the following parameters:

  • type (string): the value shortcode.
  • tag (string|array): the shortcode tag or list of shortcode aliases this transform can work with.
  • attributes (object): object representing where the block attributes should be sourced from, according to the attributes shape defined by the block configuration object. If a particular attribute contains a shortcode key, it should be a function that receives the shortcode attributes as the first arguments and the WPShortcodeMatch as second, and returns a value for the attribute that will be sourced in the block's comment.
  • isMatch (function, optional): a callback that receives the shortcode attributes per the Shortcode API and should return a boolean. Returning false from this function will prevent the shortcode to be transformed into this block.
  • priority (number, optional): controls the priority with which a transform is applied, where a lower value will take precedence over higher values. This behaves much like a WordPress hook. Like hooks, the default priority is 10 when not otherwise set.

Example: from shortcode to block

An existing shortcode can be transformed into its block counterpart.

{% codetabs %} {% ESNext %}

transforms: {
    from: [
        {
            type: 'shortcode',
            tag: 'caption',
            attributes: {
                url: {
                    type: 'string',
                    source: 'attribute',
                    attribute: 'src',
                    selector: 'img',
                },
                align: {
                    type: 'string',
                    // The shortcode function will extract
                    // the shortcode atts into a value
                    // to be sourced in the block's comment.
                    shortcode: ( { named: { align = 'alignnone' } } ) => {
                        return align.replace( 'align', '' );
                    },
                },
            },
            // Prevent the shortcode to be converted
            // into this block when it doesn't
            // have the proper ID.
            isMatch( { named: { id } } ) {
                return id === 'my-id';
            },
        },
    ]
},

{% ES5 %}

transforms: {
    from: [
        {
            type: 'shortcode',
            tag: 'caption',
            attributes: {
                url: {
                    type: 'string',
                    source: 'attribute',
                    attribute: 'src',
                    selector: 'img',
                },
                align: {
                    type: 'string',
                    // The shortcode function will extract
                    // the shortcode atts into a value
                    // to be sourced in the block's comment.
                    shortcode: function( attributes ) {
                        var align = attributes.named.align ? attributes.named.align : 'alignnone';
                        return align.replace( 'align', '' );
                    },
                },
            },
            // Prevent the shortcode to be converted
            // into this block when it doesn't
            // have the proper ID.
            isMatch: function( attributes ) {
                return attributes.named.id === 'my-id';
            },
        },
    ]
},

{% end %}