From 8e3c3ca7369c31d1cc7980bfc7e76d6cc7572570 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 11 Mar 2019 09:57:37 +0100 Subject: [PATCH 01/17] 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 | 42 ++++++----- 6 files changed, 92 insertions(+), 136 deletions(-) diff --git a/packages/block-editor/src/store/selectors.js b/packages/block-editor/src/store/selectors.js index 462cf8cda1b8c9..66ce0b94aedaef 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'; @@ -25,6 +26,7 @@ import { getBlockTypes, hasBlockSupport, hasChildBlocksWithInserterSupport, + parse, } from '@wordpress/blocks'; // Module constants @@ -1126,7 +1128,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; } @@ -1248,7 +1251,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 ) || {}; @@ -1365,6 +1369,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 be98bc2ef8bc67..9d00aa4bd1f383 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -800,19 +800,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 ef6ad6fd798c07..ccd843c22ec6af 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -9,6 +9,8 @@ import { mapValues, keys, isEqual, + map, + keyBy, } from 'lodash'; /** @@ -416,33 +418,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, }, }; } @@ -458,7 +453,10 @@ export const reusableBlocks = combineReducers( { const value = state[ id ]; return { ...omit( state, id ), - [ updatedId ]: value, + [ updatedId ]: { + ...value, + id: updatedId, + }, }; } From 3da9f4dc0dc36a32e0f5ff92fad38175c38d1f04 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Mon, 11 Mar 2019 10:55:34 +0100 Subject: [PATCH 02/17] 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 ccd843c22ec6af..0c8dac8b803d2f 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'; @@ -420,14 +419,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 09f4bf98d5fa519aeef71e89802b00f55d670d2d Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 12 Mar 2019 08:39:40 +0100 Subject: [PATCH 03/17] 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 3b8b047aa25b26f5b9c389f9ec155c38a19d234b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 12 Mar 2019 08:40:57 +0100 Subject: [PATCH 04/17] 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 221923d5f9e4fd75461b814a42622acc07bc16d7 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Tue, 12 Mar 2019 08:52:37 +0100 Subject: [PATCH 05/17] 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 66ce0b94aedaef..89dcbc3e6414ab 100644 --- a/packages/block-editor/src/store/selectors.js +++ b/packages/block-editor/src/store/selectors.js @@ -28,6 +28,7 @@ import { hasChildBlocksWithInserterSupport, parse, } from '@wordpress/blocks'; +import { SVG, Rect, G, Path } from '@wordpress/components'; // Module constants @@ -54,6 +55,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 @@ -648,29 +650,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 @@ -1114,42 +1093,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). @@ -1252,8 +1195,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 ); @@ -1264,7 +1209,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, @@ -1277,9 +1222,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 ], @@ -1316,9 +1261,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 f55f6d1b8d6fa8d339cc45e28b9524888be09afd Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 12:34:35 +0100 Subject: [PATCH 06/17] 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 67d460b4b4b404..700b6505ee7242 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'; @@ -134,7 +133,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 ab244a7cfb6202c45f9508034be68ac61fcdba08 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 12:38:50 +0100 Subject: [PATCH 07/17] 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 a1ed736f3f9561d96349235a4d76b6734b61f9ff Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 14:24:22 +0100 Subject: [PATCH 08/17] 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 455758fffc50e8ceb3b02fc2a1961529a887de2c Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 15:43:46 +0100 Subject: [PATCH 09/17] 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 4e1289e44aefc3..1c88e6f9ec563a 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 @@ -1990,155 +1992,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: { @@ -2163,14 +2041,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: {}, }, @@ -2182,16 +2056,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 d964c72c0f77a3..afceb44121ad07 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -563,19 +563,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: {}, @@ -592,9 +587,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( { @@ -609,7 +606,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: {}, }; @@ -622,7 +619,7 @@ describe( 'state', () => { expect( state ).toEqual( { data: { - 123: { clientId: '', title: '' }, + 123: { id: 123, title: '' }, }, isFetching: {}, isSaving: {}, From 4f808e142e26055c734f86b2763485a0d3a64c5b Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 15:51:35 +0100 Subject: [PATCH 10/17] 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 b4fe8b68ba5d02930e6167e9af57816f4156bad9 Mon Sep 17 00:00:00 2001 From: Riad Benguella Date: Thu, 15 Aug 2019 16:24:47 +0100 Subject: [PATCH 11/17] 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 d7e7d8eb16913ea370be05627dbd48ac38e6d33c Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 20 Aug 2019 16:39:06 -0700 Subject: [PATCH 12/17] Core Data: Implement `EntityProvider` and `useEntityProp` hook. --- package-lock.json | 1 + packages/core-data/package.json | 1 + packages/core-data/src/entity-provider.js | 74 +++++++++++++++++++++++ packages/core-data/src/index.js | 2 + 4 files changed, 78 insertions(+) create mode 100644 packages/core-data/src/entity-provider.js diff --git a/package-lock.json b/package-lock.json index 2febb418532ae1..5ee6b636dcf9a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4761,6 +4761,7 @@ "@wordpress/api-fetch": "file:packages/api-fetch", "@wordpress/data": "file:packages/data", "@wordpress/deprecated": "file:packages/deprecated", + "@wordpress/element": "file:packages/element", "@wordpress/url": "file:packages/url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", diff --git a/packages/core-data/package.json b/packages/core-data/package.json index 6f55b40cd39f69..b2dae8d258be35 100644 --- a/packages/core-data/package.json +++ b/packages/core-data/package.json @@ -26,6 +26,7 @@ "@wordpress/api-fetch": "file:../api-fetch", "@wordpress/data": "file:../data", "@wordpress/deprecated": "file:../deprecated", + "@wordpress/element": "file:../element", "@wordpress/url": "file:../url", "equivalent-key-map": "^0.2.2", "lodash": "^4.17.14", diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js new file mode 100644 index 00000000000000..7bf5d31e329839 --- /dev/null +++ b/packages/core-data/src/entity-provider.js @@ -0,0 +1,74 @@ +/** + * WordPress dependencies + */ +import { createContext, useContext, useCallback } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { defaultEntities } from './entities'; + +const entities = { + ...defaultEntities.reduce( ( acc, entity ) => { + acc[ entity.name ] = { kind: entity.kind, context: createContext() }; + return acc; + }, {} ), + post: { kind: 'postType', context: createContext() }, +}; + +/** + * Context provider component for providing + * an entity for a specific entity type. + * + * @param {number} id The entity ID. + * @param {string} type The entity type. + * @param {*} children The children to wrap. + * + * @return {Object} The provided children, wrapped with + * the entity's context provider. + */ +export default function EntityProvider( { id, type, children } ) { + const Provider = entities[ type ].context.Provider; + return { children }; +} + +/** + * Hook that returns the value and a setter for the + * specified property of the nearest provided + * entity of the specified type. + * + * @param {string} type The entity type. + * @param {string} prop The property name. + * + * @return {[*, Function]} A tuple where the first item is the + * property value and the second is the + * setter. + */ +export function useEntityProp( type, prop ) { + const kind = entities[ type ].kind; + const id = useContext( entities[ type ].context ); + + const value = useSelect( + ( select ) => { + if ( ! id ) { + return; + } + const entity = + id && select( 'core' ).getEditedEntityRecord( kind, type, id )[ prop ]; + return entity && entity[ prop ]; + }, + [ kind, type, id, prop ] + ); + + const { editEntityRecord } = useDispatch( 'core' ); + const setValue = useCallback( + ( newValue ) => + editEntityRecord( kind, type, id, { + [ prop ]: newValue, + } ), + [ kind, type, id, prop ] + ); + + return [ value, setValue ]; +} diff --git a/packages/core-data/src/index.js b/packages/core-data/src/index.js index b0c5718ab9b888..2cdddb960e448b 100644 --- a/packages/core-data/src/index.js +++ b/packages/core-data/src/index.js @@ -48,3 +48,5 @@ registerStore( REDUCER_KEY, { selectors: { ...selectors, ...entitySelectors }, resolvers: { ...resolvers, ...entityResolvers }, } ); + +export { default as EntityProvider, useEntityProp } from './entity-provider'; From 4eb13a30981591c79e6b8e1fba6d213f690a789d Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 20 Aug 2019 17:21:02 -0700 Subject: [PATCH 13/17] Blocks: Add the "Theme" block category. --- lib/blocks.php | 20 ++++++++++++++++++++ packages/blocks/src/store/reducer.js | 1 + 2 files changed, 21 insertions(+) diff --git a/lib/blocks.php b/lib/blocks.php index cf66e1d4044a89..b79745e31b17c9 100644 --- a/lib/blocks.php +++ b/lib/blocks.php @@ -46,6 +46,26 @@ function gutenberg_reregister_core_block_types() { } add_action( 'init', 'gutenberg_reregister_core_block_types' ); +/** + * Adds new block categories needed by the Gutenberg plugin. + * + * @param array $categories List of block categories. + * + * @return array List of block categories with the new categories added. + */ +function gutenberg_filter_block_categories( $categories ) { + return array_merge( + $categories, + array( + array( + 'slug' => 'theme', + 'title' => __( 'Theme Blocks', 'gutenberg' ), + ), + ) + ); +} +add_filter( 'block_categories', 'gutenberg_filter_block_categories' ); + /** * Registers a new block style. * diff --git a/packages/blocks/src/store/reducer.js b/packages/blocks/src/store/reducer.js index 405e60109adda3..64c44c3a37c8be 100644 --- a/packages/blocks/src/store/reducer.js +++ b/packages/blocks/src/store/reducer.js @@ -29,6 +29,7 @@ export const DEFAULT_CATEGORIES = [ { slug: 'widgets', title: __( 'Widgets' ) }, { slug: 'embed', title: __( 'Embeds' ) }, { slug: 'reusable', title: __( 'Reusable Blocks' ) }, + { slug: 'theme', title: __( 'Theme Blocks' ) }, ]; /** From 2d83d9a405148bc8d2be13e0f90d56298f9cccc9 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 20 Aug 2019 17:41:32 -0700 Subject: [PATCH 14/17] Block Library: Implement Post block. --- packages/block-library/src/index.js | 6 ++ packages/block-library/src/post/block.json | 9 +++ packages/block-library/src/post/edit.js | 70 ++++++++++++++++++++++ packages/block-library/src/post/index.js | 20 +++++++ packages/block-library/src/post/save.js | 8 +++ packages/block-library/src/post/style.scss | 11 ++++ packages/block-library/src/style.scss | 1 + 7 files changed, 125 insertions(+) create mode 100644 packages/block-library/src/post/block.json create mode 100644 packages/block-library/src/post/edit.js create mode 100644 packages/block-library/src/post/index.js create mode 100644 packages/block-library/src/post/save.js create mode 100644 packages/block-library/src/post/style.scss diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 700b6505ee7242..0c49549061b880 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -61,6 +61,9 @@ import * as tagCloud from './tag-cloud'; import * as classic from './classic'; +// Custom Entity Blocks +import * as post from './post'; + /** * Function to register an individual block. * @@ -136,6 +139,9 @@ export const registerCoreBlocks = () => { textColumns, verse, video, + + // Register Custom Entity Blocks. + post, ].forEach( registerBlock ); setDefaultBlockName( paragraph.name ); diff --git a/packages/block-library/src/post/block.json b/packages/block-library/src/post/block.json new file mode 100644 index 00000000000000..a8a2be4fe2eb11 --- /dev/null +++ b/packages/block-library/src/post/block.json @@ -0,0 +1,9 @@ +{ + "name": "core/post", + "category": "theme", + "attributes": { + "postId": { + "type": "number" + } + } +} diff --git a/packages/block-library/src/post/edit.js b/packages/block-library/src/post/edit.js new file mode 100644 index 00000000000000..038d0552b31824 --- /dev/null +++ b/packages/block-library/src/post/edit.js @@ -0,0 +1,70 @@ +/** + * WordPress dependencies + */ +import { useState, useCallback } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { + Placeholder, + TextControl, + Button, + Spinner, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { EntityProvider } from '@wordpress/core-data'; +import { InnerBlocks } from '@wordpress/block-editor'; + +export default function PostEdit( { attributes: { postId }, setAttributes } ) { + const [ placeholderPostId, setPlaceholderPostId ] = useState(); + const onPostIdSubmit = useCallback( ( event ) => { + event.preventDefault(); + + const value = event.currentTarget[ 0 ].value; + if ( ! value ) { + return; + } + + setAttributes( { postId: Number( value ) } ); + }, [] ); + + const entity = useSelect( + ( select ) => + postId && select( 'core' ).getEntityRecord( 'postType', 'post', postId ), + [ postId ] + ); + + if ( ! postId ) { + return ( + +
+ + + +
+ ); + } + + return entity ? ( + + + + ) : ( + + + + ); +} diff --git a/packages/block-library/src/post/index.js b/packages/block-library/src/post/index.js new file mode 100644 index 00000000000000..3d7ab2f8393b93 --- /dev/null +++ b/packages/block-library/src/post/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import save from './save'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Post' ), + edit, + save, +}; diff --git a/packages/block-library/src/post/save.js b/packages/block-library/src/post/save.js new file mode 100644 index 00000000000000..039b6d8e9efb1f --- /dev/null +++ b/packages/block-library/src/post/save.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { InnerBlocks } from '@wordpress/block-editor'; + +export default function PostSave() { + return ; +} diff --git a/packages/block-library/src/post/style.scss b/packages/block-library/src/post/style.scss new file mode 100644 index 00000000000000..f3c57fddbad860 --- /dev/null +++ b/packages/block-library/src/post/style.scss @@ -0,0 +1,11 @@ +.wp-block-post { + // Extra specificity to override default Placeholder styles. + &__placeholder-form.wp-block-post__placeholder-form { + align-items: center; + text-align: left; + } + + &__placeholder-input { + width: 100px; + } +} diff --git a/packages/block-library/src/style.scss b/packages/block-library/src/style.scss index fd78fcc3b18d11..9443f2798fdf1a 100644 --- a/packages/block-library/src/style.scss +++ b/packages/block-library/src/style.scss @@ -23,6 +23,7 @@ @import "./text-columns/style.scss"; @import "./verse/style.scss"; @import "./video/style.scss"; +@import "./post/style.scss"; // The following selectors have increased specificity (using the :root prefix) // to assure colors take effect over another base class color, mainly to let From 4224881da3f38af6c235bcae8fc6f2053f6f9317 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 20 Aug 2019 18:03:40 -0700 Subject: [PATCH 15/17] Block Library: Implement Post Title block. --- packages/block-library/src/index.js | 2 ++ .../block-library/src/post-title/block.json | 4 ++++ packages/block-library/src/post-title/edit.js | 24 +++++++++++++++++++ packages/block-library/src/post-title/icon.js | 11 +++++++++ .../block-library/src/post-title/index.js | 20 ++++++++++++++++ packages/core-data/src/entity-provider.js | 11 +++++---- 6 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 packages/block-library/src/post-title/block.json create mode 100644 packages/block-library/src/post-title/edit.js create mode 100644 packages/block-library/src/post-title/icon.js create mode 100644 packages/block-library/src/post-title/index.js diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 0c49549061b880..ee757f9c73d957 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -63,6 +63,7 @@ import * as classic from './classic'; // Custom Entity Blocks import * as post from './post'; +import * as postTitle from './post-title'; /** * Function to register an individual block. @@ -142,6 +143,7 @@ export const registerCoreBlocks = () => { // Register Custom Entity Blocks. post, + postTitle, ].forEach( registerBlock ); setDefaultBlockName( paragraph.name ); diff --git a/packages/block-library/src/post-title/block.json b/packages/block-library/src/post-title/block.json new file mode 100644 index 00000000000000..11d61129406b8a --- /dev/null +++ b/packages/block-library/src/post-title/block.json @@ -0,0 +1,4 @@ +{ + "name": "core/post-title", + "category": "theme" +} diff --git a/packages/block-library/src/post-title/edit.js b/packages/block-library/src/post-title/edit.js new file mode 100644 index 00000000000000..80b007a1774657 --- /dev/null +++ b/packages/block-library/src/post-title/edit.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { useEntityProp } from '@wordpress/core-data'; +import { RichText } from '@wordpress/block-editor'; +import { cleanForSlug } from '@wordpress/editor'; +import { __ } from '@wordpress/i18n'; + +export default function PostTitleEdit() { + const [ title, setTitle ] = useEntityProp( 'post', 'title' ); + const [ , setSlug ] = useEntityProp( 'post', 'slug' ); + return ( + { + setTitle( value ); + setSlug( cleanForSlug( value ) ); + } } + tagName="h1" + placeholder={ __( 'Title' ) } + formattingControls={ [] } + /> + ); +} diff --git a/packages/block-library/src/post-title/icon.js b/packages/block-library/src/post-title/icon.js new file mode 100644 index 00000000000000..6dc60909619f82 --- /dev/null +++ b/packages/block-library/src/post-title/icon.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +export default ( + + + + +); diff --git a/packages/block-library/src/post-title/index.js b/packages/block-library/src/post-title/index.js new file mode 100644 index 00000000000000..ba834fe69b0384 --- /dev/null +++ b/packages/block-library/src/post-title/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import icon from './icon'; +import edit from './edit'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Post Title' ), + icon, + edit, +}; diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index 7bf5d31e329839..f61722a467ac4d 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -54,8 +54,7 @@ export function useEntityProp( type, prop ) { if ( ! id ) { return; } - const entity = - id && select( 'core' ).getEditedEntityRecord( kind, type, id )[ prop ]; + const entity = select( 'core' ).getEditedEntityRecord( kind, type, id ); return entity && entity[ prop ]; }, [ kind, type, id, prop ] @@ -63,10 +62,14 @@ export function useEntityProp( type, prop ) { const { editEntityRecord } = useDispatch( 'core' ); const setValue = useCallback( - ( newValue ) => + ( newValue ) => { + if ( ! id ) { + return; + } editEntityRecord( kind, type, id, { [ prop ]: newValue, - } ), + } ); + }, [ kind, type, id, prop ] ); From 7a4bf2b890c34bee5ff3d66553d7fe17baa8b794 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 20 Aug 2019 18:12:48 -0700 Subject: [PATCH 16/17] Block Library: Implement Post Content block. --- packages/block-library/src/index.js | 2 ++ .../block-library/src/post-content/block.json | 4 ++++ .../block-library/src/post-content/edit.js | 24 +++++++++++++++++++ .../block-library/src/post-content/icon.js | 11 +++++++++ .../block-library/src/post-content/index.js | 20 ++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 packages/block-library/src/post-content/block.json create mode 100644 packages/block-library/src/post-content/edit.js create mode 100644 packages/block-library/src/post-content/icon.js create mode 100644 packages/block-library/src/post-content/index.js diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index ee757f9c73d957..4898482b2b7e5b 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -64,6 +64,7 @@ import * as classic from './classic'; // Custom Entity Blocks import * as post from './post'; import * as postTitle from './post-title'; +import * as postContent from './post-content'; /** * Function to register an individual block. @@ -144,6 +145,7 @@ export const registerCoreBlocks = () => { // Register Custom Entity Blocks. post, postTitle, + postContent, ].forEach( registerBlock ); setDefaultBlockName( paragraph.name ); diff --git a/packages/block-library/src/post-content/block.json b/packages/block-library/src/post-content/block.json new file mode 100644 index 00000000000000..7472bd1b04c150 --- /dev/null +++ b/packages/block-library/src/post-content/block.json @@ -0,0 +1,4 @@ +{ + "name": "core/post-content", + "category": "theme" +} diff --git a/packages/block-library/src/post-content/edit.js b/packages/block-library/src/post-content/edit.js new file mode 100644 index 00000000000000..7d60634913c24d --- /dev/null +++ b/packages/block-library/src/post-content/edit.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { useEntityProp } from '@wordpress/core-data'; +import { + BlockEditorProvider, + WritingFlow, + BlockList, +} from '@wordpress/block-editor'; + +export default function PostContentEdit() { + const [ blocks = [], setBlocks ] = useEntityProp( 'post', 'blocks' ); + return ( + + + + + + ); +} diff --git a/packages/block-library/src/post-content/icon.js b/packages/block-library/src/post-content/icon.js new file mode 100644 index 00000000000000..2cd26e9d5f5a31 --- /dev/null +++ b/packages/block-library/src/post-content/icon.js @@ -0,0 +1,11 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +export default ( + + + + +); diff --git a/packages/block-library/src/post-content/index.js b/packages/block-library/src/post-content/index.js new file mode 100644 index 00000000000000..76a1a07148094e --- /dev/null +++ b/packages/block-library/src/post-content/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import icon from './icon'; +import edit from './edit'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Post Content' ), + icon, + edit, +}; From 7e2bdfb34cdf2e8aa63173a3585c1dafea11fada Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 20 Aug 2019 18:38:36 -0700 Subject: [PATCH 17/17] Block Library: Implement Site Title block. --- packages/block-library/src/index.js | 2 + .../block-library/src/site-title/block.json | 4 ++ packages/block-library/src/site-title/edit.js | 37 +++++++++++++++++++ packages/block-library/src/site-title/icon.js | 12 ++++++ .../block-library/src/site-title/index.js | 20 ++++++++++ packages/core-data/src/entities.js | 1 + packages/core-data/src/entity-provider.js | 6 --- packages/core-data/src/resolvers.js | 2 +- 8 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 packages/block-library/src/site-title/block.json create mode 100644 packages/block-library/src/site-title/edit.js create mode 100644 packages/block-library/src/site-title/icon.js create mode 100644 packages/block-library/src/site-title/index.js diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js index 4898482b2b7e5b..ede82f89a1ab2a 100644 --- a/packages/block-library/src/index.js +++ b/packages/block-library/src/index.js @@ -62,6 +62,7 @@ import * as tagCloud from './tag-cloud'; import * as classic from './classic'; // Custom Entity Blocks +import * as siteTitle from './site-title'; import * as post from './post'; import * as postTitle from './post-title'; import * as postContent from './post-content'; @@ -143,6 +144,7 @@ export const registerCoreBlocks = () => { video, // Register Custom Entity Blocks. + siteTitle, post, postTitle, postContent, diff --git a/packages/block-library/src/site-title/block.json b/packages/block-library/src/site-title/block.json new file mode 100644 index 00000000000000..ab9a59291621ca --- /dev/null +++ b/packages/block-library/src/site-title/block.json @@ -0,0 +1,4 @@ +{ + "name": "core/site-title", + "category": "theme" +} diff --git a/packages/block-library/src/site-title/edit.js b/packages/block-library/src/site-title/edit.js new file mode 100644 index 00000000000000..a9aaba3d0006a3 --- /dev/null +++ b/packages/block-library/src/site-title/edit.js @@ -0,0 +1,37 @@ +/** + * WordPress dependencies + */ +import { EntityProvider, useEntityProp } from '@wordpress/core-data'; +import { RichText } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { Placeholder, Spinner } from '@wordpress/components'; + +function TitleInput() { + const [ title, setTitle ] = useEntityProp( 'site', 'title' ); + return ( + + ); +} + +export default function SiteTitleEdit() { + const site = useSelect( + ( select ) => select( 'core' ).getEntityRecord( 'root', 'site' ), + [] + ); + return site ? ( + + + + ) : ( + + + + ); +} diff --git a/packages/block-library/src/site-title/icon.js b/packages/block-library/src/site-title/icon.js new file mode 100644 index 00000000000000..1ab74dec2c32d3 --- /dev/null +++ b/packages/block-library/src/site-title/icon.js @@ -0,0 +1,12 @@ +/** + * WordPress dependencies + */ +import { SVG, Path, Circle } from '@wordpress/components'; + +export default ( + + + + + +); diff --git a/packages/block-library/src/site-title/index.js b/packages/block-library/src/site-title/index.js new file mode 100644 index 00000000000000..4b8fc50b86b34f --- /dev/null +++ b/packages/block-library/src/site-title/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import icon from './icon'; +import edit from './edit'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + title: __( 'Site Title' ), + icon, + edit, +}; diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index a78142b3360f9a..0541e240a7f6c5 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -12,6 +12,7 @@ import { apiFetch, select } from './controls'; export const DEFAULT_ENTITY_KEY = 'id'; export const defaultEntities = [ + { name: 'site', kind: 'root', baseURL: '/wp/v2/settings' }, { name: 'postType', kind: 'root', key: 'slug', baseURL: '/wp/v2/types' }, { name: 'media', kind: 'root', baseURL: '/wp/v2/media', plural: 'mediaItems' }, { name: 'taxonomy', kind: 'root', key: 'slug', baseURL: '/wp/v2/taxonomies', plural: 'taxonomies' }, diff --git a/packages/core-data/src/entity-provider.js b/packages/core-data/src/entity-provider.js index f61722a467ac4d..bbb14563a58085 100644 --- a/packages/core-data/src/entity-provider.js +++ b/packages/core-data/src/entity-provider.js @@ -51,9 +51,6 @@ export function useEntityProp( type, prop ) { const value = useSelect( ( select ) => { - if ( ! id ) { - return; - } const entity = select( 'core' ).getEditedEntityRecord( kind, type, id ); return entity && entity[ prop ]; }, @@ -63,9 +60,6 @@ export function useEntityProp( type, prop ) { const { editEntityRecord } = useDispatch( 'core' ); const setValue = useCallback( ( newValue ) => { - if ( ! id ) { - return; - } editEntityRecord( kind, type, id, { [ prop ]: newValue, } ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 8edfdbf895cded..670cb4adf2a750 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -47,7 +47,7 @@ export function* getCurrentUser() { * @param {string} name Entity name. * @param {number} key Record's key */ -export function* getEntityRecord( kind, name, key ) { +export function* getEntityRecord( kind, name, key = '' ) { const entities = yield getKindEntities( kind ); const entity = find( entities, { kind, name } ); if ( ! entity ) {