From 046814c086cd2fa6f8fdce14febd03101d00fee4 Mon Sep 17 00:00:00 2001 From: Connor Jennings Date: Mon, 25 Nov 2019 08:53:22 -0500 Subject: [PATCH 1/2] Set the post status to the correct status after a post is saved --- .eslintignore | 1 + blocks/dist/custom-status.build.js | 2 +- blocks/src/custom-status/block.js | 209 ++++++++++++------ modules/custom-status/compat/block-editor.php | 22 +- modules/custom-status/custom-status.php | 71 +++--- package-lock.json | 39 +++- package.json | 4 +- tests/e2e/specs/custom-status-post.js | 44 ++++ 8 files changed, 279 insertions(+), 113 deletions(-) create mode 100644 .eslintignore create mode 100644 tests/e2e/specs/custom-status-post.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..560e3dbb0 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +*.build.js diff --git a/blocks/dist/custom-status.build.js b/blocks/dist/custom-status.build.js index c1ff8b453..839ebe7e5 100644 --- a/blocks/dist/custom-status.build.js +++ b/blocks/dist/custom-status.build.js @@ -1,2 +1,2 @@ -!function(t){var e={};function o(n){if(e[n])return e[n].exports;var r=e[n]={i:n,l:!1,exports:{}};return t[n].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=t,o.c=e,o.d=function(t,e,n){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(o.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)o.d(n,r,function(e){return t[e]}.bind(null,r));return n},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="",o(o.s=1)}([,function(t,e,o){"use strict";o.r(e);o(5),o(6);var n=wp.i18n.__,r=wp.editPost.PluginPostStatusInfo,s=wp.plugins.registerPlugin,u=wp.data,i=u.subscribe,l=u.dispatch,a=u.select,c=u.withSelect,d=u.withDispatch,f=wp.compose.compose,p=wp.components.SelectControl,m=window.EditFlowCustomStatuses.map(function(t){return{label:t.name,value:t.slug}}),b=function(){document.querySelector(".editor-post-save-draft")&&(document.querySelector(".editor-post-save-draft").innerText="".concat(n("Save")))};i(function(){if(a("core/editor").getCurrentPostId())if(a("core/editor").isCleanNewPost())l("core/editor").editPost({status:ef_default_custom_status});else{var t=a("core/editor").getEditedPostAttribute("status");void 0!==t&&"publish"!==t&&b()}});s("edit-flow-custom-status",{icon:"edit-flow",render:f(c(function(t){return{status:t("core/editor").getEditedPostAttribute("status")}}),d(function(t){return{onUpdate:function(e){t("core/editor").editPost({status:e}),b()}}}))(function(t){var e=t.onUpdate,o=t.status;return wp.element.createElement(r,{className:"edit-flow-extended-post-status edit-flow-extended-post-status-".concat(o)},wp.element.createElement("h4",null,n("publish"!==o?"Extended Post Status":"Extended Post Status Disabled.","edit-flow")),"publish"!==o?wp.element.createElement(p,{label:"",value:o,options:m,onChange:e}):null,wp.element.createElement("small",{className:"edit-flow-extended-post-status-note"},n("publish"!==o?"Note: this will override all status settings above.":"To select a custom status, please unpublish the content first.","edit-flow")))})})},,,,function(t,e){},function(t,e){}]); +!function(t){var e={};function n(o){if(e[o])return e[o].exports;var r=e[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)n.d(o,r,function(e){return t[e]}.bind(null,r));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=1)}([,function(t,e,n){"use strict";n.r(e);n(5),n(6);var o=wp.i18n.__,r=wp.editPost.PluginPostStatusInfo,u=wp.plugins.registerPlugin,i=wp.data,s=i.subscribe,l=i.dispatch,a=i.select,c=i.withSelect,d=i.withDispatch,f=wp.compose.compose,p=wp.components.SelectControl,m=window.EditFlowStatuses.custom_statuses.map(function(t){return{label:t.name,value:t.slug}}),b=function(){return document.querySelector(".editor-post-save-draft")},w=null,v=function t(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:120,r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:20;if(!(r<0))if(b()){var u=a("core/editor").getEditedPostAttribute("status"),i=(e=u,m.find(function(t){return t.value===e}).label);if(void 0===u||void 0===i||function(t){return window.EditFlowStatuses.default_statuses.find(function(e){return e.slug===t})||"trash"===t}(u))return;!function(t){var e=t?"".concat(o("Save as")," ").concat(t):"".concat(o("Save")),n=b();n&&(n.innerText=e)}(i),clearTimeout(w)}else clearTimeout(w),w=setTimeout(function(){t(n,r-1)},n)};s(function(){if(a("core/editor").getCurrentPostId())if(a("core/editor").isCleanNewPost())l("core/editor").editPost({status:ef_default_custom_status});else{var t=a("core/editor").getEditedPostAttribute("status");void 0!==t&&"publish"!==t&&v()}});u("edit-flow-custom-status",{icon:"edit-flow",render:f(c(function(t){return{status:t("core/editor").getEditedPostAttribute("status")}}),d(function(t){return{onUpdate:function(e){t("core/editor").editPost({status:e}),v()}}}))(function(t){var e=t.onUpdate,n=t.status;return wp.element.createElement(r,{className:"edit-flow-extended-post-status edit-flow-extended-post-status-".concat(n)},wp.element.createElement("h4",null,o("publish"!==n?"Extended Post Status":"Extended Post Status Disabled.","edit-flow")),"publish"!==n?wp.element.createElement(p,{label:"",value:n,options:m,onChange:e}):null,wp.element.createElement("small",{className:"edit-flow-extended-post-status-note"},o("publish"!==n?"Note: this will override all status settings above.":"To select a custom status, please unpublish the content first.","edit-flow")))})})},,,,function(t,e){},function(t,e){}]); //# sourceMappingURL=custom-status.build.js.map \ No newline at end of file diff --git a/blocks/src/custom-status/block.js b/blocks/src/custom-status/block.js index d74033549..fadb35165 100644 --- a/blocks/src/custom-status/block.js +++ b/blocks/src/custom-status/block.js @@ -1,107 +1,172 @@ +/* global wp, document, ef_default_custom_status */ + +/** + * External dependencies + */ +import React from 'react'; +import PropTypes from 'prop-type'; + +/** + * Internal dependencies + */ import './editor.scss'; import './style.scss'; -let { __ } = wp.i18n; -let { PluginPostStatusInfo } = wp.editPost; -let { registerPlugin } = wp.plugins; -let { subscribe, dispatch, select, withSelect, withDispatch } = wp.data; -let { compose } = wp.compose; -let { SelectControl } = wp.components; +const { __ } = wp.i18n; +const { PluginPostStatusInfo } = wp.editPost; +const { registerPlugin } = wp.plugins; +const { subscribe, dispatch, select, withSelect, withDispatch } = wp.data; +const { compose } = wp.compose; +const { SelectControl } = wp.components; /** * Map Custom Statuses as options for SelectControl */ -let statuses = window.EditFlowCustomStatuses.map( s => ({ label: s.name, value: s.slug }) ); +const customStatuses = window.EditFlowStatuses.custom_statuses.map( s => ( { label: s.name, value: s.slug } ) ); + +// Trash isn't in `default_statuses` but we need to check for it +const isCoreStatus = slug => window.EditFlowStatuses.default_statuses.find( s => s.slug === slug ) || slug === 'trash'; +const getCustomStatusLabel = slug => customStatuses.find( s => s.value === slug ).label; +const getEditorPostSaveDraftDOM = () => { + return document.querySelector( '.editor-post-save-draft' ); +}; + +/** + * Hack :( + * + * @param {string} status the status of the post + * + * @see https://github.com/WordPress/gutenberg/issues/3144 + */ +const sideEffectL10nManipulation = status => { + const statusText = status ? `${ __( 'Save as' ) } ${ status }` : `${ __( 'Save' ) }`; + const node = getEditorPostSaveDraftDOM(); + if ( node ) { + node.innerText = statusText; + } +}; /** * Hack :( * * @see https://github.com/WordPress/gutenberg/issues/3144 * - * Gutenberg overrides the label of the Save button after save (i.e. "Save Draft"). But there's no way to subscribe to a "post save" message. + * Gutenberg will also override the status set in '.editor-post-save-draft' after save, and there isn't yet a way + * to subscribe to a "post save" message. So instead set a timeout and override the text in '.editor-post-save-draft + * + * The timeout for this method is an attempt to counteract https://github.com/WordPress/gutenberg/blob/95e769df1f82f6b0ef587d81af65dd2f48cd1c38/packages/editor/src/components/post-saved-state/index.js#L37-L42 * - * So instead, we're keeping the button label generic ("Save"). There's a brief period where it still flips to "Save Draft" but that's something we need to work upstream to find a good fix for. + * It's effectively a mutation observer with a limit on the number of attempts it will poll the DOM */ -let sideEffectL10nManipulation = () => { - let node = document.querySelector('.editor-post-save-draft'); - if ( node ) { - document.querySelector( '.editor-post-save-draft' ).innerText = `${ __( 'Save' ) }` - } -} +let activePostStatusUpdateTimeout = null; +const schedulePostStatusUpdater = ( timeout = 120, attempts = 20 ) => { + if ( attempts < 0 ) { + return; + } + + const node = getEditorPostSaveDraftDOM(); + + if ( node ) { + const status = select( 'core/editor' ).getEditedPostAttribute( 'status' ); + const statusLabel = getCustomStatusLabel( status ); + + if ( typeof status === 'undefined' || typeof statusLabel === 'undefined' || isCoreStatus( status ) ) { + return; + } + + sideEffectL10nManipulation( statusLabel ); + clearTimeout( activePostStatusUpdateTimeout ); + } else { + // Clearing timeouts so we don't stack them + clearTimeout( activePostStatusUpdateTimeout ); + activePostStatusUpdateTimeout = setTimeout( () => { + schedulePostStatusUpdater( timeout, attempts - 1 ); + }, timeout ); + } +}; // Set the status to the default custom status. -subscribe( function () { - const postId = select( 'core/editor' ).getCurrentPostId(); - // Post isn't ready yet so don't do anything. - if ( ! postId ) { - return; - } - - // For new posts, we need to force the our default custom status. - // Otherwise WordPress will force it to "Draft". - const isCleanNewPost = select( 'core/editor' ).isCleanNewPost(); - if ( isCleanNewPost ) { - dispatch( 'core/editor' ).editPost( { - status: ef_default_custom_status - } ); - - return; - } - - // Update the "Save" button. - var status = select( 'core/editor' ).getEditedPostAttribute( 'status' ); - if ( typeof status !== 'undefined' && status !== 'publish' ) { - sideEffectL10nManipulation(); - } +subscribe( function() { + const postId = select( 'core/editor' ).getCurrentPostId(); + // Post isn't ready yet so don't do anything. + if ( ! postId ) { + return; + } + + // For new posts, we need to force the our default custom status. + // Otherwise WordPress will force it to "Draft". + const isCleanNewPost = select( 'core/editor' ).isCleanNewPost(); + if ( isCleanNewPost ) { + dispatch( 'core/editor' ).editPost( { + status: ef_default_custom_status, + } ); + + return; + } + + // Update the "Save" button. + const status = select( 'core/editor' ).getEditedPostAttribute( 'status' ); + if ( typeof status !== 'undefined' && status !== 'publish' ) { + schedulePostStatusUpdater(); + } } ); /** * Custom status component - * @param object props + * @param {Object} param - An object. + * @param {string} onUpdate - Function called for updating + * @param {string} status - Status of the post + * + * @return {JSX} - the extended post status panel */ -let EditFlowCustomPostStati = ( { onUpdate, status } ) => ( - -

{ status !== 'publish' ? __( 'Extended Post Status', 'edit-flow' ) : __( 'Extended Post Status Disabled.', 'edit-flow' ) }

- - { status !== 'publish' ? : null } - - - { status !== 'publish' ? __( `Note: this will override all status settings above.`, 'edit-flow' ) : __( 'To select a custom status, please unpublish the content first.', 'edit-flow' ) } - -
+const EditFlowCustomPostStati = ( { onUpdate, status } ) => ( + +

{ status !== 'publish' ? __( 'Extended Post Status', 'edit-flow' ) : __( 'Extended Post Status Disabled.', 'edit-flow' ) }

+ + { status !== 'publish' ? : null } + + + { status !== 'publish' ? __( 'Note: this will override all status settings above.', 'edit-flow' ) : __( 'To select a custom status, please unpublish the content first.', 'edit-flow' ) } + +
); -const mapSelectToProps = ( select ) => { - return { - status: select('core/editor').getEditedPostAttribute('status'), - }; +EditFlowCustomPostStati.propTypes = { + onUpdate: PropTypes.func, + status: PropTypes.string, +}; + +const mapSelectToProps = _select => { + return { + status: _select( 'core/editor' ).getEditedPostAttribute( 'status' ), + }; }; -const mapDispatchToProps = ( dispatch ) => { - return { - onUpdate( status ) { - dispatch( 'core/editor' ).editPost( { status } ); - sideEffectL10nManipulation(); - }, - }; +const mapDispatchToProps = _dispatch => { + return { + onUpdate( status ) { + _dispatch( 'core/editor' ).editPost( { status } ); + schedulePostStatusUpdater(); + }, + }; }; -let plugin = compose( - withSelect( mapSelectToProps ), - withDispatch( mapDispatchToProps ) +const plugin = compose( + withSelect( mapSelectToProps ), + withDispatch( mapDispatchToProps ) )( EditFlowCustomPostStati ); /** * Kick it off */ registerPlugin( 'edit-flow-custom-status', { - icon: 'edit-flow', - render: plugin + icon: 'edit-flow', + render: plugin, } ); diff --git a/modules/custom-status/compat/block-editor.php b/modules/custom-status/compat/block-editor.php index d1ae15fa3..4081f573c 100644 --- a/modules/custom-status/compat/block-editor.php +++ b/modules/custom-status/compat/block-editor.php @@ -23,15 +23,31 @@ function action_admin_enqueue_scripts() { wp_enqueue_style( 'edit-flow-block-custom-status', EDIT_FLOW_URL . 'blocks/dist/custom-status.editor.build.css', false, EDIT_FLOW_VERSION ); wp_enqueue_script( 'edit-flow-block-custom-status', EDIT_FLOW_URL . 'blocks/dist/custom-status.build.js', array( 'wp-blocks', 'wp-element', 'wp-edit-post', 'wp-plugins', 'wp-components' ), EDIT_FLOW_VERSION ); - wp_localize_script( 'edit-flow-block-custom-status', 'EditFlowCustomStatuses', $this->get_custom_statuses() ); + wp_localize_script( + 'edit-flow-block-custom-status', + 'EditFlowStatuses', + array ( + 'custom_statuses' => $this->get_custom_statuses(), + 'default_statuses' => $this->get_default_statuses(), + ) + ); } /** * Just a wrapper to make sure we're have simple array instead of associative one. * - * @return array Custom statuses. + * @return array All statuses. */ - function get_custom_statuses() { + public function get_default_statuses() { + return array_values( $this->ef_module->get_default_statuses() ); + } + + /** + * Just a wrapper to make sure we're have simple array instead of associative one. + * + * @return array custom statuses. + */ + public function get_custom_statuses() { return array_values( $this->ef_module->get_custom_statuses() ); } } diff --git a/modules/custom-status/custom-status.php b/modules/custom-status/custom-status.php index 74a53cbe0..f9fdc1753 100644 --- a/modules/custom-status/custom-status.php +++ b/modules/custom-status/custom-status.php @@ -416,32 +416,14 @@ function post_admin_header() { $custom_statuses = apply_filters( 'ef_custom_status_list', $custom_statuses, $post ); - // All right, we want to set up the JS var which contains all custom statuses - $all_statuses = array(); - - // The default statuses from WordPress - $all_statuses[] = array( - 'name' => __( 'Published', 'edit-flow' ), - 'slug' => 'publish', - 'description' => '', - ); - $all_statuses[] = array( - 'name' => __( 'Privately Published', 'edit-flow' ), - 'slug' => 'private', - 'description' => '', - ); - $all_statuses[] = array( - 'name' => __( 'Scheduled', 'edit-flow' ), - 'slug' => 'future', - 'description' => '', - ); - - // Load the custom statuses - foreach( $custom_statuses as $status ) { - $all_statuses[] = array( - 'name' => esc_js( $status->name ), - 'slug' => esc_js( $status->slug ), - 'description' => esc_js( $status->description ), + $all_statuses = array_merge( $custom_statuses, $this->get_default_statuses() ); + + $statuses_js = array(); + foreach( $all_statuses as $status ) { + $statuses_js[] = array ( + 'name' => $status->name, + 'slug' => $status->slug, + 'description' => $status->description, ); } @@ -452,7 +434,7 @@ function post_admin_header() { // Now, let's print the JS vars ?>