Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion edit-post/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { registerCoreBlocks } from '@wordpress/core-blocks';
import { render, unmountComponentAtNode } from '@wordpress/element';
import { dispatch } from '@wordpress/data';
import { dispatch, setupPersistence } from '@wordpress/data';
import deprecated from '@wordpress/deprecated';

/**
Expand All @@ -15,6 +15,11 @@ import store from './store';
import { initializeMetaBoxState } from './store/actions';
import Editor from './editor';

/**
* Module Constants
*/
const STORAGE_KEY = `WP_EDIT_POST_DATA_${ window.userSettings.uid }`;

/**
* Reinitializes the editor after the user chooses to reboot the editor after
* an unhandled error occurs, replacing previously mounted editor element using
Expand Down Expand Up @@ -88,3 +93,5 @@ export { default as PluginPostStatusInfo } from './components/sidebar/plugin-pos
export { default as PluginPrePublishPanel } from './components/sidebar/plugin-pre-publish-panel';
export { default as PluginSidebar } from './components/sidebar/plugin-sidebar';
export { default as PluginSidebarMoreMenuItem } from './components/header/plugin-sidebar-more-menu-item';

setupPersistence( STORAGE_KEY );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put it inside initializeEditor just before core blocks get registered?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, I put it here to keep it close to the previous behavior. The stores are created when the scripts are loaded, so I thought the stores should be initialized (loading the initial value) once the stores are loaded as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, it probably doesn't matter that much

13 changes: 3 additions & 10 deletions edit-post/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
*/
import {
registerStore,
withRehydration,
loadAndPersist,
restrictPersistence,
} from '@wordpress/data';

/**
Expand All @@ -14,20 +13,14 @@ import reducer from './reducer';
import applyMiddlewares from './middlewares';
import * as actions from './actions';
import * as selectors from './selectors';

/**
* Module Constants
*/
const STORAGE_KEY = `WP_EDIT_POST_PREFERENCES_${ window.userSettings.uid }`;

const store = registerStore( 'core/edit-post', {
reducer: withRehydration( reducer, 'preferences', STORAGE_KEY ),
reducer: restrictPersistence( reducer, 'preferences' ),
actions,
selectors,
persist: true,
} );

applyMiddlewares( store );
loadAndPersist( store, reducer, 'preferences', STORAGE_KEY );
store.dispatch( { type: 'INIT' } );

export default store;
22 changes: 9 additions & 13 deletions editor/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import { forOwn } from 'lodash';
* WordPress Dependencies
*/
import {
registerReducer,
registerSelectors,
registerActions,
withRehydration,
loadAndPersist,
registerStore,
restrictPersistence,
} from '@wordpress/data';

/**
Expand All @@ -27,16 +24,15 @@ import { validateTokenSettings } from '../components/rich-text/tokens';
/**
* Module Constants
*/
const STORAGE_KEY = `GUTENBERG_PREFERENCES_${ window.userSettings.uid }`;
const MODULE_KEY = 'core/editor';

const store = applyMiddlewares(
registerReducer( MODULE_KEY, withRehydration( reducer, 'preferences', STORAGE_KEY ) )
);
loadAndPersist( store, reducer, 'preferences', STORAGE_KEY );

registerSelectors( MODULE_KEY, selectors );
registerActions( MODULE_KEY, actions );
const store = registerStore( MODULE_KEY, {
reducer: restrictPersistence( reducer, 'preferences' ),
selectors,
actions,
persist: true,
} );
applyMiddlewares( store );

forOwn( tokens, ( { name, settings } ) => {
settings = validateTokenSettings( name, settings, store.getState() );
Expand Down
34 changes: 21 additions & 13 deletions editor/utils/with-history/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,19 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: [],
present: reducer( undefined, {} ),
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
};

let lastAction;
let shouldCreateUndoLevel = false;

const {
resetTypes = [],
shouldOverwriteState = () => false,
} = options;

return ( state = initialState, action ) => {
const { past, present, future } = state;
const { past, present, future, lastAction, shouldCreateUndoLevel } = state;
const previousAction = lastAction;

lastAction = action;

switch ( action.type ) {
case 'UNDO':
// Can't undo if no past.
Expand All @@ -74,6 +71,8 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: dropRight( past ),
present: last( past ),
future: [ present, ...future ],
lastAction: null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting, that we are changing the existing logic for all action types. In the past, we would assign action instead. Isn't it what caused the issue rather than the fact that we were using instance variables?

I prefer to have them stored as they are in the updated version but I'm trying to understand what was the original issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I know but this is a small change that doesn't have any impact but I think it's better this way because when you click the "undo/redo" you don't want to perform a comparison with these actions later, instead, you want to just record a new "past" when you start editing again

shouldCreateUndoLevel: false,
};
case 'REDO':
// Can't redo if no future.
Expand All @@ -85,11 +84,16 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: [ ...past, present ],
present: first( future ),
future: drop( future ),
lastAction: null,
shouldCreateUndoLevel: false,
};

case 'CREATE_UNDO_LEVEL':
shouldCreateUndoLevel = true;
return state;
return {
...state,
lastAction: null,
shouldCreateUndoLevel: true,
};
}

const nextPresent = reducer( present, action );
Expand All @@ -99,6 +103,8 @@ const withHistory = ( options = {} ) => ( reducer ) => {
past: [],
present: nextPresent,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
};
}

