diff --git a/docs/data/data-core-editor.md b/docs/data/data-core-editor.md index fd3e2fb5e376d..735d4428aa88f 100644 --- a/docs/data/data-core-editor.md +++ b/docs/data/data-core-editor.md @@ -1299,6 +1299,18 @@ Returns whether the post is locked. Is locked. +### isPostSavingLocked + +Returns whether post saving is locked. + +*Parameters* + + * state: Global application state. + +*Returns* + +Is locked. + ### isPostLockTakeover Returns whether the edition of the post has been taken over. @@ -1752,4 +1764,20 @@ Returns an action object used in signalling that the user has enabled the publis ### disablePublishSidebar -Returns an action object used in signalling that the user has disabled the publish sidebar. \ No newline at end of file +Returns an action object used in signalling that the user has disabled the publish sidebar. + +### lockPostSaving + +Returns an action object used to signal that post saving is locked. + +*Parameters* + + * lockName: The lock name. + +### unlockPostSaving + +Returns an action object used to signal that post saving is unlocked. + +*Parameters* + + * lockName: The lock name. \ No newline at end of file diff --git a/packages/editor/src/components/post-publish-button/index.js b/packages/editor/src/components/post-publish-button/index.js index 3cd74e24e9f13..842f04554c10d 100644 --- a/packages/editor/src/components/post-publish-button/index.js +++ b/packages/editor/src/components/post-publish-button/index.js @@ -82,6 +82,7 @@ export default compose( [ getEditedPostVisibility, isEditedPostSaveable, isEditedPostPublishable, + isPostSavingLocked, getCurrentPost, getCurrentPostType, } = select( 'core/editor' ); @@ -89,7 +90,7 @@ export default compose( [ isSaving: forceIsSaving || isSavingPost(), isBeingScheduled: isEditedPostBeingScheduled(), visibility: getEditedPostVisibility(), - isSaveable: isEditedPostSaveable(), + isSaveable: isEditedPostSaveable() && ! isPostSavingLocked(), isPublishable: forceIsDirty || isEditedPostPublishable(), hasPublishAction: get( getCurrentPost(), [ '_links', 'wp:action-publish' ], false ), postType: getCurrentPostType(), diff --git a/packages/editor/src/store/actions.js b/packages/editor/src/store/actions.js index 2c36c35871b56..6a21f91a2f98e 100644 --- a/packages/editor/src/store/actions.js +++ b/packages/editor/src/store/actions.js @@ -788,3 +788,32 @@ export function disablePublishSidebar() { type: 'DISABLE_PUBLISH_SIDEBAR', }; } + +/** + * Returns an action object used to signal that post saving is locked. + * + * @param {string} lockName The lock name. + * + * @return {Object} Action object + */ +export function lockPostSaving( lockName ) { + return { + type: 'LOCK_POST_SAVING', + lockName, + }; +} + +/** + * Returns an action object used to signal that post saving is unlocked. + * + * @param {string} lockName The lock name. + * + * @return {Object} Action object + */ +export function unlockPostSaving( lockName ) { + return { + type: 'UNLOCK_POST_SAVING', + lockName, + }; +} + diff --git a/packages/editor/src/store/reducer.js b/packages/editor/src/store/reducer.js index 1bcfae88dab7b..df9f7bda24194 100644 --- a/packages/editor/src/store/reducer.js +++ b/packages/editor/src/store/reducer.js @@ -913,6 +913,27 @@ export function postLock( state = { isLocked: false }, action ) { return state; } +/** + * Post saving lock. + * + * When post saving is locked, the post cannot be published or updated. + * + * @param {PostSavingLockState} state Current state. + * @param {Object} action Dispatched action. + * + * @return {PostLockState} Updated state. + */ +export function postSavingLock( state = {}, action ) { + switch ( action.type ) { + case 'LOCK_POST_SAVING': + return { ...state, [ action.lockName ]: true }; + + case 'UNLOCK_POST_SAVING': + return omit( state, action.lockName ); + } + return state; +} + export const reusableBlocks = combineReducers( { data( state = {}, action ) { switch ( action.type ) { @@ -1133,4 +1154,5 @@ export default optimist( combineReducers( { autosave, settings, tokens, + postSavingLock, } ) ); diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index 84762a70c62f3..93b89572dab0c 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -1995,6 +1995,17 @@ export function isPostLocked( state ) { return state.postLock.isLocked; } +/** + * Returns whether post saving is locked. + * + * @param {Object} state Global application state. + * + * @return {boolean} Is locked. + */ +export function isPostSavingLocked( state ) { + return state.postSavingLock.length > 0; +} + /** * Returns whether the edition of the post has been taken over. * diff --git a/packages/editor/src/store/test/reducer.js b/packages/editor/src/store/test/reducer.js index 9913daf213ec2..a1ee2508ca2e0 100644 --- a/packages/editor/src/store/test/reducer.js +++ b/packages/editor/src/store/test/reducer.js @@ -35,6 +35,7 @@ import { template, blockListSettings, autosave, + postSavingLock, } from '../reducer'; describe( 'state', () => { @@ -2263,4 +2264,49 @@ describe( 'state', () => { } ); } ); } ); + + describe( 'postSavingLock', () => { + it( 'returns empty object by default', () => { + const state = postSavingLock( undefined, {} ); + + expect( state ).toEqual( {} ); + } ); + + it( 'returns correct post locks when locks added and removed', () => { + let state = postSavingLock( undefined, { + type: 'LOCK_POST_SAVING', + lockName: 'test-lock', + } ); + + expect( state ).toEqual( { + 'test-lock': true, + } ); + + state = postSavingLock( deepFreeze( state ), { + type: 'LOCK_POST_SAVING', + lockName: 'test-lock-2', + } ); + + expect( state ).toEqual( { + 'test-lock': true, + 'test-lock-2': true, + } ); + + state = postSavingLock( deepFreeze( state ), { + type: 'UNLOCK_POST_SAVING', + lockName: 'test-lock', + } ); + + expect( state ).toEqual( { + 'test-lock-2': true, + } ); + + state = postSavingLock( deepFreeze( state ), { + type: 'UNLOCK_POST_SAVING', + lockName: 'test-lock-2', + } ); + + expect( state ).toEqual( {} ); + } ); + } ); } ); diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index e7e6294b64fe0..6742a02aca7e9 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -111,6 +111,7 @@ const { INSERTER_UTILITY_HIGH, INSERTER_UTILITY_MEDIUM, INSERTER_UTILITY_LOW, + isPostSavingLocked, } = selectors; describe( 'selectors', () => { @@ -891,6 +892,28 @@ describe( 'selectors', () => { } ); } ); + describe( 'isPostSavingLocked', () => { + it( 'should return true if the post has postSavingLocks', () => { + const state = { + postSavingLock: [ { 1: true } ], + currentPost: {}, + saving: {}, + }; + + expect( isPostSavingLocked( state ) ).toBe( true ); + } ); + + it( 'should return false if the post has no postSavingLocks', () => { + const state = { + postSavingLock: [], + currentPost: {}, + saving: {}, + }; + + expect( isPostSavingLocked( state ) ).toBe( false ); + } ); + } ); + describe( 'isEditedPostSaveable', () => { it( 'should return false if the post has no title, excerpt, content', () => { const state = {