@wordpress/block-library
Version:
Block library for the WordPress editor.
199 lines (171 loc) • 5.15 kB
JavaScript
/**
* External dependencies
*/
import { times } from 'lodash';
import classnames from 'classnames';
import memoize from 'memize';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { PanelBody, RangeControl, G, SVG, Path } from '@wordpress/components';
import { Fragment } from '@wordpress/element';
import { createBlock } from '@wordpress/blocks';
import {
InspectorControls,
InnerBlocks,
} from '@wordpress/block-editor';
/**
* Allowed blocks constant is passed to InnerBlocks precisely as specified here.
* The contents of the array should never change.
* The array should contain the name of each block that is allowed.
* In columns block, the only block we allow is 'core/column'.
*
* @constant
* @type {string[]}
*/
const ALLOWED_BLOCKS = [ 'core/column' ];
/**
* Returns the layouts configuration for a given number of columns.
*
* @param {number} columns Number of columns.
*
* @return {Object[]} Columns layout configuration.
*/
const getColumnsTemplate = memoize( ( columns ) => {
return times( columns, () => [ 'core/column' ] );
} );
/**
* Given an HTML string for a deprecated columns inner block, returns the
* column index to which the migrated inner block should be assigned. Returns
* undefined if the inner block was not assigned to a column.
*
* @param {string} originalContent Deprecated Columns inner block HTML.
*
* @return {?number} Column to which inner block is to be assigned.
*/
function getDeprecatedLayoutColumn( originalContent ) {
let { doc } = getDeprecatedLayoutColumn;
if ( ! doc ) {
doc = document.implementation.createHTMLDocument( '' );
getDeprecatedLayoutColumn.doc = doc;
}
let columnMatch;
doc.body.innerHTML = originalContent;
for ( const classListItem of doc.body.firstChild.classList ) {
if ( ( columnMatch = classListItem.match( /^layout-column-(\d+)$/ ) ) ) {
return Number( columnMatch[ 1 ] ) - 1;
}
}
}
export const name = 'core/columns';
export const settings = {
title: __( 'Columns' ),
icon: <SVG viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><Path fill="none" d="M0 0h24v24H0V0z" /><G><Path d="M4,4H20a2,2,0,0,1,2,2V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4ZM4 6V18H8V6Zm6 0V18h4V6Zm6 0V18h4V6Z" /></G></SVG>,
category: 'layout',
attributes: {
columns: {
type: 'number',
default: 2,
},
},
description: __( 'Add a block that displays content in multiple columns, then add whatever content blocks you’d like.' ),
supports: {
align: [ 'wide', 'full' ],
html: false,
},
deprecated: [
{
attributes: {
columns: {
type: 'number',
default: 2,
},
},
isEligible( attributes, innerBlocks ) {
// Since isEligible is called on every valid instance of the
// Columns block and a deprecation is the unlikely case due to
// its subsequent migration, optimize for the `false` condition
// by performing a naive, inaccurate pass at inner blocks.
const isFastPassEligible = innerBlocks.some( ( innerBlock ) => (
/layout-column-\d+/.test( innerBlock.originalContent )
) );
if ( ! isFastPassEligible ) {
return false;
}
// Only if the fast pass is considered eligible is the more
// accurate, durable, slower condition performed.
return innerBlocks.some( ( innerBlock ) => (
getDeprecatedLayoutColumn( innerBlock.originalContent ) !== undefined
) );
},
migrate( attributes, innerBlocks ) {
const columns = innerBlocks.reduce( ( result, innerBlock ) => {
const { originalContent } = innerBlock;
let columnIndex = getDeprecatedLayoutColumn( originalContent );
if ( columnIndex === undefined ) {
columnIndex = 0;
}
if ( ! result[ columnIndex ] ) {
result[ columnIndex ] = [];
}
result[ columnIndex ].push( innerBlock );
return result;
}, [] );
const migratedInnerBlocks = columns.map( ( columnBlocks ) => (
createBlock( 'core/column', {}, columnBlocks )
) );
return [
attributes,
migratedInnerBlocks,
];
},
save( { attributes } ) {
const { columns } = attributes;
return (
<div className={ `has-${ columns }-columns` }>
<InnerBlocks.Content />
</div>
);
},
},
],
edit( { attributes, setAttributes, className } ) {
const { columns } = attributes;
const classes = classnames( className, `has-${ columns }-columns` );
return (
<Fragment>
<InspectorControls>
<PanelBody>
<RangeControl
label={ __( 'Columns' ) }
value={ columns }
onChange={ ( nextColumns ) => {
setAttributes( {
columns: nextColumns,
} );
} }
min={ 2 }
max={ 6 }
required
/>
</PanelBody>
</InspectorControls>
<div className={ classes }>
<InnerBlocks
template={ getColumnsTemplate( columns ) }
templateLock="all"
allowedBlocks={ ALLOWED_BLOCKS } />
</div>
</Fragment>
);
},
save( { attributes } ) {
const { columns } = attributes;
return (
<div className={ `has-${ columns }-columns` }>
<InnerBlocks.Content />
</div>
);
},
};