Expand All @@ -108,18 +114,20 @@ const withHistory = ( options = {} ) => ( reducer ) => {

let nextPast = past;

shouldCreateUndoLevel = ! past.length || shouldCreateUndoLevel;

if ( shouldCreateUndoLevel || ! shouldOverwriteState( action, previousAction ) ) {
if (
shouldCreateUndoLevel ||
! past.length ||
! shouldOverwriteState( action, previousAction )
) {
nextPast = [ ...past, present ];
}

shouldCreateUndoLevel = false;

return {
past: nextPast,
present: nextPresent,
future: [],
shouldCreateUndoLevel: false,
lastAction: action,
};
};
};
Expand Down
27 changes: 25 additions & 2 deletions editor/utils/with-history/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,35 @@ describe( 'withHistory', () => {
past: [],
present: 0,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

it( 'should track history', () => {
const reducer = withHistory()( counter );

let state;
const action = { type: 'INCREMENT' };
state = reducer( undefined, {} );
state = reducer( state, { type: 'INCREMENT' } );
state = reducer( state, action );

expect( state ).toEqual( {
past: [ 0 ],
present: 1,
future: [],
lastAction: action,
shouldCreateUndoLevel: false,
} );

state = reducer( state, { type: 'INCREMENT' } );
state = reducer( state, action );

expect( state ).toEqual( {
past: [ 0, 1 ],
present: 2,
future: [],
lastAction: action,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -53,6 +60,8 @@ describe( 'withHistory', () => {
past: [],
present: 0,
future: [ 1 ],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -76,6 +85,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 1,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -98,6 +109,8 @@ describe( 'withHistory', () => {
past: [],
present: 1,
future: [],
lastAction: null,
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -113,6 +126,8 @@ describe( 'withHistory', () => {
past: [ 0 ], // Needs at least one history
present: 2,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -137,6 +152,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 1,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );

state = reducer( state, { type: 'INCREMENT' } );
Expand All @@ -145,6 +162,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 2,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );
} );

Expand All @@ -162,6 +181,8 @@ describe( 'withHistory', () => {
past: [ 0 ],
present: 1,
future: [],
lastAction: null,
shouldCreateUndoLevel: true,
} );

state = reducer( state, { type: 'INCREMENT' } );
Expand All @@ -170,6 +191,8 @@ describe( 'withHistory', () => {
past: [ 0, 1 ],
present: 2,
future: [],
lastAction: { type: 'INCREMENT' },
shouldCreateUndoLevel: false,
} );
} );
} );
91 changes: 91 additions & 0 deletions packages/data/src/deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* External dependencies
*/
import { get } from 'lodash';

/**
* WordPress dependencies
*/
import deprecated from '@wordpress/deprecated';

/**
* Internal dependencies
*/
import { getPersistenceStorage } from './persist';

/**
* Adds the rehydration behavior to redux reducers.
*
* @param {Function} reducer The reducer to enhance.
* @param {string} reducerKey The reducer key to persist.
* @param {string} storageKey The storage key to use.
*
* @return {Function} Enhanced reducer.
*/
export function withRehydration( reducer, reducerKey, storageKey ) {
deprecated( 'wp.data.withRehydration', {
version: '3.6',
plugin: 'Gutenberg',
hint: 'See https://github.com/WordPress/gutenberg/pull/8146 for more details',
} );

// EnhancedReducer with auto-rehydration
const enhancedReducer = ( state, action ) => {
const nextState = reducer( state, action );

if ( action.type === 'REDUX_REHYDRATE' && action.storageKey === storageKey ) {
return {
...nextState,
[ reducerKey ]: action.payload,
};
}

return nextState;
};

return enhancedReducer;
}

/**
* Loads the initial state and persist on changes.
*
* This should be executed after the reducer's registration.
*
* @param {Object} store Store to enhance.
* @param {Function} reducer The reducer function. Used to get default values and to allow custom serialization by the reducers.
* @param {string} reducerKey The reducer key to persist (example: reducerKey.subReducerKey).
* @param {string} storageKey The storage key to use.
*/
export function loadAndPersist( store, reducer, reducerKey, storageKey ) {
deprecated( 'wp.data.loadAndPersist', {
version: '3.6',
plugin: 'Gutenberg',
hint: 'See https://github.com/WordPress/gutenberg/pull/8146 for more details',
} );

// Load initially persisted value
const persistedString = getPersistenceStorage().getItem( storageKey );
if ( persistedString ) {
const persistedState = {
...get( reducer( undefined, { type: '@@gutenberg/init' } ), reducerKey ),
...JSON.parse( persistedString ),
};

store.dispatch( {
type: 'REDUX_REHYDRATE',
payload: persistedState,
storageKey,
} );
}

// Persist updated preferences
let currentStateValue = get( store.getState(), reducerKey );
store.subscribe( () => {
const newStateValue = get( store.getState(), reducerKey );
if ( newStateValue !== currentStateValue ) {
currentStateValue = newStateValue;
const stateToSave = get( reducer( store.getState(), { type: 'SERIALIZE' } ), reducerKey );
getPersistenceStorage().setItem( storageKey, JSON.stringify( stateToSave ) );
}
} );
}
Loading