From 902f982ac32819e8e78cb46acbb798ff946e11b3 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Tue, 13 Feb 2018 13:10:41 +0000 Subject: [PATCH] Add hook to validate `useOnce` blocks --- edit-post/hooks/index.js | 1 + edit-post/hooks/validate-use-once/index.js | 123 +++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 edit-post/hooks/validate-use-once/index.js diff --git a/edit-post/hooks/index.js b/edit-post/hooks/index.js index 043eab09ed2a9..f32f0019a1b24 100644 --- a/edit-post/hooks/index.js +++ b/edit-post/hooks/index.js @@ -2,3 +2,4 @@ * Internal dependencies */ import './more-menu'; +import './validate-use-once'; diff --git a/edit-post/hooks/validate-use-once/index.js b/edit-post/hooks/validate-use-once/index.js new file mode 100644 index 0000000000000..1f8c8a187630a --- /dev/null +++ b/edit-post/hooks/validate-use-once/index.js @@ -0,0 +1,123 @@ +/** + * External dependencies + */ +import { find } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createBlock, getBlockType } from '@wordpress/blocks'; +import { Button } from '@wordpress/components'; +import { withSelect, withDispatch } from '@wordpress/data'; +import { Warning } from '@wordpress/editor'; +import { compose, getWrapperDisplayName } from '@wordpress/element'; +import { addFilter } from '@wordpress/hooks'; +import { __ } from '@wordpress/i18n'; + +const enhance = compose( + /* + * For blocks whose block type defines `useOnce`, provides the wrapped + * component with `originalBlockUid` -- a reference to the first block of + * the same type in the content -- if and only if that "original" block is + * not the current one. Thus, an inexisting `originalBlockUid` prop signals + * that the block is valid. + * + * @param {Component} WrappedBlockEdit A filtered BlockEdit instance. + * @return {Component} Enhanced component with merged state + * data props. + */ + withSelect( ( select, block ) => { + const blocks = select( 'core/editor' ).getBlocks(); + const { useOnce } = getBlockType( block.name ); + + // For block types with no `useOnce` restriction, there is no "original + // block" to be found in the content, as the block itself is valid. + if ( ! useOnce ) { + return {}; + } + + // Otherwise, only pass `originalBlockUid` if it refers to a different + // block from the current one. + const firstOfSameType = find( blocks, ( { name } ) => block.name === name ); + const isInvalid = firstOfSameType && firstOfSameType.uid !== block.id; + return { + originalBlockUid: isInvalid && firstOfSameType.uid, + }; + } ), + withDispatch( ( dispatch, { originalBlockUid } ) => ( { + selectFirst: () => dispatch( 'core/editor' ).selectBlock( originalBlockUid ), + } ) ), +); + +function withUseOnceValidation( BlockEdit ) { + const WrappedBlockEdit = ( { + originalBlockUid, + selectFirst, + ...props + } ) => { + if ( ! originalBlockUid ) { + return ; + } + + const blockType = getBlockType( props.name ); + const outboundType = getOutboundType( blockType ); + + return [ +
+ +
, + +

+ { blockType.title }: + { __( 'This block may not be used more than once.' ) }

+

+ + + { outboundType && + + } +

+
, + ]; + }; + + WrappedBlockEdit.displayName = getWrapperDisplayName( BlockEdit, 'useOnceValidation' ); + + return enhance( WrappedBlockEdit ); +} + +/** + * Given a base block type, returns the default block type to which to offer + * transforms. + * + * @param {Object} blockType Base block type. + * @return {?Object} The chosen default block type. + */ +function getOutboundType( blockType ) { + // Grab the first outbound transform + const { to = [] } = blockType.transforms || {}; + const transform = find( to, ( { type, blocks } ) => + type === 'block' && blocks.length === 1 // What about when .length > 1? + ); + + if ( ! transform ) { + return null; + } + + return getBlockType( transform.blocks[ 0 ] ); +} + +addFilter( + 'blocks.BlockEdit', + 'core/validation/useOnce', + withUseOnceValidation +);