diff --git a/core-blocks/block/edit-panel/index.js b/core-blocks/block/edit-panel/index.js
index 88b733d0ce63df..5d494d5a9ec550 100644
--- a/core-blocks/block/edit-panel/index.js
+++ b/core-blocks/block/edit-panel/index.js
@@ -1,10 +1,16 @@
+/**
+ * External dependencies
+ */
+import { over, compact } from 'lodash';
+
/**
* WordPress dependencies
*/
import { Button, withInstanceId } from '@wordpress/components';
-import { Component, Fragment, createRef } from '@wordpress/element';
+import { Component, Fragment, createRef, compose } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { keycodes } from '@wordpress/utils';
+import { withSelect, withDispatch } from '@wordpress/data';
/**
* Internal dependencies
@@ -117,4 +123,33 @@ class SharedBlockEditPanel extends Component {
}
}
-export default withInstanceId( SharedBlockEditPanel );
+export default compose( [
+ withInstanceId,
+ withSelect( ( select ) => {
+ const { getEditedPostAttribute } = select( 'core/editor' );
+
+ return {
+ title: getEditedPostAttribute( 'title' ),
+ };
+ } ),
+ withDispatch( ( dispatch, ownProps ) => {
+ const {
+ editPost,
+ undoAll,
+ savePost,
+ clearSelectedBlock,
+ } = dispatch( 'core/editor' );
+
+ const withClearAndFinish = ( fn ) => over( compact( [
+ clearSelectedBlock,
+ ownProps.onFinishedEditing,
+ fn,
+ ] ) );
+
+ return {
+ onChangeTitle: ( title ) => editPost( { title } ),
+ onSave: withClearAndFinish( savePost ),
+ onCancel: withClearAndFinish( undoAll ),
+ };
+ } ),
+] )( SharedBlockEditPanel );
diff --git a/core-blocks/block/edit.js b/core-blocks/block/edit.js
index deb56593f1827f..95ec22b361b684 100644
--- a/core-blocks/block/edit.js
+++ b/core-blocks/block/edit.js
@@ -1,169 +1,111 @@
-/**
- * External dependencies
- */
-import { noop, partial } from 'lodash';
-
/**
* WordPress dependencies
*/
-import { Component, Fragment, compose } from '@wordpress/element';
+import { Component, compose } from '@wordpress/element';
import { Placeholder, Spinner, Disabled } from '@wordpress/components';
import { withSelect, withDispatch } from '@wordpress/data';
-import { __ } from '@wordpress/i18n';
-import { BlockEdit } from '@wordpress/editor';
+import { EditorProvider, BlockList } from '@wordpress/editor';
+import isShallowEqual from '@wordpress/is-shallow-equal';
/**
* Internal dependencies
*/
import SharedBlockEditPanel from './edit-panel';
import SharedBlockIndicator from './indicator';
+import SharedBlockSelection from './selection';
class SharedBlockEdit extends Component {
- constructor( { sharedBlock } ) {
+ constructor( props ) {
super( ...arguments );
- this.startEditing = this.startEditing.bind( this );
- this.stopEditing = this.stopEditing.bind( this );
- this.setAttributes = this.setAttributes.bind( this );
- this.setTitle = this.setTitle.bind( this );
- this.save = this.save.bind( this );
+ this.startEditing = this.toggleEditing.bind( this, true );
+ this.stopEditing = this.toggleEditing.bind( this, false );
+ const { sharedBlock, settings } = props;
this.state = {
isEditing: !! ( sharedBlock && sharedBlock.isTemporary ),
- title: null,
- changedAttributes: null,
+ settingsWithLock: { ...settings, templateLock: true },
};
}
- componentDidMount() {
- if ( ! this.props.sharedBlock ) {
- this.props.fetchSharedBlock();
+ static getDerivedStateFromProps( props, prevState ) {
+ if ( isShallowEqual( props.settings, prevState.settings ) ) {
+ return null;
}
- }
-
- startEditing() {
- const { sharedBlock } = this.props;
-
- this.setState( {
- isEditing: true,
- title: sharedBlock.title,
- changedAttributes: {},
- } );
- }
-
- stopEditing() {
- this.setState( {
- isEditing: false,
- title: null,
- changedAttributes: null,
- } );
- }
- setAttributes( attributes ) {
- this.setState( ( prevState ) => {
- if ( prevState.changedAttributes !== null ) {
- return { changedAttributes: { ...prevState.changedAttributes, ...attributes } };
- }
- } );
- }
-
- setTitle( title ) {
- this.setState( { title } );
+ return {
+ settings: props.settings,
+ settingsWithLock: {
+ ...props.settings,
+ templateLock: true,
+ },
+ };
}
- save() {
- const { sharedBlock, onUpdateTitle, updateAttributes, block, onSave } = this.props;
- const { title, changedAttributes } = this.state;
-
- if ( title !== sharedBlock.title ) {
- onUpdateTitle( title );
- }
-
- updateAttributes( block.uid, changedAttributes );
- onSave();
-
- this.stopEditing();
+ toggleEditing( isEditing ) {
+ this.setState( { isEditing } );
}
render() {
- const { isSelected, sharedBlock, block, isFetching, isSaving } = this.props;
- const { isEditing, title, changedAttributes } = this.state;
+ const { setIsSelected, sharedBlock, isSelected, isSaving } = this.props;
+ const { settingsWithLock, isEditing } = this.state;
- if ( ! sharedBlock && isFetching ) {
+ if ( ! sharedBlock ) {
return ;
}
- if ( ! sharedBlock || ! block ) {
- return { __( 'Block has been deleted or is unavailable.' ) };
- }
-
- let element = (
-
- );
-
+ let list = ;
if ( ! isEditing ) {
- element = { element };
+ list = { list };
}
return (
-
- { element }
- { ( isSelected || isEditing ) && (
-
- ) }
- { ! isSelected && ! isEditing && }
-
+
+
+ { list }
+ { ( isSelected || isEditing ) && (
+
+ ) }
+ { ! isSelected && ! isEditing && }
+
+
);
}
}
export default compose( [
withSelect( ( select, ownProps ) => {
- const {
- getSharedBlock,
- isFetchingSharedBlock,
- isSavingSharedBlock,
- getBlock,
- } = select( 'core/editor' );
const { ref } = ownProps.attributes;
- const sharedBlock = getSharedBlock( ref );
+ if ( ! Number.isFinite( ref ) ) {
+ return;
+ }
+ const { getEntityRecord } = select( 'core' );
return {
- sharedBlock,
- isFetching: isFetchingSharedBlock( ref ),
- isSaving: isSavingSharedBlock( ref ),
- block: sharedBlock ? getBlock( sharedBlock.uid ) : null,
+ sharedBlock: getEntityRecord( 'postType', 'wp_block', ref ),
+ settings: select( 'core/editor' ).getEditorSettings(),
};
} ),
withDispatch( ( dispatch, ownProps ) => {
- const {
- fetchSharedBlocks,
- updateBlockAttributes,
- updateSharedBlockTitle,
- saveSharedBlock,
- } = dispatch( 'core/editor' );
- const { ref } = ownProps.attributes;
+ const { selectBlock } = dispatch( 'core/editor' );
+ const { id } = ownProps;
return {
- fetchSharedBlock: partial( fetchSharedBlocks, ref ),
- updateAttributes: updateBlockAttributes,
- onUpdateTitle: partial( updateSharedBlockTitle, ref ),
- onSave: partial( saveSharedBlock, ref ),
+ setIsSelected: () => selectBlock( id ),
};
} ),
] )( SharedBlockEdit );
diff --git a/core-blocks/block/indicator/index.js b/core-blocks/block/indicator/index.js
index 403ed6d563f86d..4dbd3b2b4eb50d 100644
--- a/core-blocks/block/indicator/index.js
+++ b/core-blocks/block/indicator/index.js
@@ -3,6 +3,7 @@
*/
import { Tooltip, Dashicon } from '@wordpress/components';
import { __, sprintf } from '@wordpress/i18n';
+import { withSelect } from '@wordpress/data';
/**
* Internal dependencies
@@ -21,4 +22,10 @@ function SharedBlockIndicator( { title } ) {
);
}
-export default SharedBlockIndicator;
+export default withSelect( ( select ) => {
+ const { getEditedPostAttribute } = select( 'core/editor' );
+
+ return {
+ title: getEditedPostAttribute( 'title' ),
+ };
+} )( SharedBlockIndicator );
diff --git a/core-blocks/block/selection.js b/core-blocks/block/selection.js
new file mode 100644
index 00000000000000..ff65d2816ddf78
--- /dev/null
+++ b/core-blocks/block/selection.js
@@ -0,0 +1,42 @@
+/**
+ * WordPress dependencies
+ */
+import { Component, compose } from '@wordpress/element';
+import { withSelect, withDispatch } from '@wordpress/data';
+
+class SharedBlockSelection extends Component {
+ componentDidUpdate( prevProps ) {
+ const {
+ isSharedBlockSelected,
+ hasSelection,
+ clearSelectedBlock,
+ onBlockSelection,
+ } = this.props;
+
+ if ( ! isSharedBlockSelected && prevProps.isSharedBlockSelected ) {
+ clearSelectedBlock();
+ }
+
+ if ( hasSelection && ! prevProps.hasSelection ) {
+ onBlockSelection();
+ }
+ }
+
+ render() {
+ return this.props.children;
+ }
+}
+
+export default compose( [
+ withSelect( ( select ) => {
+ const { getBlockSelectionStart } = select( 'core/editor' );
+
+ return {
+ hasSelection: !! getBlockSelectionStart(),
+ };
+ } ),
+ withDispatch( ( dispatch ) => {
+ const { clearSelectedBlock } = dispatch( 'core/editor' );
+ return { clearSelectedBlock };
+ } ),
+] )( SharedBlockSelection );
diff --git a/edit-post/editor.js b/edit-post/editor.js
index 42ff1f5da83031..f67f6db37f8477 100644
--- a/edit-post/editor.js
+++ b/edit-post/editor.js
@@ -9,6 +9,9 @@ import { StrictMode } from '@wordpress/element';
* Internal dependencies
*/
import Layout from './components/layout';
+import store from './store';
+
+const initializeStore = () => store.dispatch( { type: 'INIT' } );
function Editor( { settings, hasFixedToolbar, post, overridePost, onError, ...props } ) {
if ( ! post ) {
@@ -22,7 +25,11 @@ function Editor( { settings, hasFixedToolbar, post, overridePost, onError, ...pr
return (
-
+
diff --git a/edit-post/store/index.js b/edit-post/store/index.js
index d275e8ed209c87..25c97579f2b56d 100644
--- a/edit-post/store/index.js
+++ b/edit-post/store/index.js
@@ -28,6 +28,5 @@ const store = registerStore( 'core/edit-post', {
applyMiddlewares( store );
loadAndPersist( store, reducer, 'preferences', STORAGE_KEY );
-store.dispatch( { type: 'INIT' } );
export default store;
diff --git a/editor/components/block-settings-menu/shared-block-settings.js b/editor/components/block-settings-menu/shared-block-settings.js
index f95d8dc9af8477..983939b2250106 100644
--- a/editor/components/block-settings-menu/shared-block-settings.js
+++ b/editor/components/block-settings-menu/shared-block-settings.js
@@ -12,10 +12,10 @@ import { __ } from '@wordpress/i18n';
import { isSharedBlock } from '@wordpress/blocks';
import { withSelect, withDispatch } from '@wordpress/data';
-export function SharedBlockSettings( { sharedBlock, onConvertToStatic, onConvertToShared, onDelete, itemsRole } ) {
+export function SharedBlockSettings( { isStaticBlock, onConvertToStatic, onConvertToShared, onDelete, itemsRole } ) {
return (
- { ! sharedBlock && (
+ { isStaticBlock && (
) }
- { sharedBlock && (
+ { ! isStaticBlock && (
onDelete( sharedBlock.id ) }
+ onClick={ onDelete }
role={ itemsRole }
>
{ __( 'Delete Shared Block' ) }
@@ -52,18 +51,20 @@ export function SharedBlockSettings( { sharedBlock, onConvertToStatic, onConvert
export default compose( [
withSelect( ( select, { uid } ) => {
- const { getBlock, getSharedBlock } = select( 'core/editor' );
+ const { getBlock } = select( 'core/editor' );
const block = getBlock( uid );
return {
- sharedBlock: block && isSharedBlock( block ) ? getSharedBlock( block.attributes.ref ) : null,
+ sharedBlockId: block.attributes.ref,
+ isStaticBlock: ! block || ! isSharedBlock( block ),
};
} ),
- withDispatch( ( dispatch, { uid, onToggle = noop } ) => {
+ withDispatch( ( dispatch, ownProps ) => {
const {
convertBlockToShared,
convertBlockToStatic,
deleteSharedBlock,
} = dispatch( 'core/editor' );
+ const { uid, onToggle = noop, sharedBlockId } = ownProps;
return {
onConvertToStatic() {
@@ -74,7 +75,7 @@ export default compose( [
convertBlockToShared( uid );
onToggle();
},
- onDelete( id ) {
+ onDelete() {
// TODO: Make this a component or similar
// eslint-disable-next-line no-alert
const hasConfirmed = window.confirm( __(
@@ -83,7 +84,7 @@ export default compose( [
) );
if ( hasConfirmed ) {
- deleteSharedBlock( id );
+ deleteSharedBlock( sharedBlockId );
onToggle();
}
},
diff --git a/editor/components/provider/index.js b/editor/components/provider/index.js
index 788c29ea4df4f8..cdd9c19a338a41 100644
--- a/editor/components/provider/index.js
+++ b/editor/components/provider/index.js
@@ -2,17 +2,19 @@
* External dependencies
*/
import { flow, pick } from 'lodash';
+import memoize from 'memize';
/**
* WordPress Dependencies
*/
-import { createElement, Component } from '@wordpress/element';
+import { createElement, Component, compose } from '@wordpress/element';
import {
APIProvider,
DropZoneProvider,
SlotFillProvider,
} from '@wordpress/components';
-import { withDispatch } from '@wordpress/data';
+import { withDispatch, withCustomReducerKey } from '@wordpress/data';
+import createEditorStore from '../../store';
/**
* Internal dependencies
@@ -42,7 +44,13 @@ class EditorProvider extends Component {
undo,
redo,
createUndoLevel,
+ inheritContext,
} = this.props;
+
+ if ( inheritContext ) {
+ return children;
+ }
+
const providers = [
// RichText provider:
//
@@ -99,19 +107,47 @@ class EditorProvider extends Component {
}
}
-export default withDispatch( ( dispatch ) => {
- const {
- setupEditor,
- updateEditorSettings,
- undo,
- redo,
- createUndoLevel,
- } = dispatch( 'core/editor' );
- return {
- setupEditor,
- undo,
- redo,
- createUndoLevel,
- updateEditorSettings,
- };
-} )( EditorProvider );
+const createStoreOnce = memoize( ( reducerKey ) => createEditorStore( reducerKey ) );
+
+export default compose( [
+ ( WrappedComponent ) => class extends Component {
+ constructor( props ) {
+ super( ...arguments );
+
+ createStoreOnce( props.reducerKey );
+ }
+
+ componentDidMount() {
+ if ( this.props.onStoreCreated ) {
+ this.props.onStoreCreated();
+ }
+ }
+
+ render() {
+ return ;
+ }
+ },
+ withCustomReducerKey( ( reducerKey, ownProps ) => {
+ if ( reducerKey === 'core/editor' && ownProps.reducerKey ) {
+ return ownProps.reducerKey;
+ }
+
+ return reducerKey;
+ } ),
+ withDispatch( ( dispatch ) => {
+ const {
+ setupEditor,
+ updateEditorSettings,
+ undo,
+ redo,
+ createUndoLevel,
+ } = dispatch( 'core/editor' );
+ return {
+ setupEditor,
+ undo,
+ redo,
+ createUndoLevel,
+ updateEditorSettings,
+ };
+ } ),
+] )( EditorProvider );
diff --git a/editor/index.js b/editor/index.js
index 170617fc80f23d..f980261f984cb8 100644
--- a/editor/index.js
+++ b/editor/index.js
@@ -1,4 +1,3 @@
-import './store';
import './hooks';
export * from './components';
diff --git a/editor/store/actions.js b/editor/store/actions.js
index 278a76e1c0e5f7..54dda941bbad8f 100644
--- a/editor/store/actions.js
+++ b/editor/store/actions.js
@@ -108,22 +108,6 @@ export function resetBlocks( blocks ) {
};
}
-/**
- * Returns an action object used in signalling that blocks have been received.
- * Unlike resetBlocks, these should be appended to the existing known set, not
- * replacing.
- *
- * @param {Object[]} blocks Array of block objects.
- *
- * @return {Object} Action object.
- */
-export function receiveBlocks( blocks ) {
- return {
- type: 'RECEIVE_BLOCKS',
- blocks,
- };
-}
-
/**
* Returns an action object used in signalling that the block attributes with
* the specified UID has been updated.
@@ -455,6 +439,10 @@ export function undo() {
return { type: 'UNDO' };
}
+export function undoAll() {
+ return { type: 'UNDO_ALL' };
+}
+
/**
* Returns an action object used in signalling that undo history record should
* be created.
@@ -580,18 +568,13 @@ export const createErrorNotice = partial( createNotice, 'error' );
export const createWarningNotice = partial( createNotice, 'warning' );
/**
- * Returns an action object used to fetch a single shared block or all shared
- * blocks from the REST API into the store.
- *
- * @param {?string} id If given, only a single shared block with this ID will
- * be fetched.
+ * Returns an action object used to fetch all shared blocks from the REST API.
*
* @return {Object} Action object.
*/
-export function fetchSharedBlocks( id ) {
+export function fetchSharedBlocks() {
return {
type: 'FETCH_SHARED_BLOCKS',
- id,
};
}
@@ -613,17 +596,18 @@ export function receiveSharedBlocks( results ) {
}
/**
- * Returns an action object used to save a shared block that's in the store to
- * the REST API.
+ * Returns an action object used to create a new shared block.
*
- * @param {Object} id The ID of the shared block to save.
+ * @param {Object} sharedBlock Temporary shared block to be updated once saved.
+ * @param {string} content Content of shared block.
*
* @return {Object} Action object.
*/
-export function saveSharedBlock( id ) {
+export function saveSharedBlock( sharedBlock, content ) {
return {
type: 'SAVE_SHARED_BLOCK',
- id,
+ sharedBlock,
+ content,
};
}
@@ -641,23 +625,6 @@ export function deleteSharedBlock( id ) {
};
}
-/**
- * Returns an action object used in signalling that a shared block's title is
- * to be updated.
- *
- * @param {number} id The ID of the shared block to update.
- * @param {string} title The new title.
- *
- * @return {Object} Action object.
- */
-export function updateSharedBlockTitle( id, title ) {
- return {
- type: 'UPDATE_SHARED_BLOCK_TITLE',
- id,
- title,
- };
-}
-
/**
* Returns an action object used to convert a shared block into a static block.
*
diff --git a/editor/store/effects.js b/editor/store/effects.js
index 75da4df7821d25..8cb4b07863acf3 100644
--- a/editor/store/effects.js
+++ b/editor/store/effects.js
@@ -13,7 +13,6 @@ import {
switchToBlockType,
createBlock,
serialize,
- isSharedBlock,
getDefaultBlockForPostFormat,
doBlocksMatchTemplate,
synchronizeBlocksWithTemplate,
@@ -21,6 +20,7 @@ import {
import { __ } from '@wordpress/i18n';
import { speak } from '@wordpress/a11y';
import apiRequest from '@wordpress/api-request';
+import { select } from '@wordpress/data';
/**
* Internal dependencies
@@ -30,7 +30,6 @@ import {
resetAutosave,
resetPost,
updatePost,
- receiveBlocks,
receiveSharedBlocks,
replaceBlock,
replaceBlocks,
@@ -40,7 +39,6 @@ import {
removeNotice,
saveSharedBlock,
insertBlock,
- removeBlocks,
selectBlock,
removeBlock,
resetBlocks,
@@ -57,7 +55,6 @@ import {
getBlockCount,
getBlockRootUID,
getBlocks,
- getSharedBlock,
getPreviousBlockUid,
getProvisionalBlockUID,
getSelectedBlock,
@@ -476,35 +473,25 @@ export default {
return;
}
- const { id } = action;
const { dispatch } = store;
- let result;
- if ( id ) {
- result = apiRequest( { path: `/wp/v2/${ basePath }/${ id }` } );
- } else {
- result = apiRequest( { path: `/wp/v2/${ basePath }?per_page=-1` } );
- }
-
- result.then(
+ apiRequest( { path: `/wp/v2/${ basePath }?context=edit&per_page=-1` } ).then(
( sharedBlockOrBlocks ) => {
dispatch( receiveSharedBlocks( map(
castArray( sharedBlockOrBlocks ),
( sharedBlock ) => ( {
sharedBlock,
- parsedBlock: parse( sharedBlock.content )[ 0 ],
+ parsedBlock: parse( sharedBlock.content.raw )[ 0 ],
} )
) ) );
dispatch( {
type: 'FETCH_SHARED_BLOCKS_SUCCESS',
- id,
} );
},
( error ) => {
dispatch( {
type: 'FETCH_SHARED_BLOCKS_FAILURE',
- id,
error: error.responseJSON || {
code: 'unknown_error',
message: __( 'An unknown error occurred.' ),
@@ -513,9 +500,6 @@ export default {
}
);
},
- RECEIVE_SHARED_BLOCKS( action ) {
- return receiveBlocks( map( action.results, 'parsedBlock' ) );
- },
SAVE_SHARED_BLOCK( action, store ) {
// TODO: these are potentially undefined, this fix is in place
// until there is a filter to not use shared blocks if undefined
@@ -524,17 +508,13 @@ export default {
return;
}
- const { id } = action;
+ const { sharedBlock, content } = action;
+ const { id, title } = sharedBlock;
const { dispatch } = store;
- const state = store.getState();
- const { uid, title, isTemporary } = getSharedBlock( state, id );
- const { name, attributes, innerBlocks } = getBlock( state, uid );
- const content = serialize( createBlock( name, attributes, innerBlocks ) );
-
- const data = isTemporary ? { title, content } : { id, title, content };
- const path = isTemporary ? `/wp/v2/${ basePath }` : `/wp/v2/${ basePath }/${ id }`;
- const method = isTemporary ? 'POST' : 'PUT';
+ const data = { title, content };
+ const path = `/wp/v2/${ basePath }`;
+ const method = 'POST';
apiRequest( { path, data, method } ).then(
( updatedSharedBlock ) => {
@@ -543,7 +523,7 @@ export default {
updatedId: updatedSharedBlock.id,
id,
} );
- const message = isTemporary ? __( 'Block created.' ) : __( 'Block updated.' );
+ const message = __( 'Block created.' );
dispatch( createSuccessNotice( message, { id: SHARED_BLOCK_NOTICE_ID } ) );
},
( error ) => {
@@ -565,18 +545,7 @@ export default {
}
const { id } = action;
- const { getState, dispatch } = store;
-
- // Don't allow a shared block with a temporary ID to be deleted
- const sharedBlock = getSharedBlock( getState(), id );
- if ( ! sharedBlock || sharedBlock.isTemporary ) {
- return;
- }
-
- // Remove any other blocks that reference this shared block
- const allBlocks = getBlocks( getState() );
- const associatedBlocks = allBlocks.filter( ( block ) => isSharedBlock( block ) && block.attributes.ref === id );
- const associatedBlockUids = associatedBlocks.map( ( block ) => block.uid );
+ const { dispatch } = store;
const transactionId = uniqueId();
@@ -586,13 +555,7 @@ export default {
optimist: { type: BEGIN, id: transactionId },
} );
- // Remove the parsed block.
- dispatch( removeBlocks( [
- ...associatedBlockUids,
- sharedBlock.uid,
- ] ) );
-
- apiRequest( { path: `/wp/v2/${ basePath }/${ id }`, method: 'DELETE' } ).then(
+ apiRequest( { path: `/wp/v2/${ basePath }/${ id }?force=true`, method: 'DELETE' } ).then(
() => {
dispatch( {
type: 'DELETE_SHARED_BLOCK_SUCCESS',
@@ -619,8 +582,8 @@ export default {
CONVERT_BLOCK_TO_STATIC( action, store ) {
const state = store.getState();
const oldBlock = getBlock( state, action.uid );
- const sharedBlock = getSharedBlock( state, oldBlock.attributes.ref );
- const referencedBlock = getBlock( state, sharedBlock.uid );
+ const reducerKey = 'core/editor-shared-' + oldBlock.attributes.ref;
+ const referencedBlock = select( reducerKey ).getBlocks()[ 0 ];
const newBlock = createBlock( referencedBlock.name, referencedBlock.attributes );
store.dispatch( replaceBlock( oldBlock.uid, newBlock ) );
},
@@ -628,6 +591,7 @@ export default {
const { getState, dispatch } = store;
const parsedBlock = getBlock( getState(), action.uid );
+ const content = serialize( parsedBlock );
const sharedBlock = {
id: uniqueId( 'shared' ),
uid: parsedBlock.uid,
@@ -639,7 +603,7 @@ export default {
parsedBlock,
} ] ) );
- dispatch( saveSharedBlock( sharedBlock.id ) );
+ dispatch( saveSharedBlock( sharedBlock, content ) );
dispatch( replaceBlock(
parsedBlock.uid,
@@ -648,9 +612,6 @@ export default {
layout: parsedBlock.attributes.layout,
} )
) );
-
- // Re-add the original block to the store, since replaceBlock() will have removed it
- dispatch( receiveBlocks( [ parsedBlock ] ) );
},
CREATE_NOTICE( { notice: { content, spokenMessage } } ) {
const message = spokenMessage || content;
diff --git a/editor/store/index.js b/editor/store/index.js
index a602694cf59395..9dbe56169bffd3 100644
--- a/editor/store/index.js
+++ b/editor/store/index.js
@@ -21,14 +21,18 @@ import * as actions from './actions';
* Module Constants
*/
const STORAGE_KEY = `GUTENBERG_PREFERENCES_${ window.userSettings.uid }`;
-const MODULE_KEY = 'core/editor';
+const REDUCER_KEY = 'core/editor';
-const store = applyMiddlewares(
- registerReducer( MODULE_KEY, withRehydration( reducer, 'preferences', STORAGE_KEY ) )
-);
-loadAndPersist( store, reducer, 'preferences', STORAGE_KEY );
+function createEditorStore( reducerKey = REDUCER_KEY ) {
+ const store = applyMiddlewares(
+ registerReducer( reducerKey, withRehydration( reducer, 'preferences', STORAGE_KEY ) )
+ );
+ loadAndPersist( store, reducer, 'preferences', STORAGE_KEY );
-registerSelectors( MODULE_KEY, selectors );
-registerActions( MODULE_KEY, actions );
+ registerSelectors( reducerKey, selectors );
+ registerActions( reducerKey, actions );
-export default store;
+ return store;
+}
+
+export default createEditorStore;
diff --git a/editor/store/reducer.js b/editor/store/reducer.js
index fdfdfa2476ed18..c57393fa243c57 100644
--- a/editor/store/reducer.js
+++ b/editor/store/reducer.js
@@ -217,7 +217,7 @@ export const editor = flow( [
// Track undo history, starting at editor initialization.
withHistory( {
resetTypes: [ 'SETUP_EDITOR_STATE' ],
- ignoreTypes: [ 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST' ],
+ ignoreTypes: [ 'RESET_POST', 'UPDATE_POST' ],
shouldOverwriteState,
} ),
@@ -225,7 +225,7 @@ export const editor = flow( [
// editor initialization firing post reset as an effect.
withChangeDetection( {
resetTypes: [ 'SETUP_EDITOR_STATE', 'REQUEST_POST_UPDATE_START' ],
- ignoreTypes: [ 'RECEIVE_BLOCKS', 'RESET_POST', 'UPDATE_POST' ],
+ ignoreTypes: [ 'RESET_POST', 'UPDATE_POST' ],
} ),
] )( {
edits( state = {}, action ) {
@@ -286,12 +286,6 @@ export const editor = flow( [
case 'SETUP_EDITOR_STATE':
return getFlattenedBlocks( action.blocks );
- case 'RECEIVE_BLOCKS':
- return {
- ...state,
- ...getFlattenedBlocks( action.blocks ),
- };
-
case 'UPDATE_BLOCK_ATTRIBUTES':
// Ignore updates if block isn't known
if ( ! state[ action.uid ] ) {
@@ -410,12 +404,6 @@ export const editor = flow( [
case 'SETUP_EDITOR_STATE':
return mapBlockOrder( action.blocks );
- case 'RECEIVE_BLOCKS':
- return {
- ...state,
- ...omit( mapBlockOrder( action.blocks ), '' ),
- };
-
case 'INSERT_BLOCKS': {
const { rootUID = '', blocks } = action;
const subState = state[ rootUID ] || [];
@@ -924,10 +912,11 @@ export const sharedBlocks = combineReducers( {
switch ( action.type ) {
case 'RECEIVE_SHARED_BLOCKS': {
return reduce( action.results, ( nextState, result ) => {
- const { id, title } = result.sharedBlock;
- const { uid } = result.parsedBlock;
+ const { id } = result.sharedBlock;
+ const title = getPostRawValue( result.sharedBlock.title );
+ const { name: blockName } = result.parsedBlock;
- const value = { uid, title };
+ const value = { blockName, title };
if ( ! isEqual( nextState[ id ], value ) ) {
if ( nextState === state ) {
@@ -941,22 +930,6 @@ export const sharedBlocks = combineReducers( {
}, state );
}
- case 'UPDATE_SHARED_BLOCK_TITLE': {
- const { id, title } = action;
-
- if ( ! state[ id ] || state[ id ].title === title ) {
- return state;
- }
-
- return {
- ...state,
- [ id ]: {
- ...state[ id ],
- title,
- },
- };
- }
-
case 'SAVE_SHARED_BLOCK_SUCCESS': {
const { id, updatedId } = action;
@@ -980,48 +953,6 @@ export const sharedBlocks = combineReducers( {
return state;
},
-
- isFetching( state = {}, action ) {
- switch ( action.type ) {
- case 'FETCH_SHARED_BLOCKS': {
- const { id } = action;
- if ( ! id ) {
- return state;
- }
-
- return {
- ...state,
- [ id ]: true,
- };
- }
-
- case 'FETCH_SHARED_BLOCKS_SUCCESS':
- case 'FETCH_SHARED_BLOCKS_FAILURE': {
- const { id } = action;
- return omit( state, id );
- }
- }
-
- return state;
- },
-
- isSaving( state = {}, action ) {
- switch ( action.type ) {
- case 'SAVE_SHARED_BLOCK':
- return {
- ...state,
- [ action.id ]: true,
- };
-
- case 'SAVE_SHARED_BLOCK_SUCCESS':
- case 'SAVE_SHARED_BLOCK_FAILURE': {
- const { id } = action;
- return omit( state, id );
- }
- }
-
- return state;
- },
} );
/**
diff --git a/editor/store/selectors.js b/editor/store/selectors.js
index 4928ec226ba16e..526f9ea912a88a 100644
--- a/editor/store/selectors.js
+++ b/editor/store/selectors.js
@@ -1520,12 +1520,7 @@ export const getInserterItems = createSelector(
return false;
}
- const referencedBlock = getBlock( state, sharedBlock.uid );
- if ( ! referencedBlock ) {
- return false;
- }
-
- const referencedBlockType = getBlockType( referencedBlock.name );
+ const referencedBlockType = getBlockType( sharedBlock.blockName );
if ( ! referencedBlockType ) {
return false;
}
@@ -1540,8 +1535,7 @@ export const getInserterItems = createSelector(
const buildSharedBlockInserterItem = ( sharedBlock ) => {
const id = `core/block/${ sharedBlock.id }`;
- const referencedBlock = getBlock( state, sharedBlock.uid );
- const referencedBlockType = getBlockType( referencedBlock.name );
+ const referencedBlockType = getBlockType( sharedBlock.blockName );
const { time, count = 0 } = getInsertUsage( state, id ) || {};
const utility = calculateUtility( 'shared', count, false );
diff --git a/editor/store/test/actions.js b/editor/store/test/actions.js
index 401250bfeff59c..1bbff1c58be464 100644
--- a/editor/store/test/actions.js
+++ b/editor/store/test/actions.js
@@ -6,7 +6,6 @@ import {
startTyping,
stopTyping,
fetchSharedBlocks,
- saveSharedBlock,
deleteSharedBlock,
convertBlockToStatic,
convertBlockToShared,
@@ -466,22 +465,6 @@ describe( 'actions', () => {
type: 'FETCH_SHARED_BLOCKS',
} );
} );
-
- it( 'should take an optional id argument', () => {
- expect( fetchSharedBlocks( 123 ) ).toEqual( {
- type: 'FETCH_SHARED_BLOCKS',
- id: 123,
- } );
- } );
- } );
-
- describe( 'saveSharedBlock', () => {
- it( 'should return the SAVE_SHARED_BLOCK action', () => {
- expect( saveSharedBlock( 123 ) ).toEqual( {
- type: 'SAVE_SHARED_BLOCK',
- id: 123,
- } );
- } );
} );
describe( 'deleteSharedBlock', () => {
diff --git a/editor/store/test/effects.js b/editor/store/test/effects.js
index 8e135cb7c566b8..b43c7756b2335d 100644
--- a/editor/store/test/effects.js
+++ b/editor/store/test/effects.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { noop, set, reduce } from 'lodash';
+import { noop, set } from 'lodash';
/**
* WordPress dependencies
@@ -26,13 +26,6 @@ import {
createErrorNotice,
fetchSharedBlocks,
receiveSharedBlocks,
- receiveBlocks,
- saveSharedBlock,
- deleteSharedBlock,
- removeBlocks,
- resetBlocks,
- convertBlockToStatic,
- convertBlockToShared,
setTemplateValidity,
editPost,
} from '../actions';
@@ -596,8 +589,12 @@ describe( 'effects', () => {
const promise = Promise.resolve( [
{
id: 123,
- title: 'My cool block',
- content: '',
+ title: {
+ raw: 'My cool block',
+ },
+ content: {
+ raw: '',
+ },
},
] );
apiRequest.mockReturnValue = promise;
@@ -614,8 +611,12 @@ describe( 'effects', () => {
{
sharedBlock: {
id: 123,
- title: 'My cool block',
- content: '',
+ title: {
+ raw: 'My cool block',
+ },
+ content: {
+ raw: '',
+ },
},
parsedBlock: expect.objectContaining( {
name: 'core/test-block',
@@ -626,44 +627,6 @@ describe( 'effects', () => {
);
expect( dispatch ).toHaveBeenCalledWith( {
type: 'FETCH_SHARED_BLOCKS_SUCCESS',
- id: undefined,
- } );
- } );
- } );
-
- it( 'should fetch a single shared block', () => {
- const promise = Promise.resolve( {
- id: 123,
- title: 'My cool block',
- content: '',
- } );
- apiRequest.mockReturnValue = promise;
- set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' );
-
- const dispatch = jest.fn();
- const store = { getState: noop, dispatch };
-
- handler( fetchSharedBlocks( 123 ), store );
-
- return promise.then( () => {
- expect( dispatch ).toHaveBeenCalledWith(
- receiveSharedBlocks( [
- {
- sharedBlock: {
- id: 123,
- title: 'My cool block',
- content: '',
- },
- parsedBlock: expect.objectContaining( {
- name: 'core/test-block',
- attributes: { name: 'Big Bird' },
- } ),
- },
- ] )
- );
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'FETCH_SHARED_BLOCKS_SUCCESS',
- id: 123,
} );
} );
} );
@@ -689,244 +652,5 @@ describe( 'effects', () => {
} );
} );
} );
-
- describe( '.RECEIVE_SHARED_BLOCKS', () => {
- const handler = effects.RECEIVE_SHARED_BLOCKS;
-
- it( 'should receive parsed blocks', () => {
- const action = receiveSharedBlocks( [
- {
- parsedBlock: { uid: 'broccoli' },
- },
- ] );
-
- expect( handler( action ) ).toEqual( receiveBlocks( [
- { uid: 'broccoli' },
- ] ) );
- } );
- } );
-
- describe( '.SAVE_SHARED_BLOCK', () => {
- const handler = effects.SAVE_SHARED_BLOCK;
-
- it( 'should save a shared block and swap its id', () => {
- const promise = Promise.resolve( { id: 456 } );
- apiRequest.mockReturnValue = promise;
-
- set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' );
-
- const sharedBlock = { id: 123, title: 'My cool block' };
- const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } );
-
- const state = reduce( [
- receiveSharedBlocks( [ { sharedBlock, parsedBlock } ] ),
- receiveBlocks( [ parsedBlock ] ),
- ], reducer, undefined );
-
- const dispatch = jest.fn();
- const store = { getState: () => state, dispatch };
-
- handler( saveSharedBlock( 123 ), store );
-
- return promise.then( () => {
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'SAVE_SHARED_BLOCK_SUCCESS',
- id: 123,
- updatedId: 456,
- } );
- } );
- } );
-
- it( 'should handle an API error', () => {
- const promise = Promise.reject( {} );
- apiRequest.mockReturnValue = promise;
- set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' );
-
- const sharedBlock = { id: 123, title: 'My cool block' };
- const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } );
-
- const state = reduce( [
- receiveSharedBlocks( [ { sharedBlock, parsedBlock } ] ),
- receiveBlocks( [ parsedBlock ] ),
- ], reducer, undefined );
-
- const dispatch = jest.fn();
- const store = { getState: () => state, dispatch };
-
- handler( saveSharedBlock( 123 ), store );
-
- return promise.catch( () => {
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'SAVE_SHARED_BLOCK_FAILURE',
- id: 123,
- } );
- } );
- } );
- } );
-
- describe( '.DELETE_SHARED_BLOCK', () => {
- const handler = effects.DELETE_SHARED_BLOCK;
-
- it( 'should delete a shared block', () => {
- const promise = Promise.resolve( {} );
- apiRequest.mockReturnValue = promise;
- set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' );
-
- const associatedBlock = createBlock( 'core/block', { ref: 123 } );
- const sharedBlock = { id: 123, title: 'My cool block' };
- const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } );
-
- const state = reduce( [
- resetBlocks( [ associatedBlock ] ),
- receiveSharedBlocks( [ { sharedBlock, parsedBlock } ] ),
- receiveBlocks( [ parsedBlock ] ),
- ], reducer, undefined );
-
- const dispatch = jest.fn();
- const store = { getState: () => state, dispatch };
-
- handler( deleteSharedBlock( 123 ), store );
-
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'REMOVE_SHARED_BLOCK',
- id: 123,
- optimist: expect.any( Object ),
- } );
-
- expect( dispatch ).toHaveBeenCalledWith(
- removeBlocks( [ associatedBlock.uid, parsedBlock.uid ] )
- );
-
- return promise.then( () => {
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'DELETE_SHARED_BLOCK_SUCCESS',
- id: 123,
- optimist: expect.any( Object ),
- } );
- } );
- } );
-
- it( 'should handle an API error', () => {
- const promise = Promise.reject( {} );
- apiRequest.mockReturnValue = promise;
- set( global, [ 'wp', 'api', 'getPostTypeRoute' ], () => 'blocks' );
-
- const sharedBlock = { id: 123, title: 'My cool block' };
- const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } );
-
- const state = reduce( [
- receiveSharedBlocks( [ { sharedBlock, parsedBlock } ] ),
- receiveBlocks( [ parsedBlock ] ),
- ], reducer, undefined );
-
- const dispatch = jest.fn();
- const store = { getState: () => state, dispatch };
-
- handler( deleteSharedBlock( 123 ), store );
-
- return promise.catch( () => {
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'DELETE_SHARED_BLOCK_FAILURE',
- id: 123,
- optimist: expect.any( Object ),
- } );
- } );
- } );
-
- it( 'should not save shared blocks with temporary IDs', () => {
- const sharedBlock = { id: 'shared1', title: 'My cool block' };
- const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } );
-
- const state = reduce( [
- receiveSharedBlocks( [ { sharedBlock, parsedBlock } ] ),
- receiveBlocks( [ parsedBlock ] ),
- ], reducer, undefined );
-
- const dispatch = jest.fn();
- const store = { getState: () => state, dispatch };
-
- handler( deleteSharedBlock( 'shared1' ), store );
-
- expect( dispatch ).not.toHaveBeenCalled();
- } );
- } );
-
- describe( '.CONVERT_BLOCK_TO_STATIC', () => {
- const handler = effects.CONVERT_BLOCK_TO_STATIC;
-
- it( 'should convert a shared block into a static block', () => {
- const associatedBlock = createBlock( 'core/block', { ref: 123 } );
- const sharedBlock = { id: 123, title: 'My cool block' };
- const parsedBlock = createBlock( 'core/test-block', { name: 'Big Bird' } );
-
- const state = reduce( [
- resetBlocks( [ associatedBlock ] ),
- receiveSharedBlocks( [ { sharedBlock, parsedBlock } ] ),
- receiveBlocks( [ parsedBlock ] ),
- ], reducer, undefined );
-
- const dispatch = jest.fn();
- const store = { getState: () => state, dispatch };
-
- handler( convertBlockToStatic( associatedBlock.uid ), store );
-
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'REPLACE_BLOCKS',
- uids: [ associatedBlock.uid ],
- blocks: [
- expect.objectContaining( {
- name: 'core/test-block',
- attributes: { name: 'Big Bird' },
- } ),
- ],
- time: expect.any( Number ),
- } );
- } );
- } );
-
- describe( '.CONVERT_BLOCK_TO_SHARED', () => {
- const handler = effects.CONVERT_BLOCK_TO_SHARED;
-
- it( 'should convert a static block into a shared block', () => {
- const staticBlock = createBlock( 'core/block', { ref: 123 } );
- const state = reducer( undefined, resetBlocks( [ staticBlock ] ) );
-
- const dispatch = jest.fn();
- const store = { getState: () => state, dispatch };
-
- handler( convertBlockToShared( staticBlock.uid ), store );
-
- expect( dispatch ).toHaveBeenCalledWith(
- receiveSharedBlocks( [ {
- sharedBlock: {
- id: expect.stringMatching( /^shared/ ),
- uid: staticBlock.uid,
- title: 'Untitled shared block',
- },
- parsedBlock: staticBlock,
- } ] )
- );
-
- expect( dispatch ).toHaveBeenCalledWith(
- saveSharedBlock( expect.stringMatching( /^shared/ ) ),
- );
-
- expect( dispatch ).toHaveBeenCalledWith( {
- type: 'REPLACE_BLOCKS',
- uids: [ staticBlock.uid ],
- blocks: [
- expect.objectContaining( {
- name: 'core/block',
- attributes: { ref: expect.stringMatching( /^shared/ ) },
- } ),
- ],
- time: expect.any( Number ),
- } );
-
- expect( dispatch ).toHaveBeenCalledWith(
- receiveBlocks( [ staticBlock ] ),
- );
- } );
- } );
} );
} );
diff --git a/editor/store/test/reducer.js b/editor/store/test/reducer.js
index b1afa1c7e6e529..e8bcc603d80bd0 100644
--- a/editor/store/test/reducer.js
+++ b/editor/store/test/reducer.js
@@ -1944,30 +1944,6 @@ describe( 'state', () => {
} );
} );
- it( 'should update a shared block', () => {
- const initialState = {
- data: {
- 123: { uid: '', title: '' },
- },
- isFetching: {},
- isSaving: {},
- };
-
- const state = sharedBlocks( initialState, {
- type: 'UPDATE_SHARED_BLOCK_TITLE',
- id: 123,
- title: 'My block',
- } );
-
- expect( state ).toEqual( {
- data: {
- 123: { uid: '', title: 'My block' },
- },
- isFetching: {},
- isSaving: {},
- } );
- } );
-
it( 'should update the shared block\'s id if it was temporary', () => {
const initialState = {
data: {
diff --git a/editor/utils/with-history/index.js b/editor/utils/with-history/index.js
index cc90f0d4b66478..77a68345b59ab3 100644
--- a/editor/utils/with-history/index.js
+++ b/editor/utils/with-history/index.js
@@ -64,6 +64,17 @@ const withHistory = ( options = {} ) => ( reducer ) => {
lastAction = action;
switch ( action.type ) {
+ case 'UNDO_ALL':
+ // Can't undo if no past.
+ if ( ! past.length ) {
+ return state;
+ }
+
+ return {
+ past: [],
+ present: first( past ),
+ future: [],
+ };
case 'UNDO':
// Can't undo if no past.
if ( ! past.length ) {
diff --git a/lib/class-wp-rest-blocks-controller.php b/lib/class-wp-rest-blocks-controller.php
deleted file mode 100644
index 7435c63e56b9e8..00000000000000
--- a/lib/class-wp-rest-blocks-controller.php
+++ /dev/null
@@ -1,123 +0,0 @@
-post_type );
- if ( ! current_user_can( $post_type->cap->read_post, $post->ID ) ) {
- return false;
- }
-
- return parent::check_read_permission( $post );
- }
-
- /**
- * Handle a DELETE request.
- *
- * @since 1.10.0
- *
- * @param WP_REST_Request $request Full details about the request.
- * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
- */
- public function delete_item( $request ) {
- // Always hard-delete a block.
- $request->set_param( 'force', true );
-
- return parent::delete_item( $request );
- }
-
- /**
- * Given an update or create request, build the post object that is saved to
- * the database.
- *
- * @since 1.10.0
- *
- * @param WP_REST_Request $request Request object.
- * @return stdClass|WP_Error Post object or WP_Error.
- */
- public function prepare_item_for_database( $request ) {
- $prepared_post = parent::prepare_item_for_database( $request );
-
- // Force blocks to always be published.
- $prepared_post->post_status = 'publish';
-
- return $prepared_post;
- }
-
- /**
- * Given a block from the database, build the array that is returned from an
- * API response.
- *
- * @since 1.10.0
- *
- * @param WP_Post $post Post object that backs the block.
- * @param WP_REST_Request $request Request object.
- * @return WP_REST_Response Response object.
- */
- public function prepare_item_for_response( $post, $request ) {
- $data = array(
- 'id' => $post->ID,
- 'title' => $post->post_title,
- 'content' => $post->post_content,
- );
-
- $response = rest_ensure_response( $data );
-
- return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request );
- }
-
- /**
- * Builds the block's schema, conforming to JSON Schema.
- *
- * @since 1.10.0
- *
- * @return array Item schema data.
- */
- public function get_item_schema() {
- return array(
- '$schema' => 'http://json-schema.org/schema#',
- 'title' => $this->post_type,
- 'type' => 'object',
- 'properties' => array(
- 'id' => array(
- 'description' => __( 'Unique identifier for the block.', 'gutenberg' ),
- 'type' => 'integer',
- 'readonly' => true,
- ),
- 'title' => array(
- 'description' => __( 'The block\'s title.', 'gutenberg' ),
- 'type' => 'string',
- 'required' => true,
- ),
- 'content' => array(
- 'description' => __( 'The block\'s HTML content.', 'gutenberg' ),
- 'type' => 'string',
- 'required' => true,
- ),
- ),
- );
- }
-}
diff --git a/lib/load.php b/lib/load.php
index f1a8b09c66ce80..143db82a7162de 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -12,7 +12,6 @@
// These files only need to be loaded if within a rest server instance
// which this class will exist if that is the case.
if ( class_exists( 'WP_REST_Controller' ) ) {
- require dirname( __FILE__ ) . '/class-wp-rest-blocks-controller.php';
require dirname( __FILE__ ) . '/class-wp-rest-autosaves-controller.php';
require dirname( __FILE__ ) . '/class-wp-rest-block-renderer-controller.php';
require dirname( __FILE__ ) . '/rest-api.php';
diff --git a/lib/register.php b/lib/register.php
index d5265eb6092f83..4cc722aa6c1a3c 100644
--- a/lib/register.php
+++ b/lib/register.php
@@ -389,7 +389,6 @@ function gutenberg_register_post_types() {
'public' => false,
'show_in_rest' => true,
'rest_base' => 'blocks',
- 'rest_controller_class' => 'WP_REST_Blocks_Controller',
'capability_type' => 'block',
'capabilities' => array(
'read' => 'read_blocks',
diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js
index 7d36a4c65558a5..a97ce9037fcbdd 100644
--- a/packages/core-data/src/selectors.js
+++ b/packages/core-data/src/selectors.js
@@ -142,6 +142,10 @@ export function getEntityRecord( state, kind, name, key ) {
return get( state.entities.data, [ kind, name, 'byKey', key ] );
}
+export function isRequestingEntityRecord( ...args ) {
+ return isResolving( 'getEntityRecord', ...args );
+}
+
/**
* Returns the Entity's records.
*
diff --git a/packages/data/src/index.js b/packages/data/src/index.js
index 14247b8bb601c1..a5cd6ee6423cb3 100644
--- a/packages/data/src/index.js
+++ b/packages/data/src/index.js
@@ -2,7 +2,7 @@
* External dependencies
*/
import { combineReducers, createStore } from 'redux';
-import { flowRight, without, mapValues, overEvery } from 'lodash';
+import { flowRight, without, mapValues, overEvery, identity } from 'lodash';
/**
* WordPress dependencies
@@ -12,6 +12,7 @@ import {
compose,
createElement,
createHigherOrderComponent,
+ createContext,
pure,
} from '@wordpress/element';
import isShallowEqual from '@wordpress/is-shallow-equal';
@@ -256,6 +257,11 @@ export function dispatch( reducerKey ) {
return actions[ reducerKey ];
}
+const {
+ Consumer: CustomReducerKeyConsumer,
+ Provider: CustomReducerKeyProvider,
+} = createContext( identity );
+
/**
* Higher-order component used to inject state-derived props using registered
* selectors.
@@ -269,7 +275,7 @@ export function dispatch( reducerKey ) {
export const withSelect = ( mapStateToProps ) => createHigherOrderComponent( ( WrappedComponent ) => {
const DEFAULT_MERGE_PROPS = {};
- return class ComponentWithSelect extends Component {
+ class ComponentWithSelect extends Component {
constructor() {
super( ...arguments );
@@ -279,11 +285,17 @@ export const withSelect = ( mapStateToProps ) => createHigherOrderComponent( ( W
}
static getDerivedStateFromProps( props ) {
+ const { ownProps, mapReducerKey } = props;
+ const selectWithCustomReducerKey = flowRight( [
+ select,
+ mapReducerKey,
+ ] );
+
// A constant value is used as the fallback since it can be more
// efficiently shallow compared in case component is repeatedly
// rendered without its own merge props.
const mergeProps = (
- mapStateToProps( select, props ) ||
+ mapStateToProps( selectWithCustomReducerKey, ownProps ) ||
DEFAULT_MERGE_PROPS
);
@@ -301,7 +313,7 @@ export const withSelect = ( mapStateToProps ) => createHigherOrderComponent( ( W
shouldComponentUpdate( nextProps, nextState ) {
return (
- ! isShallowEqual( this.props, nextProps ) ||
+ ! isShallowEqual( this.props.ownProps, nextProps.ownProps ) ||
! isShallowEqual( this.state.mergeProps, nextState.mergeProps )
);
}
@@ -322,9 +334,25 @@ export const withSelect = ( mapStateToProps ) => createHigherOrderComponent( ( W
}
render() {
- return ;
+ return (
+
+ );
}
- };
+ }
+
+ return ( props ) => (
+
+ { ( mapReducerKey ) => (
+
+ ) }
+
+ );
}, 'withSelect' );
/**
@@ -343,7 +371,7 @@ export const withDispatch = ( mapDispatchToProps ) => createHigherOrderComponent
compose( [
pure,
( WrappedComponent ) => {
- return class ComponentWithDispatch extends Component {
+ class ComponentWithDispatch extends Component {
constructor( props ) {
super( ...arguments );
@@ -356,14 +384,20 @@ export const withDispatch = ( mapDispatchToProps ) => createHigherOrderComponent
}
proxyDispatch( propName, ...args ) {
+ const { mapReducerKey, ownProps } = this.props;
+ const dispatchWithCustomReducerKey = flowRight( [ dispatch, mapReducerKey ] );
+
// Original dispatcher is a pre-bound (dispatching) action creator.
- mapDispatchToProps( dispatch, this.props )[ propName ]( ...args );
+ mapDispatchToProps( dispatchWithCustomReducerKey, ownProps )[ propName ]( ...args );
}
setProxyProps( props ) {
+ const { ownProps, mapReducerKey } = props;
+ const dispatchWithCustomReducerKey = flowRight( [ dispatch, mapReducerKey ] );
+
// Assign as instance property so that in reconciling subsequent
// renders, the assigned prop values are referentially equal.
- const propsToDispatchers = mapDispatchToProps( dispatch, props );
+ const propsToDispatchers = mapDispatchToProps( dispatchWithCustomReducerKey, ownProps );
this.proxyProps = mapValues( propsToDispatchers, ( dispatcher, propName ) => {
// Prebind with prop name so we have reference to the original
// dispatcher to invoke. Track between re-renders to avoid
@@ -377,14 +411,59 @@ export const withDispatch = ( mapDispatchToProps ) => createHigherOrderComponent
}
render() {
- return ;
+ return (
+
+ );
}
- };
+ }
+
+ return ( props ) => (
+
+ { ( mapReducerKey ) => (
+
+ ) }
+
+ );
},
] ),
'withDispatch'
);
+export function withCustomReducerKey( mapReducerKey ) {
+ return createHigherOrderComponent(
+ ( WrappedComponent ) => class extends Component {
+ constructor() {
+ super( ...arguments );
+
+ this.mapReducerKey = this.mapReducerKey.bind( this );
+
+ this.state = {
+ mapReducerKey: this.mapReducerKey,
+ };
+ }
+
+ mapReducerKey( reducerKey ) {
+ return mapReducerKey( reducerKey, this.props );
+ }
+
+ render() {
+ return (
+
+
+
+ );
+ }
+ },
+ 'withCustomReducerKey'
+ );
+}
+
/**
* Returns true if the given argument appears to be a dispatchable action.
*
diff --git a/phpunit/class-rest-blocks-controller-test.php b/phpunit/class-rest-blocks-controller-test.php
deleted file mode 100644
index 5c81f1278878f8..00000000000000
--- a/phpunit/class-rest-blocks-controller-test.php
+++ /dev/null
@@ -1,339 +0,0 @@
- 'wp_block',
- 'post_status' => 'publish',
- 'post_title' => 'My cool block',
- 'post_content' => 'Hello!
',
- )
- );
-
- self::$user_id = $factory->user->create(
- array(
- 'role' => 'editor',
- )
- );
- }
-
- /**
- * Delete our fake data after our tests run.
- */
- public static function wpTearDownAfterClass() {
- wp_delete_post( self::$post_id );
-
- self::delete_user( self::$user_id );
- }
-
- /**
- * Check that our routes get set up properly.
- */
- public function test_register_routes() {
- $routes = rest_get_server()->get_routes();
-
- $this->assertArrayHasKey( '/wp/v2/blocks', $routes );
- $this->assertCount( 2, $routes['/wp/v2/blocks'] );
- $this->assertArrayHasKey( '/wp/v2/blocks/(?P[\d]+)', $routes );
- $this->assertCount( 3, $routes['/wp/v2/blocks/(?P[\d]+)'] );
- }
-
- /**
- * Check that we can GET a collection of blocks.
- */
- public function test_get_items() {
- wp_set_current_user( self::$user_id );
-
- $request = new WP_REST_Request( 'GET', '/wp/v2/blocks' );
- $response = rest_get_server()->dispatch( $request );
-
- $this->assertEquals( 200, $response->get_status() );
- $this->assertEquals(
- array(
- array(
- 'id' => self::$post_id,
- 'title' => 'My cool block',
- 'content' => 'Hello!
',
- ),
- ), $response->get_data()
- );
- }
-
- /**
- * Check that we can GET a single block.
- */
- public function test_get_item() {
- wp_set_current_user( self::$user_id );
-
- $request = new WP_REST_Request( 'GET', '/wp/v2/blocks/' . self::$post_id );
- $response = rest_get_server()->dispatch( $request );
-
- $this->assertEquals( 200, $response->get_status() );
- $this->assertEquals(
- array(
- 'id' => self::$post_id,
- 'title' => 'My cool block',
- 'content' => 'Hello!
',
- ), $response->get_data()
- );
- }
-
- /**
- * Check that we can POST to create a new block.
- */
- public function test_create_item() {
- wp_set_current_user( self::$user_id );
-
- $request = new WP_REST_Request( 'POST', '/wp/v2/blocks/' . self::$post_id );
- $request->set_body_params(
- array(
- 'title' => 'New cool block',
- 'content' => 'Wow!
',
- )
- );
-
- $response = rest_get_server()->dispatch( $request );
-
- $this->assertEquals( 200, $response->get_status() );
-
- $data = $response->get_data();
-
- $this->assertArrayHasKey( 'id', $data );
- $this->assertArrayHasKey( 'title', $data );
- $this->assertArrayHasKey( 'content', $data );
-
- $this->assertEquals( self::$post_id, $data['id'] );
- $this->assertEquals( 'New cool block', $data['title'] );
- $this->assertEquals( 'Wow!
', $data['content'] );
- }
-
- /**
- * Check that we can PUT to update a block.
- */
- public function test_update_item() {
- wp_set_current_user( self::$user_id );
-
- $request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . self::$post_id );
- $request->set_body_params(
- array(
- 'title' => 'Updated cool block',
- 'content' => 'Nice!
',
- )
- );
-
- $response = rest_get_server()->dispatch( $request );
-
- $this->assertEquals( 200, $response->get_status() );
-
- $data = $response->get_data();
-
- $this->assertArrayHasKey( 'id', $data );
- $this->assertArrayHasKey( 'title', $data );
- $this->assertArrayHasKey( 'content', $data );
-
- $this->assertEquals( self::$post_id, $data['id'] );
- $this->assertEquals( 'Updated cool block', $data['title'] );
- $this->assertEquals( 'Nice!
', $data['content'] );
- }
-
- /**
- * Check that we can DELETE a block.
- */
- public function test_delete_item() {
- wp_set_current_user( self::$user_id );
-
- $request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . self::$post_id );
-
- $response = rest_get_server()->dispatch( $request );
-
- $this->assertEquals( 200, $response->get_status() );
-
- $data = $response->get_data();
-
- $this->assertArrayHasKey( 'deleted', $data );
- $this->assertArrayHasKey( 'previous', $data );
-
- $this->assertTrue( $data['deleted'] );
-
- $this->assertArrayHasKey( 'id', $data['previous'] );
- $this->assertArrayHasKey( 'title', $data['previous'] );
- $this->assertArrayHasKey( 'content', $data['previous'] );
-
- $this->assertEquals( self::$post_id, $data['previous']['id'] );
- $this->assertEquals( 'My cool block', $data['previous']['title'] );
- $this->assertEquals( 'Hello!
', $data['previous']['content'] );
- }
-
- /**
- * Check that we have defined a JSON schema.
- */
- public function test_get_item_schema() {
- $request = new WP_REST_Request( 'OPTIONS', '/wp/v2/blocks' );
- $response = rest_get_server()->dispatch( $request );
- $data = $response->get_data();
- $properties = $data['schema']['properties'];
-
- $this->assertEquals( 3, count( $properties ) );
- $this->assertArrayHasKey( 'id', $properties );
- $this->assertArrayHasKey( 'title', $properties );
- $this->assertArrayHasKey( 'content', $properties );
- }
-
- /**
- * Test cases for test_capabilities().
- */
- public function data_capabilities() {
- return array(
- array( 'create', 'editor', 201 ),
- array( 'create', 'author', 201 ),
- array( 'create', 'contributor', 403 ),
- array( 'create', null, 401 ),
-
- array( 'read', 'editor', 200 ),
- array( 'read', 'author', 200 ),
- array( 'read', 'contributor', 200 ),
- array( 'read', null, 401 ),
-
- array( 'update_delete_own', 'editor', 200 ),
- array( 'update_delete_own', 'author', 200 ),
- array( 'update_delete_own', 'contributor', 403 ),
-
- array( 'update_delete_others', 'editor', 200 ),
- array( 'update_delete_others', 'author', 403 ),
- array( 'update_delete_others', 'contributor', 403 ),
- array( 'update_delete_others', null, 401 ),
- );
- }
-
- /**
- * Exhaustively check that each role either can or cannot create, edit,
- * update, and delete shared blocks.
- *
- * @dataProvider data_capabilities
- */
- public function test_capabilities( $action, $role, $expected_status ) {
- if ( $role ) {
- $user_id = $this->factory->user->create( array( 'role' => $role ) );
- wp_set_current_user( $user_id );
- } else {
- wp_set_current_user( 0 );
- }
-
- switch ( $action ) {
- case 'create':
- $request = new WP_REST_Request( 'POST', '/wp/v2/blocks' );
- $request->set_body_params(
- array(
- 'title' => 'Test',
- 'content' => 'Test
',
- )
- );
-
- $response = rest_get_server()->dispatch( $request );
- $this->assertEquals( $expected_status, $response->get_status() );
-
- break;
-
- case 'read':
- $request = new WP_REST_Request( 'GET', '/wp/v2/blocks/' . self::$post_id );
-
- $response = rest_get_server()->dispatch( $request );
- $this->assertEquals( $expected_status, $response->get_status() );
-
- break;
-
- case 'update_delete_own':
- $post_id = wp_insert_post(
- array(
- 'post_type' => 'wp_block',
- 'post_status' => 'publish',
- 'post_title' => 'My cool block',
- 'post_content' => 'Hello!
',
- 'post_author' => $user_id,
- )
- );
-
- $request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . $post_id );
- $request->set_body_params(
- array(
- 'title' => 'Test',
- 'content' => 'Test
',
- )
- );
-
- $response = rest_get_server()->dispatch( $request );
- $this->assertEquals( $expected_status, $response->get_status() );
-
- $request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . $post_id );
-
- $response = rest_get_server()->dispatch( $request );
- $this->assertEquals( $expected_status, $response->get_status() );
-
- wp_delete_post( $post_id );
-
- break;
-
- case 'update_delete_others':
- $request = new WP_REST_Request( 'PUT', '/wp/v2/blocks/' . self::$post_id );
- $request->set_body_params(
- array(
- 'title' => 'Test',
- 'content' => 'Test
',
- )
- );
-
- $response = rest_get_server()->dispatch( $request );
- $this->assertEquals( $expected_status, $response->get_status() );
-
- $request = new WP_REST_Request( 'DELETE', '/wp/v2/blocks/' . self::$post_id );
-
- $response = rest_get_server()->dispatch( $request );
- $this->assertEquals( $expected_status, $response->get_status() );
-
- break;
-
- default:
- $this->fail( "'$action' is not a valid action." );
- }
-
- if ( isset( $user_id ) ) {
- self::delete_user( $user_id );
- }
- }
-
- public function test_context_param() {
- $this->markTestSkipped( 'Controller doesn\'t implement get_context_param().' );
- }
- public function test_prepare_item() {
- $this->markTestSkipped( 'Controller doesn\'t implement prepare_item().' );
- }
-}