From 2e9cb5d8b4cef9e83e03646be59604ecccbad67b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 11 Mar 2019 09:57:37 +0100 Subject: [PATCH 01/14] Try refactoring the reusable blocks to use a separate block editor --- packages/block-editor/src/store/selectors.js | 33 ++++++++- packages/block-library/src/block/edit.js | 71 ++++++++----------- packages/editor/src/store/actions.js | 12 ++-- packages/editor/src/store/effects.js | 2 - .../src/store/effects/reusable-blocks.js | 68 ++---------------- packages/editor/src/store/reducer.js | 51 +++++++------ 6 files changed, 100 insertions(+), 137 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 9bafe5b832844a..5786b2efd59773 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -14,6 +14,7 @@ import { orderBy, reduce, some, + find, } from 'lodash'; import createSelector from 'rememo'; @@ -24,6 +25,7 @@ import { getBlockType, getBlockTypes, hasBlockSupport, + parse, } from '@wordpress/blocks'; // Module constants @@ -1125,7 +1127,8 @@ const canIncludeReusableBlockInInserter = ( state, reusableBlock, rootClientId ) return false; } - const referencedBlockName = getBlockName( state, reusableBlock.clientId ); + const referencedBlocks = __experimentalGetParsedReusableBlock( state, reusableBlock.id ); + const referencedBlockName = referencedBlocks ? referencedBlocks[ 0 ].name : null; if ( ! referencedBlockName ) { return false; } @@ -1246,7 +1249,8 @@ export const getInserterItems = createSelector( const buildReusableBlockInserterItem = ( reusableBlock ) => { const id = `core/block/${ reusableBlock.id }`; - const referencedBlockName = getBlockName( state, reusableBlock.clientId ); + const referencedBlocks = __experimentalGetParsedReusableBlock( state, reusableBlock.id ); + const referencedBlockName = referencedBlocks[ 0 ].name; const referencedBlockType = getBlockType( referencedBlockName ); const { time, count = 0 } = getInsertUsage( state, id ) || {}; @@ -1363,6 +1367,31 @@ export function isLastBlockChangePersistent( state ) { return state.blocks.isPersistentChange; } +/** + * Returns the parsed block saved as shared block with the given ID. + * + * @param {Object} state Global application state. + * @param {number|string} ref The shared block's ID. + * + * @return {Object} The parsed block. + */ +export const __experimentalGetParsedReusableBlock = createSelector( + ( state, ref ) => { + const reusableBlock = find( + getReusableBlocks( state ), + ( block ) => block.id === ref + ); + if ( ! reusableBlock ) { + return null; + } + + return parse( reusableBlock.content ); + }, + ( state ) => [ + getReusableBlocks( state ), + ], +); + /** * Returns true if the most recent block change is be considered ignored, or * false otherwise. An ignored change is one not to be committed by diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 785f5a762c01fd..52f4cd9d027c65 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -1,7 +1,7 @@ /** * External dependencies */ -import { noop, partial } from 'lodash'; +import { partial } from 'lodash'; /** * WordPress dependencies @@ -10,7 +10,7 @@ import { Component } from '@wordpress/element'; import { Placeholder, Spinner, Disabled } from '@wordpress/components'; import { withSelect, withDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BlockEdit } from '@wordpress/block-editor'; +import { BlockEditorProvider } from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; /** @@ -18,6 +18,7 @@ import { compose } from '@wordpress/compose'; */ import ReusableBlockEditPanel from './edit-panel'; import ReusableBlockIndicator from './indicator'; +import { parse, serialize } from 'path'; class ReusableBlockEdit extends Component { constructor( { reusableBlock } ) { @@ -25,7 +26,7 @@ class ReusableBlockEdit extends Component { this.startEditing = this.startEditing.bind( this ); this.stopEditing = this.stopEditing.bind( this ); - this.setAttributes = this.setAttributes.bind( this ); + this.setBlocks = this.setBlocks.bind( this ); this.setTitle = this.setTitle.bind( this ); this.save = this.save.bind( this ); @@ -34,14 +35,14 @@ class ReusableBlockEdit extends Component { this.state = { isEditing: true, title: reusableBlock.title, - changedAttributes: {}, + blocks: [], }; } else { // Start in preview mode when we're working with an existing reusable block this.state = { isEditing: false, title: null, - changedAttributes: null, + blocks: [], }; } } @@ -58,7 +59,7 @@ class ReusableBlockEdit extends Component { this.setState( { isEditing: true, title: reusableBlock.title, - changedAttributes: {}, + blocks: parse( reusableBlock.content ), } ); } @@ -66,16 +67,12 @@ class ReusableBlockEdit extends Component { this.setState( { isEditing: false, title: null, - changedAttributes: null, + blocks: [], } ); } - setAttributes( attributes ) { - this.setState( ( prevState ) => { - if ( prevState.changedAttributes !== null ) { - return { changedAttributes: { ...prevState.changedAttributes, ...attributes } }; - } - } ); + setBlocks( blocks ) { + this.setState( { blocks } ); } setTitle( title ) { @@ -83,40 +80,36 @@ class ReusableBlockEdit extends Component { } save() { - const { reusableBlock, onUpdateTitle, updateAttributes, block, onSave } = this.props; - const { title, changedAttributes } = this.state; - - if ( title !== reusableBlock.title ) { - onUpdateTitle( title ); - } - - updateAttributes( block.clientId, changedAttributes ); + const { onChange, onSave } = this.props; + const { blocks, title } = this.state; + const content = serialize( blocks ); + onChange( { title, content } ); onSave(); this.stopEditing(); } render() { - const { isSelected, reusableBlock, block, isFetching, isSaving, canUpdateBlock } = this.props; - const { isEditing, title, changedAttributes } = this.state; + const { isSelected, reusableBlock, isFetching, isSaving, canUpdateBlock, settings } = this.props; + const { isEditing, title, blocks } = this.state; if ( ! reusableBlock && isFetching ) { return ; } - if ( ! reusableBlock || ! block ) { + if ( ! reusableBlock ) { return { __( 'Block has been deleted or is unavailable.' ) }; } let element = ( - + +
test sdfsdf sdf sdf
+
); if ( ! isEditing ) { @@ -153,7 +146,8 @@ export default compose( [ } = select( 'core/editor' ); const { canUser } = select( 'core' ); const { - getBlock, + __experimentalGetParsedReusableBlock, + getSettings, } = select( 'core/block-editor' ); const { ref } = ownProps.attributes; const reusableBlock = getReusableBlock( ref ); @@ -162,25 +156,22 @@ export default compose( [ reusableBlock, isFetching: isFetchingReusableBlock( ref ), isSaving: isSavingReusableBlock( ref ), - block: reusableBlock ? getBlock( reusableBlock.clientId ) : null, + blocks: reusableBlock ? __experimentalGetParsedReusableBlock( reusableBlock.id ) : null, canUpdateBlock: !! reusableBlock && ! reusableBlock.isTemporary && !! canUser( 'update', 'blocks', ref ), + settings: getSettings(), }; } ), withDispatch( ( dispatch, ownProps ) => { const { __experimentalFetchReusableBlocks: fetchReusableBlocks, - __experimentalUpdateReusableBlockTitle: updateReusableBlockTitle, + __experimentalUpdateReusableBlock: updateReusableBlock, __experimentalSaveReusableBlock: saveReusableBlock, } = dispatch( 'core/editor' ); - const { - updateBlockAttributes, - } = dispatch( 'core/block-editor' ); const { ref } = ownProps.attributes; return { fetchReusableBlock: partial( fetchReusableBlocks, ref ), - updateAttributes: updateBlockAttributes, - onUpdateTitle: partial( updateReusableBlockTitle, ref ), + onChange: partial( updateReusableBlock, ref ), onSave: partial( saveReusableBlock, ref ), }; } ), diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 78b58f919dc16d..a20b0624fa3ea9 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -628,19 +628,19 @@ export function __experimentalDeleteReusableBlock( id ) { } /** - * Returns an action object used in signalling that a reusable block's title is + * Returns an action object used in signalling that a reusable block is * to be updated. * - * @param {number} id The ID of the reusable block to update. - * @param {string} title The new title. + * @param {number} id The ID of the reusable block to update. + * @param {Object} changes The changes to apply. * * @return {Object} Action object. */ -export function __experimentalUpdateReusableBlockTitle( id, title ) { +export function __experimentalUpdateReusableBlock( id, changes ) { return { - type: 'UPDATE_REUSABLE_BLOCK_TITLE', + type: 'UPDATE_REUSABLE_BLOCK', id, - title, + changes, }; } diff --git a/packages/editor/src/store/effects.js b/packages/editor/src/store/effects.js index 0893f8315cbf11..9e831b7a5a4b7d 100644 --- a/packages/editor/src/store/effects.js +++ b/packages/editor/src/store/effects.js @@ -7,7 +7,6 @@ import { deleteReusableBlocks, convertBlockToReusable, convertBlockToStatic, - receiveReusableBlocks, } from './effects/reusable-blocks'; export default { @@ -20,7 +19,6 @@ export default { DELETE_REUSABLE_BLOCK: ( action, store ) => { deleteReusableBlocks( action, store ); }, - RECEIVE_REUSABLE_BLOCKS: receiveReusableBlocks, CONVERT_BLOCK_TO_STATIC: convertBlockToStatic, CONVERT_BLOCK_TO_REUSABLE: convertBlockToReusable, }; diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index ebd4caee7fbed8..aff3ad757cacf1 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -12,8 +12,6 @@ import { parse, serialize, createBlock, - isReusableBlock, - cloneBlock, } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; // TODO: Ideally this would be the only dispatch in scope. This requires either @@ -31,7 +29,6 @@ import { import { __experimentalGetReusableBlock as getReusableBlock, } from '../selectors'; -import { getPostRawValue } from '../reducer'; /** * Module Constants @@ -69,16 +66,7 @@ export const fetchReusableBlocks = async ( action, store ) => { return null; } - const parsedBlocks = parse( post.content.raw ); - return { - reusableBlock: { - id: post.id, - title: getPostRawValue( post.title ), - }, - parsedBlock: parsedBlocks.length === 1 ? - parsedBlocks[ 0 ] : - createBlock( 'core/template', {}, parsedBlocks ), - }; + return post; } ) ); if ( results.length ) { @@ -115,9 +103,7 @@ export const saveReusableBlocks = async ( action, store ) => { const { id } = action; const { dispatch } = store; const state = store.getState(); - const { clientId, title, isTemporary } = getReusableBlock( state, id ); - const reusableBlock = select( 'core/block-editor' ).getBlock( clientId ); - const content = serialize( reusableBlock.name === 'core/template' ? reusableBlock.innerBlocks : reusableBlock ); + const { title, content, isTemporary } = getReusableBlock( state, id ); const data = isTemporary ? { title, content, status: 'publish' } : { id, title, content, status: 'publish' }; const path = isTemporary ? `/wp/v2/${ postType.rest_base }` : `/wp/v2/${ postType.rest_base }/${ id }`; @@ -168,11 +154,6 @@ export const deleteReusableBlocks = async ( action, store ) => { return; } - // Remove any other blocks that reference this reusable block - const allBlocks = select( 'core/block-editor' ).getBlocks(); - const associatedBlocks = allBlocks.filter( ( block ) => isReusableBlock( block ) && block.attributes.ref === id ); - const associatedBlockClientIds = associatedBlocks.map( ( block ) => block.clientId ); - const transactionId = uniqueId(); dispatch( { @@ -181,12 +162,6 @@ export const deleteReusableBlocks = async ( action, store ) => { optimist: { type: BEGIN, id: transactionId }, } ); - // Remove the parsed block. - dataDispatch( 'core/block-editor' ).removeBlocks( [ - ...associatedBlockClientIds, - reusableBlock.clientId, - ] ); - try { await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }`, @@ -214,15 +189,6 @@ export const deleteReusableBlocks = async ( action, store ) => { } }; -/** - * Receive Reusable Blocks Effect Handler. - * - * @param {Object} action action object. - */ -export const receiveReusableBlocks = ( action ) => { - dataDispatch( 'core/block-editor' ).receiveBlocks( map( action.results, 'parsedBlock' ) ); -}; - /** * Convert a reusable block to a static block effect handler * @@ -233,13 +199,7 @@ export const convertBlockToStatic = ( action, store ) => { const state = store.getState(); const oldBlock = select( 'core/block-editor' ).getBlock( action.clientId ); const reusableBlock = getReusableBlock( state, oldBlock.attributes.ref ); - const referencedBlock = select( 'core/block-editor' ).getBlock( reusableBlock.clientId ); - let newBlocks; - if ( referencedBlock.name === 'core/template' ) { - newBlocks = referencedBlock.innerBlocks.map( ( innerBlock ) => cloneBlock( innerBlock ) ); - } else { - newBlocks = [ cloneBlock( referencedBlock ) ]; - } + const newBlocks = parse( reusableBlock.content ); dataDispatch( 'core/block-editor' ).replaceBlocks( oldBlock.clientId, newBlocks ); }; @@ -251,32 +211,15 @@ export const convertBlockToStatic = ( action, store ) => { */ export const convertBlockToReusable = ( action, store ) => { const { dispatch } = store; - let parsedBlock; - if ( action.clientIds.length === 1 ) { - parsedBlock = select( 'core/block-editor' ).getBlock( action.clientIds[ 0 ] ); - } else { - parsedBlock = createBlock( - 'core/template', - {}, - select( 'core/block-editor' ).getBlocksByClientId( action.clientIds ) - ); - - // This shouldn't be necessary but at the moment - // we expect the content of the shared blocks to live in the blocks state. - dataDispatch( 'core/block-editor' ).receiveBlocks( [ parsedBlock ] ); - } - const reusableBlock = { id: uniqueId( 'reusable' ), - clientId: parsedBlock.clientId, title: __( 'Untitled Reusable Block' ), + content: serialize( select( 'core/block-editor' ).getBlocksByClientId( action.clientIds ) ), }; dispatch( receiveReusableBlocksAction( [ { reusableBlock, - parsedBlock, } ] ) ); - dispatch( saveReusableBlock( reusableBlock.id ) ); dataDispatch( 'core/block-editor' ).replaceBlocks( @@ -285,7 +228,4 @@ export const convertBlockToReusable = ( action, store ) => { ref: reusableBlock.id, } ) ); - - // Re-add the original block to the store, since replaceBlock() will have removed it - dataDispatch( 'core/block-editor' ).receiveBlocks( [ parsedBlock ] ); }; diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 69ab09f7bf0c8c..2917d003efb8da 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -2,7 +2,16 @@ * External dependencies */ import optimist from 'redux-optimist'; -import { reduce, omit, keys, isEqual } from 'lodash'; +import { + flow, + reduce, + omit, + mapValues, + keys, + isEqual, + map, + keyBy, +} from 'lodash'; /** * WordPress dependencies @@ -263,33 +272,26 @@ export const reusableBlocks = combineReducers( { data( state = {}, action ) { switch ( action.type ) { case 'RECEIVE_REUSABLE_BLOCKS': { - return reduce( action.results, ( nextState, result ) => { - const { id, title } = result.reusableBlock; - const { clientId } = result.parsedBlock; - - const value = { clientId, title }; - - if ( ! isEqual( nextState[ id ], value ) ) { - nextState = getMutateSafeObject( state, nextState ); - nextState[ id ] = value; - } - - return nextState; - }, state ); + return { + ...state, + ...map( + keyBy( action.results, 'id' ), + ( block ) => ( { + ...block, + content: block.content.raw, + title: block.title.raw, + } ) + ), + }; } - case 'UPDATE_REUSABLE_BLOCK_TITLE': { - const { id, title } = action; - - if ( ! state[ id ] || state[ id ].title === title ) { - return state; - } - + case 'UPDATE_REUSABLE_BLOCK': { + const { id, changes } = action; return { ...state, [ id ]: { ...state[ id ], - title, + ...changes, }, }; } @@ -305,7 +307,10 @@ export const reusableBlocks = combineReducers( { const value = state[ id ]; return { ...omit( state, id ), - [ updatedId ]: value, + [ updatedId ]: { + ...value, + id: updatedId, + }, }; } From 1e8a12c1a05d6b60e4a036210dd3df2427e8177b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 11 Mar 2019 10:55:34 +0100 Subject: [PATCH 02/14] Support parent registries --- packages/block-editor/src/index.js | 2 - packages/block-library/src/block/edit.js | 75 ++++++++++++++----- packages/data/src/registry.js | 4 + .../src/store/effects/reusable-blocks.js | 10 ++- packages/editor/src/store/reducer.js | 10 +-- 5 files changed, 70 insertions(+), 31 deletions(-) diff --git a/packages/block-editor/src/index.js b/packages/block-editor/src/index.js index 6931c7b1d7f658..85b2af41e2e064 100644 --- a/packages/block-editor/src/index.js +++ b/packages/block-editor/src/index.js @@ -8,9 +8,7 @@ import '@wordpress/viewport'; /** * Internal dependencies */ -import './store'; import './hooks'; - export * from './components'; export * from './utils'; export { storeConfig } from './store'; diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 52f4cd9d027c65..61c7592e9fd9c7 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -8,20 +8,48 @@ import { partial } from 'lodash'; */ import { Component } from '@wordpress/element'; import { Placeholder, Spinner, Disabled } from '@wordpress/components'; -import { withSelect, withDispatch } from '@wordpress/data'; +import { + withSelect, + withDispatch, + RegistryProvider, + RegistryConsumer, + createRegistry, +} from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BlockEditorProvider } from '@wordpress/block-editor'; -import { compose } from '@wordpress/compose'; +import { BlockEditorProvider, BlockList, storeConfig } from '@wordpress/block-editor'; +import { compose, createHigherOrderComponent } from '@wordpress/compose'; +import { parse, serialize } from '@wordpress/blocks'; /** * Internal dependencies */ import ReusableBlockEditPanel from './edit-panel'; import ReusableBlockIndicator from './indicator'; -import { parse, serialize } from 'path'; + +/** + * Higher-order component which renders the original component with the current + * registry context passed as its `registry` prop. + * + * @param {WPComponent} OriginalComponent Original component. + * + * @return {WPComponent} Enhanced component. + */ +const withRegistry = createHigherOrderComponent( + ( OriginalComponent ) => ( props ) => ( + + { ( registry ) => ( + + ) } + + ), + 'withRegistry' +); class ReusableBlockEdit extends Component { - constructor( { reusableBlock } ) { + constructor( { reusableBlock, registry } ) { super( ...arguments ); this.startEditing = this.startEditing.bind( this ); @@ -30,12 +58,12 @@ class ReusableBlockEdit extends Component { this.setTitle = this.setTitle.bind( this ); this.save = this.save.bind( this ); - if ( reusableBlock && reusableBlock.isTemporary ) { + if ( reusableBlock ) { // Start in edit mode when we're working with a newly created reusable block this.state = { - isEditing: true, + isEditing: reusableBlock.isTemporary, title: reusableBlock.title, - blocks: [], + blocks: parse( reusableBlock.content ), }; } else { // Start in preview mode when we're working with an existing reusable block @@ -45,6 +73,8 @@ class ReusableBlockEdit extends Component { blocks: [], }; } + + this.registry = createRegistry( { 'core/block-editor': storeConfig }, registry ); } componentDidMount() { @@ -53,9 +83,17 @@ class ReusableBlockEdit extends Component { } } + componentDidUpdate( prevProps ) { + if ( prevProps.reusableBlock !== this.props.reusableBlock && this.state.title === null ) { + this.setState( { + title: this.props.reusableBlock.title, + blocks: parse( this.props.reusableBlock.content ), + } ); + } + } + startEditing() { const { reusableBlock } = this.props; - this.setState( { isEditing: true, title: reusableBlock.title, @@ -102,14 +140,16 @@ class ReusableBlockEdit extends Component { } let element = ( - -
test sdfsdf sdf sdf
-
+ + + + + ); if ( ! isEditing ) { @@ -175,4 +215,5 @@ export default compose( [ onSave: partial( saveReusableBlock, ref ), }; } ), + withRegistry, ] )( ReusableBlockEdit ); diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index ecaaa94c8e64d5..6a9e8920674d65 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -191,5 +191,9 @@ export function createRegistry( storeConfigs = {}, parent = null ) { parent.subscribe( globalListener ); } + if ( parent ) { + parent.subscribe( globalListener ); + } + return withPlugins( registry ); } diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index aff3ad757cacf1..990151e248c080 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -66,7 +66,11 @@ export const fetchReusableBlocks = async ( action, store ) => { return null; } - return post; + return { + ...post, + content: post.content.raw, + title: post.title.raw, + }; } ) ); if ( results.length ) { @@ -217,9 +221,9 @@ export const convertBlockToReusable = ( action, store ) => { content: serialize( select( 'core/block-editor' ).getBlocksByClientId( action.clientIds ) ), }; - dispatch( receiveReusableBlocksAction( [ { + dispatch( receiveReusableBlocksAction( [ reusableBlock, - } ] ) ); + ] ) ); dispatch( saveReusableBlock( reusableBlock.id ) ); dataDispatch( 'core/block-editor' ).replaceBlocks( diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 2917d003efb8da..c7a4217baa2bab 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -9,7 +9,6 @@ import { mapValues, keys, isEqual, - map, keyBy, } from 'lodash'; @@ -274,14 +273,7 @@ export const reusableBlocks = combineReducers( { case 'RECEIVE_REUSABLE_BLOCKS': { return { ...state, - ...map( - keyBy( action.results, 'id' ), - ( block ) => ( { - ...block, - content: block.content.raw, - title: block.title.raw, - } ) - ), + ...keyBy( action.results, 'id' ), }; } From 288ac59c8f9a2eb72165a136eb1ac7542bc2ba6e Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 12 Mar 2019 08:39:40 +0100 Subject: [PATCH 03/14] Remove useless block type --- packages/block-library/src/template/index.js | 29 -------------------- 1 file changed, 29 deletions(-) delete mode 100644 packages/block-library/src/template/index.js diff --git a/packages/block-library/src/template/index.js b/packages/block-library/src/template/index.js deleted file mode 100644 index 7abddbd6552bfe..00000000000000 --- a/packages/block-library/src/template/index.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import edit from './edit'; -import icon from './icon'; -import metadata from './block.json'; -import save from './save'; - -const { name } = metadata; - -export { metadata, name }; - -export const settings = { - title: __( 'Reusable Template' ), - description: __( 'Template block used as a container.' ), - icon, - supports: { - customClassName: false, - html: false, - inserter: false, - }, - edit, - save, -}; From 468064d7f156bf426f1b10e38c3a7a8bf7f2262e Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 12 Mar 2019 08:40:57 +0100 Subject: [PATCH 04/14] Use extracted withRegistry --- packages/block-library/src/block/edit.js | 26 ++---------------------- 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 61c7592e9fd9c7..ebffd59f849df8 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -11,13 +11,13 @@ import { Placeholder, Spinner, Disabled } from '@wordpress/components'; import { withSelect, withDispatch, + withRegistry, RegistryProvider, - RegistryConsumer, createRegistry, } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { BlockEditorProvider, BlockList, storeConfig } from '@wordpress/block-editor'; -import { compose, createHigherOrderComponent } from '@wordpress/compose'; +import { compose } from '@wordpress/compose'; import { parse, serialize } from '@wordpress/blocks'; /** @@ -26,28 +26,6 @@ import { parse, serialize } from '@wordpress/blocks'; import ReusableBlockEditPanel from './edit-panel'; import ReusableBlockIndicator from './indicator'; -/** - * Higher-order component which renders the original component with the current - * registry context passed as its `registry` prop. - * - * @param {WPComponent} OriginalComponent Original component. - * - * @return {WPComponent} Enhanced component. - */ -const withRegistry = createHigherOrderComponent( - ( OriginalComponent ) => ( props ) => ( - - { ( registry ) => ( - - ) } - - ), - 'withRegistry' -); - class ReusableBlockEdit extends Component { constructor( { reusableBlock, registry } ) { super( ...arguments ); From 59a20e420f0366516267091ceeaed04190255ee1 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 12 Mar 2019 08:52:37 +0100 Subject: [PATCH 05/14] Support template reusable blocks --- packages/block-editor/src/store/selectors.js | 81 ++++---------------- 1 file changed, 13 insertions(+), 68 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 5786b2efd59773..4ab5d663f551b6 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -27,6 +27,7 @@ import { hasBlockSupport, parse, } from '@wordpress/blocks'; +import { SVG, Rect, G, Path } from '@wordpress/components'; // Module constants @@ -53,6 +54,7 @@ export const INSERTER_UTILITY_NONE = 0; const MILLISECONDS_PER_HOUR = 3600 * 1000; const MILLISECONDS_PER_DAY = 24 * 3600 * 1000; const MILLISECONDS_PER_WEEK = 7 * 24 * 3600 * 1000; +const templateIcon = ; /** * Shared reference to an empty array for cases where it is important to avoid @@ -647,29 +649,6 @@ export function getLastMultiSelectedBlockClientId( state ) { return last( getMultiSelectedBlockClientIds( state ) ) || null; } -/** - * Checks if possibleAncestorId is an ancestor of possibleDescendentId. - * - * @param {Object} state Editor state. - * @param {string} possibleAncestorId Possible ancestor client ID. - * @param {string} possibleDescendentId Possible descent client ID. - * - * @return {boolean} True if possibleAncestorId is an ancestor - * of possibleDescendentId, and false otherwise. - */ -const isAncestorOf = createSelector( - ( state, possibleAncestorId, possibleDescendentId ) => { - let idToCheck = possibleDescendentId; - while ( possibleAncestorId !== idToCheck && idToCheck ) { - idToCheck = getBlockRootClientId( state, idToCheck ); - } - return possibleAncestorId === idToCheck; - }, - ( state ) => [ - state.blocks.order, - ], -); - /** * Returns true if a multi-selection exists, and the block corresponding to the * specified client ID is the first block of the multi-selection set, or false @@ -1113,42 +1092,6 @@ const canIncludeBlockTypeInInserter = ( state, blockType, rootClientId ) => { return canInsertBlockTypeUnmemoized( state, blockType.name, rootClientId ); }; -/** - * Returns whether we can show a reusable block in the inserter - * - * @param {Object} state Global State - * @param {Object} reusableBlock Reusable block object - * @param {?string} rootClientId Optional root client ID of block list. - * - * @return {boolean} Whether the given block type is allowed to be shown in the inserter. - */ -const canIncludeReusableBlockInInserter = ( state, reusableBlock, rootClientId ) => { - if ( ! canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) ) { - return false; - } - - const referencedBlocks = __experimentalGetParsedReusableBlock( state, reusableBlock.id ); - const referencedBlockName = referencedBlocks ? referencedBlocks[ 0 ].name : null; - if ( ! referencedBlockName ) { - return false; - } - - const referencedBlockType = getBlockType( referencedBlockName ); - if ( ! referencedBlockType ) { - return false; - } - - if ( ! canInsertBlockTypeUnmemoized( state, referencedBlockName, rootClientId ) ) { - return false; - } - - if ( isAncestorOf( state, reusableBlock.clientId, rootClientId ) ) { - return false; - } - - return true; -}; - /** * Determines the items that appear in the inserter. Includes both static * items (e.g. a regular block type) and dynamic items (e.g. a reusable block). @@ -1250,8 +1193,10 @@ export const getInserterItems = createSelector( const id = `core/block/${ reusableBlock.id }`; const referencedBlocks = __experimentalGetParsedReusableBlock( state, reusableBlock.id ); - const referencedBlockName = referencedBlocks[ 0 ].name; - const referencedBlockType = getBlockType( referencedBlockName ); + let referencedBlockType; + if ( referencedBlocks.length === 1 ) { + referencedBlockType = getBlockType( referencedBlocks[ 0 ].name ); + } const { time, count = 0 } = getInsertUsage( state, id ) || {}; const utility = calculateUtility( 'reusable', count, false ); @@ -1262,7 +1207,7 @@ export const getInserterItems = createSelector( name: 'core/block', initialAttributes: { ref: reusableBlock.id }, title: reusableBlock.title, - icon: referencedBlockType.icon, + icon: referencedBlockType ? referencedBlockType.icon : templateIcon, category: 'reusable', keywords: [], isDisabled: false, @@ -1275,9 +1220,9 @@ export const getInserterItems = createSelector( .filter( ( blockType ) => canIncludeBlockTypeInInserter( state, blockType, rootClientId ) ) .map( buildBlockTypeInserterItem ); - const reusableBlockInserterItems = getReusableBlocks( state ) - .filter( ( block ) => canIncludeReusableBlockInInserter( state, block, rootClientId ) ) - .map( buildReusableBlockInserterItem ); + const reusableBlockInserterItems = canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) ? + getReusableBlocks( state ).map( buildReusableBlockInserterItem ) : + []; return orderBy( [ ...blockTypeInserterItems, ...reusableBlockInserterItems ], @@ -1314,9 +1259,9 @@ export const hasInserterItems = createSelector( if ( hasBlockType ) { return true; } - const hasReusableBlock = some( - getReusableBlocks( state ), - ( block ) => canIncludeReusableBlockInInserter( state, block, rootClientId ) + const hasReusableBlock = ( + canInsertBlockTypeUnmemoized( state, 'core/block', rootClientId ) && + getReusableBlocks( state ).length > 0 ); return hasReusableBlock; From 284dfe418cf5a4bf369f82d93df75719e3b2113e Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 12:34:35 +0100 Subject: [PATCH 06/14] Remove unnecessary files remaining from the template block refactoring --- packages/block-library/src/index.js | 2 -- packages/block-library/src/index.native.js | 2 -- packages/block-library/src/template/block.json | 4 ---- packages/block-library/src/template/edit.js | 8 -------- packages/block-library/src/template/icon.js | 8 -------- packages/block-library/src/template/save.js | 8 -------- 6 files changed, 32 deletions(-) delete mode 100644 packages/block-library/src/template/block.json delete mode 100644 packages/block-library/src/template/edit.js delete mode 100644 packages/block-library/src/template/icon.js delete mode 100644 packages/block-library/src/template/save.js diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index bcb177db239efb..e05057a05ed100 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -54,7 +54,6 @@ import * as shortcode from './shortcode'; import * as spacer from './spacer'; import * as subhead from './subhead'; import * as table from './table'; -import * as template from './template'; import * as textColumns from './text-columns'; import * as verse from './verse'; import * as video from './video'; @@ -137,7 +136,6 @@ export const registerCoreBlocks = () => { subhead, table, tagCloud, - template, textColumns, verse, video, diff --git a/packages/block-library/src/index.native.js b/packages/block-library/src/index.native.js index b3411411ee75fd..8d5eab4182e507 100644 --- a/packages/block-library/src/index.native.js +++ b/packages/block-library/src/index.native.js @@ -44,7 +44,6 @@ import * as shortcode from './shortcode'; import * as spacer from './spacer'; import * as subhead from './subhead'; import * as table from './table'; -import * as template from './template'; import * as textColumns from './text-columns'; import * as verse from './verse'; import * as video from './video'; @@ -92,7 +91,6 @@ export const coreBlocks = [ subhead, table, tagCloud, - template, textColumns, verse, video, diff --git a/packages/block-library/src/template/block.json b/packages/block-library/src/template/block.json deleted file mode 100644 index fc5600a48cc3a8..00000000000000 --- a/packages/block-library/src/template/block.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "core/template", - "category": "reusable" -} diff --git a/packages/block-library/src/template/edit.js b/packages/block-library/src/template/edit.js deleted file mode 100644 index 7a78461fc6ff27..00000000000000 --- a/packages/block-library/src/template/edit.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * WordPress dependencies - */ -import { InnerBlocks } from '@wordpress/block-editor'; - -export default function TemplateEdit() { - return ; -} diff --git a/packages/block-library/src/template/icon.js b/packages/block-library/src/template/icon.js deleted file mode 100644 index e62335ee654bb7..00000000000000 --- a/packages/block-library/src/template/icon.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * WordPress dependencies - */ -import { G, Path, Rect, SVG } from '@wordpress/components'; - -export default ( - -); diff --git a/packages/block-library/src/template/save.js b/packages/block-library/src/template/save.js deleted file mode 100644 index 17571d8f30d2de..00000000000000 --- a/packages/block-library/src/template/save.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * WordPress dependencies - */ -import { InnerBlocks } from '@wordpress/block-editor'; - -export default function save() { - return ; -} From 9dc8fcfb31091d4c0bc35cddfd9f742bc8680098 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 12:38:50 +0100 Subject: [PATCH 07/14] Add writing flow to the reusable block UI --- packages/block-library/src/block/edit.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index ebffd59f849df8..cce889d3f138eb 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -16,7 +16,12 @@ import { createRegistry, } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import { BlockEditorProvider, BlockList, storeConfig } from '@wordpress/block-editor'; +import { + BlockEditorProvider, + BlockList, + WritingFlow, + storeConfig, +} from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; import { parse, serialize } from '@wordpress/blocks'; @@ -125,7 +130,9 @@ class ReusableBlockEdit extends Component { onChange={ this.setBlocks } onInput={ this.setBlocks } > - + + + ); From 6c04025dfdea0987e84331848f5131409f204244 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 14:24:22 +0100 Subject: [PATCH 08/14] Fix reusable block previiew scaling --- packages/block-library/src/block/edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index cce889d3f138eb..97fd719dbdd21a 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -142,7 +142,7 @@ class ReusableBlockEdit extends Component { } return ( - <> +
{ ( isSelected || isEditing ) && ( } { element } - +
); } } From 49e3fbf62be4b55da6b5c86e9e4a45c7d8a1557f Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 15:43:46 +0100 Subject: [PATCH 09/14] Fix unit tests --- .../block-editor/src/store/test/selectors.js | 194 ++++-------------- .../src/store/effects/reusable-blocks.js | 10 + .../src/store/effects/test/reusable-blocks.js | 134 ++++-------- packages/editor/src/store/test/reducer.js | 21 +- 4 files changed, 96 insertions(+), 263 deletions(-) diff --git a/packages/block-editor/src/store/test/selectors.js b/packages/block-editor/src/store/test/selectors.js index 1e7dfe7f2b6f44..22ee0ed1adef30 100644 --- a/packages/block-editor/src/store/test/selectors.js +++ b/packages/block-editor/src/store/test/selectors.js @@ -1934,19 +1934,21 @@ describe( 'selectors', () => { it( 'should properly list block type and reusable block items', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - }, + byClientId: {}, + attributes: {}, order: {}, parents: {}, cache: {}, }, settings: { __experimentalReusableBlocks: [ - { id: 1, isTemporary: false, clientId: 'block1', title: 'Reusable Block 1' }, + { + id: 1, + isTemporary: false, + clientId: 'block1', + title: 'Reusable Block 1', + content: '', + }, ], }, // Intentionally include a test case which considers @@ -1989,155 +1991,31 @@ describe( 'selectors', () => { } ); } ); - it( 'should not list a reusable block item if it is being inserted inside it self', () => { - const state = { - blocks: { - byClientId: { - block1ref: { - name: 'core/block', - clientId: 'block1ref', - }, - itselfBlock1: { name: 'core/test-block-a' }, - itselfBlock2: { name: 'core/test-block-b' }, - }, - attributes: { - block1ref: { - attributes: { - ref: 1, - }, - }, - itselfBlock1: {}, - itselfBlock2: {}, - }, - order: { - '': [ 'block1ref' ], - }, - parents: { - block1ref: '', - }, - cache: { - block1ref: {}, - }, - }, - settings: { - __experimentalReusableBlocks: [ - { id: 1, isTemporary: false, clientId: 'itselfBlock1', title: 'Reusable Block 1' }, - { id: 2, isTemporary: false, clientId: 'itselfBlock2', title: 'Reusable Block 2' }, - ], - }, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - }; - const items = getInserterItems( state, 'itselfBlock1' ); - const reusableBlockItems = filter( items, [ 'name', 'core/block' ] ); - expect( reusableBlockItems ).toHaveLength( 1 ); - expect( reusableBlockItems[ 0 ] ).toEqual( { - id: 'core/block/2', - name: 'core/block', - initialAttributes: { ref: 2 }, - title: 'Reusable Block 2', - icon: { - src: 'test', - }, - category: 'reusable', - keywords: [], - isDisabled: false, - utility: 0, - frecency: 0, - } ); - } ); - - it( 'should not list a reusable block item if it is being inserted inside a descendent', () => { - const state = { - blocks: { - byClientId: { - block2ref: { - name: 'core/block', - clientId: 'block1ref', - }, - referredBlock1: { name: 'core/test-block-a' }, - referredBlock2: { name: 'core/test-block-b' }, - childReferredBlock2: { name: 'core/test-block-a' }, - grandchildReferredBlock2: { name: 'core/test-block-b' }, - }, - attributes: { - block2ref: { - attributes: { - ref: 2, - }, - }, - referredBlock1: {}, - referredBlock2: {}, - childReferredBlock2: {}, - grandchildReferredBlock2: {}, - }, - order: { - '': [ 'block2ref' ], - referredBlock2: [ 'childReferredBlock2' ], - childReferredBlock2: [ 'grandchildReferredBlock2' ], - }, - parents: { - block2ref: '', - childReferredBlock2: 'referredBlock2', - grandchildReferredBlock2: 'childReferredBlock2', - }, - cache: { - block2ref: {}, - childReferredBlock2: {}, - grandchildReferredBlock2: {}, - }, - }, - - settings: { - __experimentalReusableBlocks: [ - { id: 1, isTemporary: false, clientId: 'referredBlock1', title: 'Reusable Block 1' }, - { id: 2, isTemporary: false, clientId: 'referredBlock2', title: 'Reusable Block 2' }, - ], - }, - preferences: { - insertUsage: {}, - }, - blockListSettings: {}, - }; - const items = getInserterItems( state, 'grandchildReferredBlock2' ); - const reusableBlockItems = filter( items, [ 'name', 'core/block' ] ); - expect( reusableBlockItems ).toHaveLength( 1 ); - expect( reusableBlockItems[ 0 ] ).toEqual( { - id: 'core/block/1', - name: 'core/block', - initialAttributes: { ref: 1 }, - title: 'Reusable Block 1', - icon: { - src: 'test', - }, - category: 'reusable', - keywords: [], - isDisabled: false, - utility: 0, - frecency: 0, - } ); - } ); it( 'should order items by descending utility and frecency', () => { const state = { blocks: { - byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-a' }, - }, - attributes: { - block1: {}, - block2: {}, - }, + byClientId: {}, + attributes: {}, order: {}, parents: {}, cache: {}, }, settings: { __experimentalReusableBlocks: [ - { id: 1, isTemporary: false, clientId: 'block1', title: 'Reusable Block 1' }, - { id: 2, isTemporary: false, clientId: 'block2', title: 'Reusable Block 2' }, + { + id: 1, + isTemporary: false, + clientId: 'block1', + title: 'Reusable Block 1', + content: '', + }, + { + id: 2, + isTemporary: false, + clientId: 'block2', + title: 'Reusable Block 2', + content: '', + }, ], }, preferences: { @@ -2162,14 +2040,10 @@ describe( 'selectors', () => { const state = { blocks: { byClientId: { - block1: { name: 'core/test-block-a' }, - block2: { name: 'core/test-block-a' }, block3: { name: 'core/test-block-a' }, block4: { name: 'core/test-block-a' }, }, attributes: { - block1: {}, - block2: {}, block3: {}, block4: {}, }, @@ -2181,16 +2055,26 @@ describe( 'selectors', () => { block4: '', }, cache: { - block1: {}, - block2: {}, block3: {}, block4: {}, }, }, settings: { __experimentalReusableBlocks: [ - { id: 1, isTemporary: false, clientId: 'block1', title: 'Reusable Block 1' }, - { id: 2, isTemporary: false, clientId: 'block2', title: 'Reusable Block 2' }, + { + id: 1, + isTemporary: false, + clientId: 'block1', + title: 'Reusable Block 1', + content: '', + }, + { + id: 2, + isTemporary: false, + clientId: 'block2', + title: 'Reusable Block 2', + content: '', + }, ], }, preferences: { diff --git a/packages/editor/src/store/effects/reusable-blocks.js b/packages/editor/src/store/effects/reusable-blocks.js index 990151e248c080..bdc0ebaa5a7d97 100644 --- a/packages/editor/src/store/effects/reusable-blocks.js +++ b/packages/editor/src/store/effects/reusable-blocks.js @@ -12,6 +12,7 @@ import { parse, serialize, createBlock, + isReusableBlock, } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; // TODO: Ideally this would be the only dispatch in scope. This requires either @@ -157,6 +158,10 @@ export const deleteReusableBlocks = async ( action, store ) => { if ( ! reusableBlock || reusableBlock.isTemporary ) { return; } + // Remove any other blocks that reference this reusable block + const allBlocks = select( 'core/block-editor' ).getBlocks(); + const associatedBlocks = allBlocks.filter( ( block ) => isReusableBlock( block ) && block.attributes.ref === id ); + const associatedBlockClientIds = associatedBlocks.map( ( block ) => block.clientId ); const transactionId = uniqueId(); @@ -166,6 +171,11 @@ export const deleteReusableBlocks = async ( action, store ) => { optimist: { type: BEGIN, id: transactionId }, } ); + // Remove the parsed block. + if ( associatedBlockClientIds.length ) { + dataDispatch( 'core/block-editor' ).removeBlocks( associatedBlockClientIds ); + } + try { await apiFetch( { path: `/wp/v2/${ postType.rest_base }/${ id }`, diff --git a/packages/editor/src/store/effects/test/reusable-blocks.js b/packages/editor/src/store/effects/test/reusable-blocks.js index 1d783b1dd9fe19..97ae30feaf9494 100644 --- a/packages/editor/src/store/effects/test/reusable-blocks.js +++ b/packages/editor/src/store/effects/test/reusable-blocks.js @@ -20,7 +20,6 @@ import { dispatch as dataDispatch, select as dataSelect } from '@wordpress/data' import { fetchReusableBlocks, saveReusableBlocks, - receiveReusableBlocks, deleteReusableBlocks, convertBlockToStatic, convertBlockToReusable, @@ -99,14 +98,10 @@ describe( 'reusable blocks effects', () => { expect( dispatch ).toHaveBeenCalledWith( receiveReusableBlocksAction( [ { - reusableBlock: { - id: 123, - title: 'My cool block', - }, - parsedBlock: expect.objectContaining( { - name: 'core/test-block', - attributes: { name: 'Big Bird' }, - } ), + id: 123, + title: 'My cool block', + content: '', + status: 'publish', }, ] ) ); @@ -146,18 +141,12 @@ describe( 'reusable blocks effects', () => { await fetchReusableBlocks( fetchReusableBlocksAction( 123 ), store ); expect( dispatch ).toHaveBeenCalledWith( - receiveReusableBlocksAction( [ - { - reusableBlock: { - id: 123, - title: 'My cool block', - }, - parsedBlock: expect.objectContaining( { - name: 'core/test-block', - attributes: { name: 'Big Bird' }, - } ), - }, - ] ) + receiveReusableBlocksAction( [ { + id: 123, + title: 'My cool block', + content: '', + status: 'publish', + } ] ) ); expect( dispatch ).toHaveBeenCalledWith( { type: 'FETCH_REUSABLE_BLOCKS_SUCCESS', @@ -248,11 +237,8 @@ describe( 'reusable blocks effects', () => { return savePromise; } ); - const reusableBlock = { id: 123, title: 'My cool block' }; - const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - - const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( () => parsedBlock ); + const reusableBlock = { id: 123, title: 'My cool block', content: '' }; + const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -264,8 +250,6 @@ describe( 'reusable blocks effects', () => { id: 123, updatedId: 456, } ); - - dataSelect( 'core/block-editor' ).getBlock.mockReset(); } ); it( 'should handle an API error', async () => { @@ -282,11 +266,8 @@ describe( 'reusable blocks effects', () => { return savePromise; } ); - const reusableBlock = { id: 123, title: 'My cool block' }; - const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - - const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( () => parsedBlock ); + const reusableBlock = { id: 123, title: 'My cool block', content: '' }; + const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); const dispatch = jest.fn(); const store = { getState: () => state, dispatch }; @@ -296,26 +277,6 @@ describe( 'reusable blocks effects', () => { type: 'SAVE_REUSABLE_BLOCK_FAILURE', id: 123, } ); - - dataSelect( 'core/block-editor' ).getBlock.mockReset(); - } ); - } ); - - describe( 'receiveReusableBlocks', () => { - it( 'should receive parsed blocks', () => { - const action = receiveReusableBlocksAction( [ - { - parsedBlock: { clientId: 'broccoli' }, - }, - ] ); - - jest.spyOn( dataDispatch( 'core/block-editor' ), 'receiveBlocks' ).mockImplementation( () => {} ); - receiveReusableBlocks( action ); - expect( dataDispatch( 'core/block-editor' ).receiveBlocks ).toHaveBeenCalledWith( [ - { clientId: 'broccoli' }, - ] ); - - dataDispatch( 'core/block-editor' ).receiveBlocks.mockReset(); } ); } ); @@ -334,14 +295,11 @@ describe( 'reusable blocks effects', () => { return deletePromise; } ); + const reusableBlock = { id: 123, title: 'My cool block', content: '' }; + const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); const associatedBlock = createBlock( 'core/block', { ref: 123 } ); - const reusableBlock = { id: 123, title: 'My cool block' }; - const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - - const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [ associatedBlock, - parsedBlock, ] ); jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); @@ -357,7 +315,7 @@ describe( 'reusable blocks effects', () => { } ); expect( dataDispatch( 'core/block-editor' ).removeBlocks ).toHaveBeenCalledWith( - [ associatedBlock.clientId, parsedBlock.clientId ] + [ associatedBlock.clientId ] ); expect( dispatch ).toHaveBeenCalledWith( { @@ -384,12 +342,9 @@ describe( 'reusable blocks effects', () => { return deletePromise; } ); - const reusableBlock = { id: 123, title: 'My cool block' }; - const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [ - parsedBlock, - ] ); + const reusableBlock = { id: 123, title: 'My cool block', content: '' }; + const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [] ); jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); @@ -408,12 +363,8 @@ describe( 'reusable blocks effects', () => { it( 'should not save reusable blocks with temporary IDs', async () => { const reusableBlock = { id: 'reusable1', title: 'My cool block' }; - const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - - const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [ - parsedBlock, - ] ); + const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocks' ).mockImplementation( () => [] ); jest.spyOn( dataDispatch( 'core/block-editor' ), 'removeBlocks' ).mockImplementation( () => {} ); const dispatch = jest.fn(); @@ -430,12 +381,10 @@ describe( 'reusable blocks effects', () => { describe( 'convertBlockToStatic', () => { it( 'should convert a reusable block into a static block', () => { const associatedBlock = createBlock( 'core/block', { ref: 123 } ); - const reusableBlock = { id: 123, title: 'My cool block' }; - const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } ); - - const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + const reusableBlock = { id: 123, title: 'My cool block', content: '' }; + const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( id ) => - associatedBlock.clientId === id ? associatedBlock : parsedBlock + associatedBlock.clientId === id ? associatedBlock : null ); jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); @@ -460,14 +409,14 @@ describe( 'reusable blocks effects', () => { it( 'should convert a reusable block with nested blocks into a static block', () => { const associatedBlock = createBlock( 'core/block', { ref: 123 } ); - const reusableBlock = { id: 123, title: 'My cool block' }; - const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' }, [ - createBlock( 'core/test-block', { name: 'Oscar the Grouch' } ), - createBlock( 'core/test-block', { name: 'Cookie Monster' } ), - ] ); - const state = reducer( undefined, receiveReusableBlocksAction( [ { reusableBlock, parsedBlock } ] ) ); + const reusableBlock = { + id: 123, + title: 'My cool block', + content: '', + }; + const state = reducer( undefined, receiveReusableBlocksAction( [ reusableBlock ] ) ); jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( id ) => - associatedBlock.clientId === id ? associatedBlock : parsedBlock + associatedBlock.clientId === id ? associatedBlock : null ); jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); @@ -501,9 +450,9 @@ describe( 'reusable blocks effects', () => { describe( 'convertBlockToReusable', () => { it( 'should convert a static block into a reusable block', () => { - const staticBlock = createBlock( 'core/block', { ref: 123 } ); - jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlock' ).mockImplementation( ( ) => - staticBlock + const staticBlock = createBlock( 'core/test-block' ); + jest.spyOn( dataSelect( 'core/block-editor' ), 'getBlocksByClientId' ).mockImplementation( ( ) => + [ staticBlock ] ); jest.spyOn( dataDispatch( 'core/block-editor' ), 'replaceBlocks' ).mockImplementation( () => {} ); jest.spyOn( dataDispatch( 'core/block-editor' ), 'receiveBlocks' ).mockImplementation( () => {} ); @@ -515,12 +464,9 @@ describe( 'reusable blocks effects', () => { expect( dispatch ).toHaveBeenCalledWith( receiveReusableBlocksAction( [ { - reusableBlock: { - id: expect.stringMatching( /^reusable/ ), - clientId: staticBlock.clientId, - title: 'Untitled Reusable Block', - }, - parsedBlock: staticBlock, + id: expect.stringMatching( /^reusable/ ), + title: 'Untitled Reusable Block', + content: '', } ] ) ); @@ -536,10 +482,6 @@ describe( 'reusable blocks effects', () => { } ), ); - expect( dataDispatch( 'core/block-editor' ).receiveBlocks ).toHaveBeenCalledWith( - [ staticBlock ] - ); - dataDispatch( 'core/block-editor' ).replaceBlocks.mockReset(); dataDispatch( 'core/block-editor' ).receiveBlocks.mockReset(); dataSelect( 'core/block-editor' ).getBlock.mockReset(); diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index 156ce5b9b3602a..56428d44855b99 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -207,19 +207,14 @@ describe( 'state', () => { const state = reusableBlocks( {}, { type: 'RECEIVE_REUSABLE_BLOCKS', results: [ { - reusableBlock: { - id: 123, - title: 'My cool block', - }, - parsedBlock: { - clientId: 'foo', - }, + id: 123, + title: 'My cool block', } ], } ); expect( state ).toEqual( { data: { - 123: { clientId: 'foo', title: 'My cool block' }, + 123: { id: 123, title: 'My cool block' }, }, isFetching: {}, isSaving: {}, @@ -236,9 +231,11 @@ describe( 'state', () => { }; const state = reusableBlocks( initialState, { - type: 'UPDATE_REUSABLE_BLOCK_TITLE', + type: 'UPDATE_REUSABLE_BLOCK', id: 123, - title: 'My block', + changes: { + title: 'My block', + }, } ); expect( state ).toEqual( { @@ -253,7 +250,7 @@ describe( 'state', () => { it( "should update the reusable block's id if it was temporary", () => { const initialState = { data: { - reusable1: { clientId: '', title: '' }, + reusable1: { id: 'reusable1', title: '' }, }, isSaving: {}, }; @@ -266,7 +263,7 @@ describe( 'state', () => { expect( state ).toEqual( { data: { - 123: { clientId: '', title: '' }, + 123: { id: 123, title: '' }, }, isFetching: {}, isSaving: {}, From 8435925370ce3e85c9b2b8fd06b16fff3454d4b3 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 15:51:35 +0100 Subject: [PATCH 10/14] Fix e2e tests --- packages/e2e-tests/specs/reusable-blocks.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/e2e-tests/specs/reusable-blocks.test.js b/packages/e2e-tests/specs/reusable-blocks.test.js index 81f8db73d48629..0cc0232acd6b25 100644 --- a/packages/e2e-tests/specs/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/reusable-blocks.test.js @@ -118,6 +118,7 @@ describe( 'Reusable Blocks', () => { // Tab three times to navigate to the block's content await page.keyboard.press( 'Tab' ); await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'Enter' ); // Enter edit mode // Change the block's content await page.keyboard.type( 'Oh! ' ); From cc7151d3455c2bc8d3a58ab3c0350056e11a7b7d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 16:24:47 +0100 Subject: [PATCH 11/14] RegistryProvider is absorbed by BlockEditorProvider --- packages/block-library/src/block/edit.js | 31 +++++++++--------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 97fd719dbdd21a..8be303df2eeca7 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -11,16 +11,12 @@ import { Placeholder, Spinner, Disabled } from '@wordpress/components'; import { withSelect, withDispatch, - withRegistry, - RegistryProvider, - createRegistry, } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { BlockEditorProvider, BlockList, WritingFlow, - storeConfig, } from '@wordpress/block-editor'; import { compose } from '@wordpress/compose'; import { parse, serialize } from '@wordpress/blocks'; @@ -32,7 +28,7 @@ import ReusableBlockEditPanel from './edit-panel'; import ReusableBlockIndicator from './indicator'; class ReusableBlockEdit extends Component { - constructor( { reusableBlock, registry } ) { + constructor( { reusableBlock } ) { super( ...arguments ); this.startEditing = this.startEditing.bind( this ); @@ -56,8 +52,6 @@ class ReusableBlockEdit extends Component { blocks: [], }; } - - this.registry = createRegistry( { 'core/block-editor': storeConfig }, registry ); } componentDidMount() { @@ -123,18 +117,16 @@ class ReusableBlockEdit extends Component { } let element = ( - - - - - - - + + + + + ); if ( ! isEditing ) { @@ -200,5 +192,4 @@ export default compose( [ onSave: partial( saveReusableBlock, ref ), }; } ), - withRegistry, ] )( ReusableBlockEdit ); From d2b5b2519371799c2341fc7140c6103cc09b232a Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Wed, 21 Aug 2019 09:48:20 +0100 Subject: [PATCH 12/14] Rebase fix --- packages/data/src/registry.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/data/src/registry.js b/packages/data/src/registry.js index 6a9e8920674d65..ecaaa94c8e64d5 100644 --- a/packages/data/src/registry.js +++ b/packages/data/src/registry.js @@ -191,9 +191,5 @@ export function createRegistry( storeConfigs = {}, parent = null ) { parent.subscribe( globalListener ); } - if ( parent ) { - parent.subscribe( globalListener ); - } - return withPlugins( registry ); } From d83e8fd2e1fbfe03bccfdec1c1db7c990413452a Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 9 Sep 2019 13:19:46 +0100 Subject: [PATCH 13/14] Fix the empty space in reusable blocks --- packages/block-library/src/block/edit.js | 2 +- packages/block-library/src/block/editor.scss | 3 +++ packages/block-library/src/editor.scss | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 packages/block-library/src/block/editor.scss diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 8be303df2eeca7..7e450327085df7 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -134,7 +134,7 @@ class ReusableBlockEdit extends Component { } return ( -
+
{ ( isSelected || isEditing ) && ( Date: Mon, 9 Sep 2019 14:42:38 +0100 Subject: [PATCH 14/14] Remove unused variables --- packages/editor/src/store/reducer.js | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index c7a4217baa2bab..7014d8a3b1fe56 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -3,10 +3,7 @@ */ import optimist from 'redux-optimist'; import { - flow, - reduce, omit, - mapValues, keys, isEqual, keyBy, @@ -38,23 +35,6 @@ export function getPostRawValue( value ) { return value; } -/** - * Returns an object against which it is safe to perform mutating operations, - * given the original object and its current working copy. - * - * @param {Object} original Original object. - * @param {Object} working Working object. - * - * @return {Object} Mutation-safe object. - */ -function getMutateSafeObject( original, working ) { - if ( original === working ) { - return { ...original }; - } - - return working; -} - /** * Returns true if the two object arguments have the same keys, or false * otherwise.