@wordpress/blocks
Version:
Block API for WordPress.
145 lines (125 loc) • 3.84 kB
text/typescript
/**
* WordPress dependencies
*/
import { renderToString } from '@wordpress/element';
/**
* Internal dependencies
*/
import { convertLegacyBlockNameAndAttributes } from './parser/convert-legacy-block';
import { createBlock } from './factory';
import { getBlockType } from './registration';
import type { Block, BlockAttribute } from '../types';
type TemplateItem = [ string, Record< string, unknown >?, TemplateItem[]? ];
/**
* Checks whether a list of blocks matches a template by comparing the block names.
*
* @param blocks Block list.
* @param template Block template.
*
* @return Whether the list of blocks matches a templates.
*/
export function doBlocksMatchTemplate(
blocks: Block[] = [],
template: TemplateItem[] = []
): boolean {
return (
blocks.length === template.length &&
template.every( ( [ name, , innerBlocksTemplate ], index ) => {
const block = blocks[ index ];
return (
name === block.name &&
doBlocksMatchTemplate( block.innerBlocks, innerBlocksTemplate )
);
} )
);
}
const isHTMLAttribute = (
attributeDefinition: BlockAttribute | undefined
): boolean => attributeDefinition?.source === 'html';
const isQueryAttribute = (
attributeDefinition: BlockAttribute | undefined
): boolean => attributeDefinition?.source === 'query';
function normalizeAttributes(
schema: Record< string, BlockAttribute >,
values: Record< string, unknown > | undefined
): Record< string, unknown > {
if ( ! values ) {
return {};
}
return Object.fromEntries(
Object.entries( values ).map( ( [ key, value ] ) => [
key,
normalizeAttribute( schema[ key ], value ),
] )
);
}
function normalizeAttribute(
definition: BlockAttribute | undefined,
value: unknown
): unknown {
if ( isHTMLAttribute( definition ) && Array.isArray( value ) ) {
// Introduce a deprecated call at this point
// When we're confident that "children" format should be removed from the templates.
return renderToString( value );
}
if ( isQueryAttribute( definition ) && value ) {
return ( value as Record< string, unknown >[] ).map(
( subValues: Record< string, unknown > ) => {
return normalizeAttributes( definition!.query!, subValues );
}
);
}
return value;
}
/**
* Synchronize a block list with a block template.
*
* Synchronizing a block list with a block template means that we loop over the blocks
* keep the block as is if it matches the block at the same position in the template
* (If it has the same name) and if doesn't match, we create a new block based on the template.
* Extra blocks not present in the template are removed.
*
* @param blocks Block list.
* @param template Block template.
*
* @return Updated Block list.
*/
export function synchronizeBlocksWithTemplate(
blocks: Block[] = [],
template?: TemplateItem[]
): Block[] {
// If no template is provided, return blocks unmodified.
if ( ! template ) {
return blocks;
}
return template.map(
( [ name, attributes, innerBlocksTemplate ], index ) => {
const block = blocks[ index ];
if ( block && block.name === name ) {
const innerBlocks = synchronizeBlocksWithTemplate(
block.innerBlocks,
innerBlocksTemplate
);
return { ...block, innerBlocks };
}
// To support old templates that were using the "children" format
// for the attributes using "html" strings now, we normalize the template attributes
// before creating the blocks.
const blockType = getBlockType( name );
const normalizedAttributes = normalizeAttributes(
blockType?.attributes ?? {},
attributes
);
const [ blockName, blockAttributes ] =
convertLegacyBlockNameAndAttributes(
name,
normalizedAttributes
);
return createBlock(
blockName!,
blockAttributes,
synchronizeBlocksWithTemplate( [], innerBlocksTemplate )
);
}
);
}