From f83ad94af58d97b46342d31f37c47437a81e7ecd Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Wed, 18 Nov 2020 13:04:59 +0200 Subject: [PATCH 1/9] Edit Post: Refactor effects to action generators --- .../developers/data/data-core-edit-post.md | 8 - packages/edit-post/src/store/actions.js | 143 ++++++++++++++++-- 2 files changed, 133 insertions(+), 18 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-edit-post.md b/docs/designers-developers/developers/data/data-core-edit-post.md index 85e5d162c0bf97..03be0d1e9ff917 100644 --- a/docs/designers-developers/developers/data/data-core-edit-post.md +++ b/docs/designers-developers/developers/data/data-core-edit-post.md @@ -372,10 +372,6 @@ _Returns_ Returns an action object used to request meta box update. -_Returns_ - -- `Object`: Action object. - # **setAvailableMetaBoxesPerLocation** Returns an action object used in signaling @@ -385,10 +381,6 @@ _Parameters_ - _metaBoxesPerLocation_ `Object`: Meta boxes per location. -_Returns_ - -- `Object`: Action object. - # **setIsInserterOpened** Returns an action object used to open/close the inserter. diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index dfb1373bb05c51..6b91aa8c2ebb8c 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -1,12 +1,20 @@ /** * External dependencies */ -import { castArray } from 'lodash'; +import { castArray, reduce } from 'lodash'; /** * WordPress dependencies */ -import { controls } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { apiFetch } from '@wordpress/data-controls'; +import { controls, dispatch, select, subscribe } from '@wordpress/data'; +import { speak } from '@wordpress/a11y'; + +/** + * Internal dependencies + */ +import { getMetaBoxContainer } from '../utils/meta-boxes'; /** * Returns an action object used in signalling that the user opened an editor sidebar. @@ -153,11 +161,22 @@ export function toggleFeature( feature ) { }; } -export function switchEditorMode( mode ) { - return { +export function* switchEditorMode( mode ) { + yield { type: 'SWITCH_MODE', mode, }; + + // Unselect blocks when we switch to the code editor. + if ( mode !== 'visual' ) { + yield controls.dispatch( 'core/block-editor', 'clearSelectedBlock' ); + } + + const message = + mode === 'visual' + ? __( 'Visual editor selected' ) + : __( 'Code editor selected' ); + speak( message, 'assertive' ); } /** @@ -240,24 +259,128 @@ export function showBlockTypes( blockNames ) { * * @param {Object} metaBoxesPerLocation Meta boxes per location. * - * @return {Object} Action object. + * @yield {Object} Action object. */ -export function setAvailableMetaBoxesPerLocation( metaBoxesPerLocation ) { - return { +export function* setAvailableMetaBoxesPerLocation( metaBoxesPerLocation ) { + yield { type: 'SET_META_BOXES_PER_LOCATIONS', metaBoxesPerLocation, }; + + const postType = yield controls.select( + 'core/editor', + 'getCurrentPostType' + ); + if ( window.postboxes.page !== postType ) { + window.postboxes.add_postbox_toggles( postType ); + } + + let wasSavingPost = yield controls.select( 'core/editor', 'isSavingPost' ); + let wasAutosavingPost = yield controls.select( + 'core/editor', + 'isAutosavingPost' + ); + + // Meta boxes are initialized once at page load. It is not necessary to + // account for updates on each state change. + // + // See: https://github.com/WordPress/WordPress/blob/5.1.1/wp-admin/includes/post.php#L2307-L2309 + const hasActiveMetaBoxes = yield controls.select( + 'core/edit-post', + 'hasMetaBoxes' + ); + + // First remove any existing subscription in order to prevent multiple saves + if ( !! saveMetaboxUnsubscribe ) { + saveMetaboxUnsubscribe(); + } + + // Save metaboxes when performing a full save on the post. + const saveMetaboxUnsubscribe = subscribe( () => { + const isSavingPost = select( 'core/editor' ).isSavingPost(); + const isAutosavingPost = select( 'core/editor' ).isAutosavingPost(); + + // Save metaboxes on save completion, except for autosaves that are not a post preview. + const shouldTriggerMetaboxesSave = + hasActiveMetaBoxes && + wasSavingPost && + ! isSavingPost && + ! wasAutosavingPost; + + // Save current state for next inspection. + wasSavingPost = isSavingPost; + wasAutosavingPost = isAutosavingPost; + + if ( shouldTriggerMetaboxesSave ) { + dispatch( 'core/edit-post' ).requestMetaBoxUpdates(); + } + } ); } /** * Returns an action object used to request meta box update. * - * @return {Object} Action object. + * @yield {Object} Action object. */ -export function requestMetaBoxUpdates() { - return { +export function* requestMetaBoxUpdates() { + yield { type: 'REQUEST_META_BOX_UPDATES', }; + + // Saves the wp_editor fields + if ( window.tinyMCE ) { + window.tinyMCE.triggerSave(); + } + + // Additional data needed for backward compatibility. + // If we do not provide this data, the post will be overridden with the default values. + const post = yield controls.select( 'core/editor', 'getCurrentPost' ); + const additionalData = [ + post.comment_status ? [ 'comment_status', post.comment_status ] : false, + post.ping_status ? [ 'ping_status', post.ping_status ] : false, + post.sticky ? [ 'sticky', post.sticky ] : false, + post.author ? [ 'post_author', post.author ] : false, + ].filter( Boolean ); + + // We gather all the metaboxes locations data and the base form data + const baseFormData = new window.FormData( + document.querySelector( '.metabox-base-form' ) + ); + const activeMetaBoxLocations = yield controls.select( + 'core/edit-post', + 'getActiveMetaBoxLocations' + ); + const formDataToMerge = [ + baseFormData, + ...activeMetaBoxLocations.map( + ( location ) => + new window.FormData( getMetaBoxContainer( location ) ) + ), + ]; + + // Merge all form data objects into a single one. + const formData = reduce( + formDataToMerge, + ( memo, currentFormData ) => { + for ( const [ key, value ] of currentFormData ) { + memo.append( key, value ); + } + return memo; + }, + new window.FormData() + ); + additionalData.forEach( ( [ key, value ] ) => + formData.append( key, value ) + ); + + // Save the metaboxes + yield apiFetch( { + url: window._wpMetaBoxUrl, + method: 'POST', + body: formData, + parse: false, + } ); + yield controls.dispatch( 'core/edit-post', 'metaBoxUpdatesSuccess' ); } /** From 9569b6a70ef461bb30eb18e4909e0c7240005e1b Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Wed, 18 Nov 2020 13:07:58 +0200 Subject: [PATCH 2/9] Edit Post: Remove effects middleware and use register() --- packages/edit-post/src/store/index.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index 7d08516f251a42..418739f2eb1a75 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -1,13 +1,12 @@ /** * WordPress dependencies */ -import { createReduxStore, registerStore } from '@wordpress/data'; +import { createReduxStore, register } from '@wordpress/data'; /** * Internal dependencies */ import reducer from './reducer'; -import applyMiddlewares from './middlewares'; import * as actions from './actions'; import * as selectors from './selectors'; import { STORE_NAME } from './constants'; @@ -28,7 +27,4 @@ const storeConfig = { */ export const store = createReduxStore( STORE_NAME, storeConfig ); -// Ideally we use register instead of register store. -// We shouuld be able to make the switch once we remove the effects. -const instantiatedStore = registerStore( STORE_NAME, storeConfig ); -applyMiddlewares( instantiatedStore ); +register( store ); From 646b98b433a7e81113c9ac961747e1638943fb51 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Wed, 18 Nov 2020 13:13:24 +0200 Subject: [PATCH 3/9] Edit Post: Remove refx and add @wordpress/data-controls --- package-lock.json | 2 +- packages/edit-post/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9b0cd4a7d26cee..f04ad156c7eaf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17696,6 +17696,7 @@ "@wordpress/compose": "file:packages/compose", "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", + "@wordpress/data-controls": "file:packages/data-controls", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", @@ -17715,7 +17716,6 @@ "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "refx": "^3.0.0", "rememo": "^3.0.0" } }, diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index bd8bc599f00d10..6447ac08981dfb 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -33,6 +33,7 @@ "@wordpress/compose": "file:../compose", "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", + "@wordpress/data-controls": "file:../data-controls", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", @@ -52,7 +53,6 @@ "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "refx": "^3.0.0", "rememo": "^3.0.0" }, "publishConfig": { From 6accb234777654566c75ae3d7d60e88be9a2908c Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Wed, 18 Nov 2020 13:13:42 +0200 Subject: [PATCH 4/9] Edit Post: Delete unused effects and middlewares --- packages/edit-post/src/store/effects.js | 141 -------------------- packages/edit-post/src/store/middlewares.js | 41 ------ 2 files changed, 182 deletions(-) delete mode 100644 packages/edit-post/src/store/effects.js delete mode 100644 packages/edit-post/src/store/middlewares.js diff --git a/packages/edit-post/src/store/effects.js b/packages/edit-post/src/store/effects.js deleted file mode 100644 index 0e69bd005dd6f9..00000000000000 --- a/packages/edit-post/src/store/effects.js +++ /dev/null @@ -1,141 +0,0 @@ -/** - * External dependencies - */ -import { reduce } from 'lodash'; - -/** - * WordPress dependencies - */ -import { select, subscribe, dispatch } from '@wordpress/data'; -import { speak } from '@wordpress/a11y'; -import { __ } from '@wordpress/i18n'; -import apiFetch from '@wordpress/api-fetch'; - -/** - * Internal dependencies - */ -import { metaBoxUpdatesSuccess, requestMetaBoxUpdates } from './actions'; -import { getActiveMetaBoxLocations } from './selectors'; -import { getMetaBoxContainer } from '../utils/meta-boxes'; - -let saveMetaboxUnsubscribe; - -const effects = { - SET_META_BOXES_PER_LOCATIONS( action, store ) { - // Allow toggling metaboxes panels - // We need to wait for all scripts to load - // If the meta box loads the post script, it will already trigger this. - // After merge in Core, make sure to drop the timeout and update the postboxes script - // to avoid the double binding. - setTimeout( () => { - const postType = select( 'core/editor' ).getCurrentPostType(); - if ( window.postboxes.page !== postType ) { - window.postboxes.add_postbox_toggles( postType ); - } - } ); - - let wasSavingPost = select( 'core/editor' ).isSavingPost(); - let wasAutosavingPost = select( 'core/editor' ).isAutosavingPost(); - - // Meta boxes are initialized once at page load. It is not necessary to - // account for updates on each state change. - // - // See: https://github.com/WordPress/WordPress/blob/5.1.1/wp-admin/includes/post.php#L2307-L2309 - const hasActiveMetaBoxes = select( 'core/edit-post' ).hasMetaBoxes(); - - // First remove any existing subscription in order to prevent multiple saves - if ( !! saveMetaboxUnsubscribe ) { - saveMetaboxUnsubscribe(); - } - - // Save metaboxes when performing a full save on the post. - saveMetaboxUnsubscribe = subscribe( () => { - const isSavingPost = select( 'core/editor' ).isSavingPost(); - const isAutosavingPost = select( 'core/editor' ).isAutosavingPost(); - - // Save metaboxes on save completion, except for autosaves that are not a post preview. - const shouldTriggerMetaboxesSave = - hasActiveMetaBoxes && - wasSavingPost && - ! isSavingPost && - ! wasAutosavingPost; - - // Save current state for next inspection. - wasSavingPost = isSavingPost; - wasAutosavingPost = isAutosavingPost; - - if ( shouldTriggerMetaboxesSave ) { - store.dispatch( requestMetaBoxUpdates() ); - } - } ); - }, - REQUEST_META_BOX_UPDATES( action, store ) { - // Saves the wp_editor fields - if ( window.tinyMCE ) { - window.tinyMCE.triggerSave(); - } - - const state = store.getState(); - - // Additional data needed for backward compatibility. - // If we do not provide this data, the post will be overridden with the default values. - const post = select( 'core/editor' ).getCurrentPost( state ); - const additionalData = [ - post.comment_status - ? [ 'comment_status', post.comment_status ] - : false, - post.ping_status ? [ 'ping_status', post.ping_status ] : false, - post.sticky ? [ 'sticky', post.sticky ] : false, - post.author ? [ 'post_author', post.author ] : false, - ].filter( Boolean ); - - // We gather all the metaboxes locations data and the base form data - const baseFormData = new window.FormData( - document.querySelector( '.metabox-base-form' ) - ); - const formDataToMerge = [ - baseFormData, - ...getActiveMetaBoxLocations( state ).map( - ( location ) => - new window.FormData( getMetaBoxContainer( location ) ) - ), - ]; - - // Merge all form data objects into a single one. - const formData = reduce( - formDataToMerge, - ( memo, currentFormData ) => { - for ( const [ key, value ] of currentFormData ) { - memo.append( key, value ); - } - return memo; - }, - new window.FormData() - ); - additionalData.forEach( ( [ key, value ] ) => - formData.append( key, value ) - ); - - // Save the metaboxes - apiFetch( { - url: window._wpMetaBoxUrl, - method: 'POST', - body: formData, - parse: false, - } ).then( () => store.dispatch( metaBoxUpdatesSuccess() ) ); - }, - SWITCH_MODE( action ) { - // Unselect blocks when we switch to the code editor. - if ( action.mode !== 'visual' ) { - dispatch( 'core/block-editor' ).clearSelectedBlock(); - } - - const message = - action.mode === 'visual' - ? __( 'Visual editor selected' ) - : __( 'Code editor selected' ); - speak( message, 'assertive' ); - }, -}; - -export default effects; diff --git a/packages/edit-post/src/store/middlewares.js b/packages/edit-post/src/store/middlewares.js deleted file mode 100644 index 06cacf026d58c2..00000000000000 --- a/packages/edit-post/src/store/middlewares.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * External dependencies - */ -import { flowRight } from 'lodash'; -import refx from 'refx'; - -/** - * Internal dependencies - */ -import effects from './effects'; - -/** - * Applies the custom middlewares used specifically in the editor module. - * - * @param {Object} store Store Object. - * - * @return {Object} Update Store Object. - */ -function applyMiddlewares( store ) { - const middlewares = [ refx( effects ) ]; - - let enhancedDispatch = () => { - throw new Error( - 'Dispatching while constructing your middleware is not allowed. ' + - 'Other middleware would not be applied to this dispatch.' - ); - }; - let chain = []; - - const middlewareAPI = { - getState: store.getState, - dispatch: ( ...args ) => enhancedDispatch( ...args ), - }; - chain = middlewares.map( ( middleware ) => middleware( middlewareAPI ) ); - enhancedDispatch = flowRight( ...chain )( store.dispatch ); - - store.dispatch = enhancedDispatch; - return store; -} - -export default applyMiddlewares; From c36eccb762461848edcadc948919a931ffe97f5e Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Wed, 18 Nov 2020 13:56:31 +0200 Subject: [PATCH 5/9] Edit Post: Fix tests --- packages/edit-post/src/store/test/actions.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/edit-post/src/store/test/actions.js b/packages/edit-post/src/store/test/actions.js index 31877f378bb286..fc398c3aeb2117 100644 --- a/packages/edit-post/src/store/test/actions.js +++ b/packages/edit-post/src/store/test/actions.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { controls } from '@wordpress/data'; + /** * Internal dependencies */ @@ -95,9 +100,17 @@ describe( 'actions', () => { } ); describe( 'requestMetaBoxUpdates', () => { - it( 'should return the REQUEST_META_BOX_UPDATES action', () => { - expect( requestMetaBoxUpdates() ).toEqual( { - type: 'REQUEST_META_BOX_UPDATES', + it( 'should yield the REQUEST_META_BOX_UPDATES action', () => { + const fulfillment = requestMetaBoxUpdates(); + expect( fulfillment.next() ).toEqual( { + done: false, + value: { + type: 'REQUEST_META_BOX_UPDATES', + }, + } ); + expect( fulfillment.next() ).toEqual( { + done: false, + value: controls.select( 'core/editor', 'getCurrentPost' ), } ); } ); } ); From c4fb951d6fd90ed41f08741997978a93fd177835 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Wed, 18 Nov 2020 16:44:10 +0200 Subject: [PATCH 6/9] Edit Post: Revert store registration --- packages/edit-post/src/store/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index 418739f2eb1a75..aaa9ed8ec707dc 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { createReduxStore, register } from '@wordpress/data'; +import { createReduxStore, registerStore } from '@wordpress/data'; /** * Internal dependencies @@ -27,4 +27,5 @@ const storeConfig = { */ export const store = createReduxStore( STORE_NAME, storeConfig ); -register( store ); +// Ideally we use register instead of register store. +registerStore( STORE_NAME, storeConfig ); From b705adac478eee6e70f18d4e0e9ea0a00195c972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Tue, 24 Nov 2020 11:08:30 +0100 Subject: [PATCH 7/9] Use global saveMetaboxUnsubscribe --- packages/edit-post/src/store/actions.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 6b91aa8c2ebb8c..5dbbaed47b7096 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -253,6 +253,8 @@ export function showBlockTypes( blockNames ) { }; } +let saveMetaboxUnsubscribe; + /** * Returns an action object used in signaling * what Meta boxes are available in which location. @@ -296,7 +298,7 @@ export function* setAvailableMetaBoxesPerLocation( metaBoxesPerLocation ) { } // Save metaboxes when performing a full save on the post. - const saveMetaboxUnsubscribe = subscribe( () => { + saveMetaboxUnsubscribe = subscribe( () => { const isSavingPost = select( 'core/editor' ).isSavingPost(); const isAutosavingPost = select( 'core/editor' ).isAutosavingPost(); From 28e439bff3fc470f37c3c30218904a7cb91a24b5 Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 26 Nov 2020 13:05:03 +0200 Subject: [PATCH 8/9] Enable the data controls in the edit-post registry --- packages/edit-post/src/store/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/edit-post/src/store/index.js b/packages/edit-post/src/store/index.js index aaa9ed8ec707dc..c461e54c601fc6 100644 --- a/packages/edit-post/src/store/index.js +++ b/packages/edit-post/src/store/index.js @@ -2,6 +2,7 @@ * WordPress dependencies */ import { createReduxStore, registerStore } from '@wordpress/data'; +import { controls } from '@wordpress/data-controls'; /** * Internal dependencies @@ -15,6 +16,7 @@ const storeConfig = { reducer, actions, selectors, + controls, persist: [ 'preferences' ], }; From 3470c388e50d1f07513126d437553825755a9dda Mon Sep 17 00:00:00 2001 From: Marin Atanasov Date: Thu, 26 Nov 2020 13:39:56 +0200 Subject: [PATCH 9/9] Do not wait for requestMetaBoxUpdates to finish --- .../e2e-tests/specs/editor/various/publish-button.test.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/e2e-tests/specs/editor/various/publish-button.test.js b/packages/e2e-tests/specs/editor/various/publish-button.test.js index 680ca0082c2eda..d9396b7f48c386 100644 --- a/packages/e2e-tests/specs/editor/various/publish-button.test.js +++ b/packages/e2e-tests/specs/editor/various/publish-button.test.js @@ -49,9 +49,10 @@ describe( 'PostPublishButton', () => { await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).toBeNull(); - await page.evaluate( () => - window.wp.data.dispatch( 'core/edit-post' ).requestMetaBoxUpdates() - ); + await page.evaluate( () => { + window.wp.data.dispatch( 'core/edit-post' ).requestMetaBoxUpdates(); + return true; + } ); expect( await page.$( '.editor-post-publish-button[aria-disabled="true"]' ) ).not.toBeNull();