diff --git a/editor/actions.js b/editor/actions.js index aee2a223911761..7aa2030ec360de 100644 --- a/editor/actions.js +++ b/editor/actions.js @@ -4,6 +4,48 @@ import uuid from 'uuid/v4'; import { partial, castArray } from 'lodash'; +/** + * Returns an action object used in signalling that editor has initialized with + * the specified post object. + * + * @param {Object} post Post object + * @return {Object} Action object + */ +export function setInitialPost( post ) { + return { + type: 'SET_INITIAL_POST', + post, + }; +} + +/** + * Returns an action object used in signalling that the latest version of the + * post has been received, either by initialization or save. + * + * @param {Object} post Post object + * @return {Object} Action object + */ +export function resetPost( post ) { + return { + type: 'RESET_POST', + post, + }; +} + +/** + * Returns an action object used in signalling that editor has initialized as a + * new post with specified edits which should be considered non-dirtying. + * + * @param {Object} edits Edited attributes object + * @return {Object} Action object + */ +export function setupNewPost( edits ) { + return { + type: 'SETUP_NEW_POST', + edits, + }; +} + /** * Returns an action object used in signalling that blocks state should be * reset to the specified array of blocks, taking precedence over any other diff --git a/editor/effects.js b/editor/effects.js index d5999954d3fc9b..7d88e917a8aa85 100644 --- a/editor/effects.js +++ b/editor/effects.js @@ -15,6 +15,8 @@ import { __ } from '@wordpress/i18n'; */ import { getGutenbergURL, getWPAdminURL } from './utils/url'; import { + resetPost, + setupNewPost, resetBlocks, focusBlock, replaceBlocks, @@ -249,10 +251,26 @@ export default { dispatch( savePost() ); }, - RESET_POST( action ) { + SET_INITIAL_POST( action ) { const { post } = action; - if ( post.content ) { - return resetBlocks( parse( post.content.raw ) ); + const effects = []; + + // Parse content as blocks + if ( post.content.raw ) { + effects.push( resetBlocks( parse( post.content.raw ) ) ); } + + // Resetting post should occur after blocks have been reset, since it's + // the post reset that restarts history (used in dirty detection). + effects.push( resetPost( post ) ); + + // Include auto draft title in edits while not flagging post as dirty + if ( post.status === 'auto-draft' ) { + effects.push( setupNewPost( { + title: post.title.raw, + } ) ); + } + + return effects; }, }; diff --git a/editor/index.js b/editor/index.js index d563c2ff857ea0..0c6998a20f9521 100644 --- a/editor/index.js +++ b/editor/index.js @@ -20,7 +20,7 @@ import { settings } from '@wordpress/date'; import './assets/stylesheets/main.scss'; import Layout from './layout'; import { createReduxStore } from './state'; -import { undo } from './actions'; +import { setInitialPost, undo } from './actions'; import EditorSettingsProvider from './settings/provider'; /** @@ -55,30 +55,6 @@ if ( settings.timezone.string ) { moment.tz.setDefault( 'WP' ); } -/** - * Initializes Redux state with bootstrapped post, if provided. - * - * @param {Redux.Store} store Redux store instance - * @param {Object} post Bootstrapped post object - */ -function preparePostState( store, post ) { - // Set current post into state - store.dispatch( { - type: 'RESET_POST', - post, - } ); - - // Include auto draft title in edits while not flagging post as dirty - if ( post.status === 'auto-draft' ) { - store.dispatch( { - type: 'SETUP_NEW_POST', - edits: { - title: post.title.raw, - }, - } ); - } -} - /** * Initializes and returns an instance of Editor. * @@ -95,7 +71,7 @@ export function createEditorInstance( id, post, userSettings ) { settings: editorSettings, } ); - preparePostState( store, post ); + store.dispatch( setInitialPost( post ) ); render( diff --git a/editor/state.js b/editor/state.js index a33307a15e0a2f..e298dde66ed468 100644 --- a/editor/state.js +++ b/editor/state.js @@ -4,6 +4,7 @@ import optimist from 'redux-optimist'; import { combineReducers, applyMiddleware, createStore } from 'redux'; import refx from 'refx'; +import multi from 'redux-multi'; import { reduce, keyBy, first, last, omit, without, flowRight, mapValues } from 'lodash'; /** @@ -532,7 +533,7 @@ export function createReduxStore() { userData, } ) ); - const enhancers = [ applyMiddleware( refx( effects ) ) ]; + const enhancers = [ applyMiddleware( multi, refx( effects ) ) ]; if ( window.__REDUX_DEVTOOLS_EXTENSION__ ) { enhancers.push( window.__REDUX_DEVTOOLS_EXTENSION__() ); } diff --git a/editor/test/effects.js b/editor/test/effects.js index abdc693a1abcd4..cff639382b0f1a 100644 --- a/editor/test/effects.js +++ b/editor/test/effects.js @@ -11,14 +11,22 @@ import { getBlockTypes, unregisterBlockType, registerBlockType, createBlock } fr /** * Internal dependencies */ -import { mergeBlocks, focusBlock, replaceBlocks, editPost, savePost } from '../actions'; +import { + resetPost, + setupNewPost, + mergeBlocks, + focusBlock, + replaceBlocks, + editPost, + savePost, +} from '../actions'; import effects from '../effects'; import * as selectors from '../selectors'; jest.mock( '../selectors' ); describe( 'effects', () => { - const defaultBlockSettings = { save: noop, category: 'common' }; + const defaultBlockSettings = { save: () => 'Saved', category: 'common' }; beforeEach( () => jest.resetAllMocks() ); @@ -236,4 +244,69 @@ describe( 'effects', () => { expect( dispatch ).toHaveBeenCalledWith( savePost() ); } ); } ); + + describe( '.SET_INITIAL_POST', () => { + const handler = effects.SET_INITIAL_POST; + + it( 'should return post reset action', () => { + const post = { + id: 1, + title: { + raw: 'A History of Pork', + }, + content: { + raw: '', + }, + status: 'draft', + }; + + const result = handler( { post } ); + + expect( result ).toEqual( [ + resetPost( post ), + ] ); + } ); + + it( 'should return block reset with non-empty content', () => { + registerBlockType( 'core/test-block', defaultBlockSettings ); + const post = { + id: 1, + title: { + raw: 'A History of Pork', + }, + content: { + raw: 'Saved', + }, + status: 'draft', + }; + + const result = handler( { post } ); + + expect( result ).toHaveLength( 2 ); + expect( result ).toContainEqual( resetPost( post ) ); + expect( result.some( ( { blocks } ) => { + return blocks && blocks[ 0 ].name === 'core/test-block'; + } ) ).toBe( true ); + } ); + + it( 'should return post setup action only if auto-draft', () => { + const post = { + id: 1, + title: { + raw: 'A History of Pork', + }, + content: { + raw: '', + }, + status: 'auto-draft', + }; + + const result = handler( { post } ); + + expect( result ).toEqual( [ + resetPost( post ), + setupNewPost( { title: 'A History of Pork' } ), + ] ); + } ); + } ); } ); diff --git a/package.json b/package.json index 82424ffa8d57f7..5e73fc0f2d0e38 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "react-slot-fill": "^1.0.0-alpha.11", "react-transition-group": "^1.1.3", "redux": "^3.6.0", + "redux-multi": "^0.1.12", "redux-optimist": "0.0.2", "refx": "^2.0.0", "rememo": "^2.3.0",