From bb7b896080b8a6953915dc8f261fe6e7a9bfedc0 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 20 Dec 2017 16:30:45 -0500 Subject: [PATCH 01/62] Create per user autosaves, first pass. --- editor/components/post-saved-state/index.js | 2 +- editor/store/effects.js | 6 +- gutenberg.php | 116 ++++++++++++++++++++ 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/editor/components/post-saved-state/index.js b/editor/components/post-saved-state/index.js index a24b0f29a95a91..0160a32fcf87e0 100644 --- a/editor/components/post-saved-state/index.js +++ b/editor/components/post-saved-state/index.js @@ -36,7 +36,7 @@ export function PostSavedState( { isNew, isPublished, isDirty, isSaving, isSavea ); } - if ( ! isSaveable || isPublished ) { + if ( ! isSaveable ) { return null; } diff --git a/editor/store/effects.js b/editor/store/effects.js index 96046d03034f3f..3484ec9c5772b9 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -268,11 +268,7 @@ export default { } if ( isCurrentPostPublished( state ) ) { - // TODO: Publish autosave. - // - Autosaves are created as revisions for published posts, but - // the necessary REST API behavior does not yet exist - // - May need to check for whether the status of the edited post - // has changed from the saved copy (i.e. published -> pending) + dispatch( savePost( { 'autosave': 1 } ) ); return; } diff --git a/gutenberg.php b/gutenberg.php index 9d18814676f898..15f8c7b80f3c92 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -121,6 +121,24 @@ function gutenberg_pre_init() { } } +function gutenberg_add_rest_filters() { + + // Read the request payload. + $request_body = file_get_contents('php://input'); + $data = json_decode($request_body); + + if ( ! $data ) { + return; + } + + if ( isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] ) { + $post_id = (int) $data->id; + $post = get_post( $post_id ); + add_filter( 'rest_pre_insert_' . $post->post_type, 'gutenberg_handle_rest_pre_insert', 10, 2 ); + } + +} +add_action( 'rest_api_init', 'gutenberg_add_rest_filters' ); /** * Initialize Gutenberg. * @@ -153,6 +171,104 @@ function gutenberg_init( $return, $post ) { return true; } +/** + * In rest_pre_insert, possibly create an autosave. + * + * @param stdClass $prepared_post An object representing a single post prepared + * for inserting or updating the database. + * @param WP_REST_Request $request Request object. + * + * @return array The prepared post data. + */ +function gutenberg_handle_rest_pre_insert( $prepared_post ) { + if ( isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] ) { + $autosave_id = gutenberg_create_post_autosave( (array) $prepared_post ); + return new WP_Error( + 'gutenberg_create_post_autosave_interrupt', + __( 'Interrupt normal post saving to create an autosave for Gutenberg.', 'gutenberg' ), + $autosave_id + ); + } + + return $prepared_post; + +} + +/** + * Hijack the response process to avoid throwing an error. Possibly could be client side instead. + */ +function gutenberg_handle_rest_request_after_callbacks( $response, $handler, $request ) { + + if ( + isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] && + 'gutenberg_create_post_autosave_interrupt' === $response->get_error_code() + ) { + $post = get_post( (int) $response->get_error_data() ); + $post->post_status = null; + $post->post_name = null; + return $post; + } + return $response; +} + +add_filter( 'rest_request_after_callbacks', 'gutenberg_handle_rest_request_after_callbacks', 10, 3 ); + +/** + * Creates autosave data for the specified post from $_POST data. + * + * From core post.php. + * + * @since 2.6.0 + * + * @param mixed $post_data Associative array containing the post data or int post ID. + * @return mixed The autosave revision ID. WP_Error or 0 on error. + */ +function gutenberg_create_post_autosave( $post_data ) { + + $post_id = (int) $post_data['ID']; + set_query_var( 'post_id', $post_id ); + $post_author = get_current_user_id(); + + // Store one autosave per author. If there is already an autosave, overwrite it. + if ( $old_autosave = wp_get_post_autosave( $post_id, $post_author ) ) { + $new_autosave = _wp_post_revision_data( $post_data, true ); + $new_autosave['ID'] = $old_autosave->ID; + $new_autosave['post_author'] = $post_author; + + // If the new autosave has the same content as the post, delete the autosave. + $post = get_post( $post_id ); + $autosave_is_different = false; + foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { + if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) { + $autosave_is_different = true; + break; + } + } + + if ( ! $autosave_is_different ) { + wp_delete_post_revision( $old_autosave->ID ); + return 0; + } + + /** + * Fires before an autosave is stored. + * + * @since 4.1.0 + * + * @param array $new_autosave Post array - the autosave that is about to be saved. + */ + do_action( 'wp_creating_autosave', $new_autosave ); + + return wp_update_post( $new_autosave ); + } + + // _wp_put_post_revision() expects unescaped. + $post_data = wp_unslash( $post_data ); + + // Otherwise create the new autosave as a special post revision + return _wp_put_post_revision( $post_data, true ); +} + /** * Emulate post.php */ From 3ee6738a8be10980009688e5eb983f731ee86090 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 21 Dec 2017 16:08:28 -0500 Subject: [PATCH 02/62] no message for autosaves --- editor/store/effects.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/store/effects.js b/editor/store/effects.js index 3484ec9c5772b9..441bec10ee2ff4 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -128,6 +128,8 @@ export default { private: __( 'Post published privately!' ), future: __( 'Post scheduled!' ), }[ post.status ]; + } else if ( isPublished && willPublish ) { + noticeMessage = false; } else { // Generic fallback notice noticeMessage = __( 'Post updated!' ); From 2e3ec2e6172077372c56536a028bc855826c8f42 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 21 Dec 2017 16:09:04 -0500 Subject: [PATCH 03/62] Tag the autosave action to avoid creating revisions. --- editor/store/effects.js | 53 +++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index 441bec10ee2ff4..ef6b5675013508 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -17,6 +17,7 @@ import { getDefaultBlockName, } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; +import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -78,8 +79,14 @@ export default { optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, } ); dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); - const Model = wp.api.getPostTypeModel( getCurrentPostType( state ) ); - new Model( toSend ).save().done( ( newPost ) => { + const Model = wp.api.getPostTypeModel( type ); + const newModel = new Model( toSend ); + + // Tag the autosave action to avoid creating revisions. + if ( action.options && action.options.autosave ) { + newModel.url = addQueryArgs( newModel.url(), { 'gutenberg_autosave': '1' } ); + } + newModel.save().done( ( newPost ) => { dispatch( { type: 'RESET_POST', post: newPost, @@ -91,16 +98,31 @@ export default { optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, } ); } ).fail( ( err ) => { - dispatch( { - type: 'REQUEST_POST_UPDATE_FAILURE', - error: get( err, 'responseJSON', { - code: 'unknown_error', - message: __( 'An unknown error occurred.' ), - } ), - post, - edits, - optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, - } ); + // No notices for autosaves. + if ( err.responseJSON && 'gutenberg_create_post_autosave_interrupt' === err.responseJSON.code ) { + dispatch( { + type: 'RESET_POST', + post: post, + } ); + dispatch( { + type: 'REQUEST_POST_UPDATE_SUCCESS', + previousPost: post, + post: post, + optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, + } ); + } else { + + dispatch( { + type: 'REQUEST_POST_UPDATE_FAILURE', + error: get( err, 'responseJSON', { + code: 'unknown_error', + message: __( 'An unknown error occurred.' ), + } ), + post, + edits, + optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, + } ); + } } ); }, REQUEST_POST_UPDATE_SUCCESS( action, store ) { @@ -269,17 +291,12 @@ export default { return; } - if ( isCurrentPostPublished( state ) ) { - dispatch( savePost( { 'autosave': 1 } ) ); - return; - } - // Change status from auto-draft to draft if ( isEditedPostNew( state ) ) { dispatch( editPost( { status: 'draft' } ) ); } - dispatch( savePost() ); + dispatch( savePost( { 'autosave': 1 } ) ); }, SETUP_EDITOR( action ) { const { post, settings } = action; From 86b70c22bfd4c9ee7d5c070231c37cf551b09515 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 21 Dec 2017 16:09:22 -0500 Subject: [PATCH 04/62] pass options to savePost --- editor/store/actions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/store/actions.js b/editor/store/actions.js index 23027df7e93a74..aba2ecfd11505a 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -234,9 +234,10 @@ export function editPost( edits ) { }; } -export function savePost() { +export function savePost( options ) { return { type: 'REQUEST_POST_UPDATE', + options, }; } From db02aea8e6dc8c46539211d161dc39f1a34ef34b Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 21 Dec 2017 16:09:46 -0500 Subject: [PATCH 05/62] cap check before storing autosave --- gutenberg.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gutenberg.php b/gutenberg.php index 15f8c7b80f3c92..ecc6197ff35186 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -183,6 +183,10 @@ function gutenberg_init( $return, $post ) { function gutenberg_handle_rest_pre_insert( $prepared_post ) { if ( isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] ) { $autosave_id = gutenberg_create_post_autosave( (array) $prepared_post ); + if ( ! current_user_can( 'edit_post', (int) $prepared_post->ID ) ) { + return $prepared_post; + } + return new WP_Error( 'gutenberg_create_post_autosave_interrupt', __( 'Interrupt normal post saving to create an autosave for Gutenberg.', 'gutenberg' ), From e82ffc189b43dffe0016bff85fd0c826acbcb257 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 21 Dec 2017 16:11:29 -0500 Subject: [PATCH 06/62] Set and return an autosave ID --- gutenberg.php | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index ecc6197ff35186..9cd4dc1df4b2c3 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -182,15 +182,25 @@ function gutenberg_init( $return, $post ) { */ function gutenberg_handle_rest_pre_insert( $prepared_post ) { if ( isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] ) { - $autosave_id = gutenberg_create_post_autosave( (array) $prepared_post ); if ( ! current_user_can( 'edit_post', (int) $prepared_post->ID ) ) { return $prepared_post; } + // Map new fields onto the existing post data. + $existing_post = get_post( (int) $prepared_post->ID ); + foreach( $prepared_post as $key => $value ) { + $existing_post->$key = $value; + } + + $autosave_id = gutenberg_create_post_autosave( (array) $existing_post ); + + // Pass the autosave id as part of the error data so we can return the autosave data later. return new WP_Error( 'gutenberg_create_post_autosave_interrupt', __( 'Interrupt normal post saving to create an autosave for Gutenberg.', 'gutenberg' ), - $autosave_id + array( + 'autosave_id' => $autosave_id, + ) ); } @@ -198,25 +208,14 @@ function gutenberg_handle_rest_pre_insert( $prepared_post ) { } + +/** /** * Hijack the response process to avoid throwing an error. Possibly could be client side instead. */ function gutenberg_handle_rest_request_after_callbacks( $response, $handler, $request ) { - - if ( - isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] && - 'gutenberg_create_post_autosave_interrupt' === $response->get_error_code() - ) { - $post = get_post( (int) $response->get_error_data() ); - $post->post_status = null; - $post->post_name = null; - return $post; - } - return $response; } - add_filter( 'rest_request_after_callbacks', 'gutenberg_handle_rest_request_after_callbacks', 10, 3 ); - /** * Creates autosave data for the specified post from $_POST data. * @@ -231,6 +230,12 @@ function gutenberg_create_post_autosave( $post_data ) { $post_id = (int) $post_data['ID']; set_query_var( 'post_id', $post_id ); +} + +add_filter( 'rest_request_after_callbacks', 'gutenberg_handle_rest_request_after_callbacks', 10, 3 ); +} + +add_filter( 'rest_request_after_callbacks', 'gutenberg_handle_rest_request_after_callbacks', 10, 3 ); $post_author = get_current_user_id(); // Store one autosave per author. If there is already an autosave, overwrite it. From 9e5ccf8de3dc47fbc28c046d90f501771fb34af4 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 21 Dec 2017 16:12:28 -0500 Subject: [PATCH 07/62] =?UTF-8?q?Don=E2=80=99t=20show=20link=20top=20save?= =?UTF-8?q?=20published=20posts=20(autosave)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editor/components/post-saved-state/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/components/post-saved-state/index.js b/editor/components/post-saved-state/index.js index 0160a32fcf87e0..3c975dd8d61a0c 100644 --- a/editor/components/post-saved-state/index.js +++ b/editor/components/post-saved-state/index.js @@ -60,7 +60,7 @@ export function PostSavedState( { isNew, isPublished, isDirty, isSaving, isSavea return ( ); } From 968241d128345e25e1320cb7993aadb815267a53 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 22 Dec 2017 15:16:18 -0500 Subject: [PATCH 08/62] WP_REST_Autosaves_Controller first pass --- gutenberg.php | 26 +- lib/class-wp-rest-autosave-controller.php | 301 ++++++++++++++++++++++ lib/load.php | 1 + lib/register.php | 3 + 4 files changed, 307 insertions(+), 24 deletions(-) create mode 100644 lib/class-wp-rest-autosave-controller.php diff --git a/gutenberg.php b/gutenberg.php index 9cd4dc1df4b2c3..1b1e1b340b31cb 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -121,24 +121,15 @@ function gutenberg_pre_init() { } } -function gutenberg_add_rest_filters() { - - // Read the request payload. - $request_body = file_get_contents('php://input'); - $data = json_decode($request_body); - - if ( ! $data ) { - return; - } +function gutenberg_add_rest_endpoints() { if ( isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] ) { $post_id = (int) $data->id; $post = get_post( $post_id ); - add_filter( 'rest_pre_insert_' . $post->post_type, 'gutenberg_handle_rest_pre_insert', 10, 2 ); } } -add_action( 'rest_api_init', 'gutenberg_add_rest_filters' ); +add_action( 'rest_api_init', 'gutenberg_add_rest_endpoints' ); /** * Initialize Gutenberg. * @@ -208,14 +199,6 @@ function gutenberg_handle_rest_pre_insert( $prepared_post ) { } - -/** -/** - * Hijack the response process to avoid throwing an error. Possibly could be client side instead. - */ -function gutenberg_handle_rest_request_after_callbacks( $response, $handler, $request ) { -} -add_filter( 'rest_request_after_callbacks', 'gutenberg_handle_rest_request_after_callbacks', 10, 3 ); /** * Creates autosave data for the specified post from $_POST data. * @@ -230,12 +213,7 @@ function gutenberg_create_post_autosave( $post_data ) { $post_id = (int) $post_data['ID']; set_query_var( 'post_id', $post_id ); -} - -add_filter( 'rest_request_after_callbacks', 'gutenberg_handle_rest_request_after_callbacks', 10, 3 ); -} -add_filter( 'rest_request_after_callbacks', 'gutenberg_handle_rest_request_after_callbacks', 10, 3 ); $post_author = get_current_user_id(); // Store one autosave per author. If there is already an autosave, overwrite it. diff --git a/lib/class-wp-rest-autosave-controller.php b/lib/class-wp-rest-autosave-controller.php new file mode 100644 index 00000000000000..e72d69d3a66817 --- /dev/null +++ b/lib/class-wp-rest-autosave-controller.php @@ -0,0 +1,301 @@ +parent_post_type = $parent_post_type; + $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); + $this->namespace = 'wp/v2'; + $this->rest_base = 'autosaves'; + $post_type_object = get_post_type_object( $parent_post_type ); + $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; + } + + /** + * Registers routes for autosaves based on post types supporting autosaves. + * + * @since 5.0.0 + * + * @see register_rest_route() + */ + public function register_routes() { + error_log('register routes'); + register_rest_route( + $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The ID for the parent of the object.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', array( + 'args' => array( + 'parent' => array( + 'description' => __( 'The ID for the parent of the object.' ), + 'type' => 'integer', + ), + 'id' => array( + 'description' => __( 'Unique identifier for the object.' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'type' => 'boolean', + 'default' => false, + 'description' => __( 'Required to be true, as autosaves do not support trashing.' ), + ), + ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + } + + /** + * Get the parent post, if the ID is valid. + * + * @since 4.7.2 + * + * @param int $id Supplied ID. + * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. + */ + protected function get_parent( $parent ) { + $error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) ); + if ( (int) $parent <= 0 ) { + return $error; + } + + $parent = get_post( (int) $parent ); + if ( empty( $parent ) || empty( $parent->ID ) || $this->parent_post_type !== $parent->post_type ) { + return $error; + } + + return $parent; + } + + /** + * Checks if a given request has access to get autosaves. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full data about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { + + return true; + + } + + /** + * Get the revision, if the ID is valid. + * + * @since 4.7.2 + * + * @param int $id Supplied ID. + * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. + */ + protected function get_revision( $id ) { + $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) ); + if ( (int) $id <= 0 ) { + return $error; + } + + $revision = get_post( (int) $id ); + if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) { + return $error; + } + + return $revision; + } + + /** + * Gets a collection of autosaves using wp_get_post_autosave. + * + * Contains the user's autosave, for empty if it doesn't exist. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full data about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $parent = $this->get_parent( $request['parent'] ); + if ( is_wp_error( $parent ) ) { + return $parent; + } + + $autosave = wp_get_post_autosave( $request['parent'] ); + + if ( ! $autosave ) { + return array(); + } + + $response = array(); + $data = $this->prepare_item_for_response( $autosave, $request ); + $response[] = $this->prepare_response_for_collection( $data ); + + return rest_ensure_response( $response ); + } + + + /** + * Retrieves the autosave's schema, conforming to JSON Schema. + * + * @since 5.0.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => "{$this->parent_post_type}-autosave", + 'type' => 'object', + // Base properties for every Revision. + 'properties' => array( + 'author' => array( + 'description' => __( 'The ID for the author of the object.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'date' => array( + 'description' => __( "The date the object was published, in the site's timezone." ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'date_gmt' => array( + 'description' => __( 'The date the object was published, as GMT.' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'guid' => array( + 'description' => __( 'GUID for the object, as it exists in the database.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + 'id' => array( + 'description' => __( 'Unique identifier for the object.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'modified' => array( + 'description' => __( "The date the object was last modified, in the site's timezone." ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'modified_gmt' => array( + 'description' => __( 'The date the object was last modified, as GMT.' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + ), + 'parent' => array( + 'description' => __( 'The ID for the parent of the object.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'slug' => array( + 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + ), + ), + ); + + $parent_schema = $this->parent_controller->get_item_schema(); + + if ( ! empty( $parent_schema['properties']['title'] ) ) { + $schema['properties']['title'] = $parent_schema['properties']['title']; + } + + if ( ! empty( $parent_schema['properties']['content'] ) ) { + $schema['properties']['content'] = $parent_schema['properties']['content']; + } + + if ( ! empty( $parent_schema['properties']['excerpt'] ) ) { + $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt']; + } + + if ( ! empty( $parent_schema['properties']['guid'] ) ) { + $schema['properties']['guid'] = $parent_schema['properties']['guid']; + } + + return $this->add_additional_fields_schema( $schema ); + } + + +} diff --git a/lib/load.php b/lib/load.php index f50f03990b50dd..eec1ca1395c3ab 100644 --- a/lib/load.php +++ b/lib/load.php @@ -13,6 +13,7 @@ require dirname( __FILE__ ) . '/class-wp-block-type.php'; require dirname( __FILE__ ) . '/class-wp-block-type-registry.php'; require dirname( __FILE__ ) . '/class-wp-rest-reusable-blocks-controller.php'; +require dirname( __FILE__ ) . '/class-wp-rest-autosave-controller.php'; require dirname( __FILE__ ) . '/blocks.php'; require dirname( __FILE__ ) . '/client-assets.php'; require dirname( __FILE__ ) . '/compat.php'; diff --git a/lib/register.php b/lib/register.php index a8bc99b75bc8c5..5ca0b8c74feae4 100644 --- a/lib/register.php +++ b/lib/register.php @@ -407,6 +407,9 @@ function gutenberg_register_post_types() { function gutenberg_register_rest_routes() { $controller = new WP_REST_Reusable_Blocks_Controller(); $controller->register_routes(); + + $autosaves = new WP_REST_Autosaves_Controller( 'post' ); + $autosaves->register_routes(); } add_action( 'rest_api_init', 'gutenberg_register_rest_routes' ); From 52110b732315aa8a44010e9a272e123fd742c0c6 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 22 Dec 2017 15:16:40 -0500 Subject: [PATCH 09/62] Autosaves: add support for create_item --- lib/class-wp-rest-autosave-controller.php | 82 ++++++++++++++++++----- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/lib/class-wp-rest-autosave-controller.php b/lib/class-wp-rest-autosave-controller.php index e72d69d3a66817..e0884adda24e28 100644 --- a/lib/class-wp-rest-autosave-controller.php +++ b/lib/class-wp-rest-autosave-controller.php @@ -48,7 +48,6 @@ class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller { * @param string $parent_post_type Post type of the parent. */ public function __construct( $parent_post_type ) { - error_log('WP_REST_Autosaves_Controller'); $this->parent_post_type = $parent_post_type; $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); $this->namespace = 'wp/v2'; @@ -65,7 +64,6 @@ public function __construct( $parent_post_type ) { * @see register_rest_route() */ public function register_routes() { - error_log('register routes'); register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, array( 'args' => array( @@ -80,6 +78,12 @@ public function register_routes() { 'permission_callback' => array( $this, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); @@ -122,10 +126,66 @@ public function register_routes() { } + /** + * Creates a single autosave. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function create_item( $request ) { + // Map new fields onto the existing post data. + $parent = $this->get_parent( $request['parent'] ); + + foreach( $prepared_post as $key => $value ) { + $existing_post->$key = $value; + } + $prepared_post = $this->prepare_item_for_database( $request ); + + $autosave_id = gutenberg_create_post_autosave( (array) $existing_post ); + $post = get_post( $autosave_id ); + + $request->set_param( 'context', 'edit' ); + + $response = $this->prepare_item_for_response( $post, $request ); + $response = rest_ensure_response( $response ); + + $response->set_status( 201 ); + $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); + + return $response; + } + + + /** + * Checks if a given request has access to create an autosave. + * + * @since 5.0.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return bool|WP_Error True if the request has access to delete the item, WP_Error object otherwise. + */ + public function create_item_permissions_check( $request ) { + $parent = $this->get_parent( $request['parent'] ); + if ( is_wp_error( $parent ) ) { + return $parent; + } + + $response = $this->get_items_permissions_check( $request ); + if ( ! $response || is_wp_error( $response ) ) { + return $response; + } + + $post_type = get_post_type_object( 'revision' ); + return current_user_can( $post_type->cap->edit_post, $parent->ID ); + } + + /** * Get the parent post, if the ID is valid. * - * @since 4.7.2 + * @since 5.0.0 * * @param int $id Supplied ID. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. @@ -144,24 +204,10 @@ protected function get_parent( $parent ) { return $parent; } - /** - * Checks if a given request has access to get autosaves. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full data about the request. - * @return true|WP_Error True if the request has read access, WP_Error object otherwise. - */ - public function get_items_permissions_check( $request ) { - - return true; - - } - /** * Get the revision, if the ID is valid. * - * @since 4.7.2 + * @since 5.0.0 * * @param int $id Supplied ID. * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. From 9997325ba968b0f8477badda42bd094b84c7efed Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:05:48 -0500 Subject: [PATCH 10/62] Add an autosavePost action --- editor/store/actions.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/editor/store/actions.js b/editor/store/actions.js index aba2ecfd11505a..033d7a18524cd5 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -241,6 +241,13 @@ export function savePost( options ) { }; } +export function autosavePost( options ) { + return { + type: 'REQUEST_POST_AUTOSAVE', + options, + }; +} + export function trashPost( postId, postType ) { return { type: 'TRASH_POST', From f389030618d989ee1297af7782d3984eb1af241f Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:07:13 -0500 Subject: [PATCH 11/62] REQUEST_POST_AUTOSAVE / effects for autosave --- editor/store/effects.js | 90 +++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index ef6b5675013508..35c8363401a9e4 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -33,6 +33,7 @@ import { createErrorNotice, removeNotice, savePost, + autosavePost, editPost, requestMetaBoxUpdates, updateReusableBlock, @@ -62,6 +63,52 @@ const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; const SAVE_REUSABLE_BLOCK_NOTICE_ID = 'SAVE_REUSABLE_BLOCK_NOTICE_ID'; export default { + REQUEST_POST_AUTOSAVE( action, store ) { + console.log( 'REQUEST_POST_AUTOSAVE' ); + const { dispatch, getState } = store; + const state = getState(); + const post = getCurrentPost( state ); + const edits = getPostEdits( state ); + const toSend = { + ...edits, + content: getEditedPostContent( state ), + parent: post.id, + }; + + dispatch( { + type: 'UPDATE_POST', + edits: post, + optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, + } ); + + dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); + const Model = wp.api.getPostTypeAutosaveModel( getCurrentPostType( state ) ); + const newModel = new Model( toSend ); + + newModel.save().done( ( newPost ) => { + dispatch( { + type: 'RESET_POST', + post: post, + } ); + dispatch( { + type: 'REQUEST_POST_UPDATE_SUCCESS', + previousPost: post, + post: post, + optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, + } ); + } ).fail( ( err ) => { + dispatch( { + type: 'REQUEST_POST_UPDATE_FAILURE', + error: get( err, 'responseJSON', { + code: 'unknown_error', + message: __( 'An unknown error occurred.' ), + } ), + post, + edits, + optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, + } ); + } ); + }, REQUEST_POST_UPDATE( action, store ) { const { dispatch, getState } = store; const state = getState(); @@ -79,13 +126,9 @@ export default { optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, } ); dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); - const Model = wp.api.getPostTypeModel( type ); + const Model = wp.api.getPostTypeModel( getCurrentPostType( state ) ); const newModel = new Model( toSend ); - // Tag the autosave action to avoid creating revisions. - if ( action.options && action.options.autosave ) { - newModel.url = addQueryArgs( newModel.url(), { 'gutenberg_autosave': '1' } ); - } newModel.save().done( ( newPost ) => { dispatch( { type: 'RESET_POST', @@ -98,31 +141,16 @@ export default { optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, } ); } ).fail( ( err ) => { - // No notices for autosaves. - if ( err.responseJSON && 'gutenberg_create_post_autosave_interrupt' === err.responseJSON.code ) { - dispatch( { - type: 'RESET_POST', - post: post, - } ); - dispatch( { - type: 'REQUEST_POST_UPDATE_SUCCESS', - previousPost: post, - post: post, - optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, - } ); - } else { - - dispatch( { - type: 'REQUEST_POST_UPDATE_FAILURE', - error: get( err, 'responseJSON', { - code: 'unknown_error', - message: __( 'An unknown error occurred.' ), - } ), - post, - edits, - optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, - } ); - } + dispatch( { + type: 'REQUEST_POST_UPDATE_FAILURE', + error: get( err, 'responseJSON', { + code: 'unknown_error', + message: __( 'An unknown error occurred.' ), + } ), + post, + edits, + optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, + } ); } ); }, REQUEST_POST_UPDATE_SUCCESS( action, store ) { @@ -296,7 +324,7 @@ export default { dispatch( editPost( { status: 'draft' } ) ); } - dispatch( savePost( { 'autosave': 1 } ) ); + dispatch( autosavePost() ); }, SETUP_EDITOR( action ) { const { post, settings } = action; From 4b31a3c9ec3b40ed8b19e94fa629ab94ba0d7bb0 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:08:07 -0500 Subject: [PATCH 12/62] remove unused query var set --- gutenberg.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 1b1e1b340b31cb..c7f682cb8bafd6 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -212,8 +212,6 @@ function gutenberg_handle_rest_pre_insert( $prepared_post ) { function gutenberg_create_post_autosave( $post_data ) { $post_id = (int) $post_data['ID']; - set_query_var( 'post_id', $post_id ); - $post_author = get_current_user_id(); // Store one autosave per author. If there is already an autosave, overwrite it. From 36e3183f921026fd69d5a57677d961339eb1c623 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:09:59 -0500 Subject: [PATCH 13/62] Autosave controller cleanup --- lib/class-wp-rest-autosave-controller.php | 181 +++++----------------- 1 file changed, 35 insertions(+), 146 deletions(-) diff --git a/lib/class-wp-rest-autosave-controller.php b/lib/class-wp-rest-autosave-controller.php index e0884adda24e28..f03560e9035453 100644 --- a/lib/class-wp-rest-autosave-controller.php +++ b/lib/class-wp-rest-autosave-controller.php @@ -32,6 +32,14 @@ class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller { */ private $parent_controller; + /** + * Parent controller. + * + * @since 5.0.0 + * @var WP_REST_Controller + */ + private $revision_controller; + /** * The base of the parent controller's route. * @@ -48,12 +56,13 @@ class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller { * @param string $parent_post_type Post type of the parent. */ public function __construct( $parent_post_type ) { - $this->parent_post_type = $parent_post_type; - $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); - $this->namespace = 'wp/v2'; - $this->rest_base = 'autosaves'; - $post_type_object = get_post_type_object( $parent_post_type ); - $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; + $this->parent_post_type = $parent_post_type; + $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); + $this->revision_controller = new WP_REST_Revisions_Controller( $parent_post_type ); + $this->namespace = 'wp/v2'; + $this->rest_base = 'autosaves'; + $post_type_object = get_post_type_object( $parent_post_type ); + $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; } /** @@ -75,13 +84,13 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'permission_callback' => '__return_true',//array( $this->revision_controller, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( 'methods' => WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), - 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'permission_callback' => array( $this->parent_controller, 'create_item_permissions_check' ), 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), ), 'schema' => array( $this, 'get_public_item_schema' ), @@ -103,7 +112,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), - 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'permission_callback' => array( $this->revision_controller, 'get_item_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), @@ -111,7 +120,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), - 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'permission_callback' => array( $this->revision_controller, 'delete_item_permissions_check' ), 'args' => array( 'force' => array( 'type' => 'boolean', @@ -120,6 +129,12 @@ public function register_routes() { ), ), ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this->parent_controller, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); @@ -135,16 +150,14 @@ public function register_routes() { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function create_item( $request ) { - // Map new fields onto the existing post data. - $parent = $this->get_parent( $request['parent'] ); - foreach( $prepared_post as $key => $value ) { - $existing_post->$key = $value; - } - $prepared_post = $this->prepare_item_for_database( $request ); - - $autosave_id = gutenberg_create_post_autosave( (array) $existing_post ); - $post = get_post( $autosave_id ); + // Map new fields onto the existing post data. + $parent = $this->revision_controller->get_parent( $request['parent'] ); + $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); + $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); + $prepared_post->ID = $parent->ID; + $autosave_id = gutenberg_create_post_autosave( (array) $prepared_post ); + $post = get_post( $autosave_id ); $request->set_param( 'context', 'edit' ); @@ -152,58 +165,11 @@ public function create_item( $request ) { $response = rest_ensure_response( $response ); $response->set_status( 201 ); - $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); + $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $autosave_id ) ) ); return $response; } - - /** - * Checks if a given request has access to create an autosave. - * - * @since 5.0.0 - * - * @param WP_REST_Request $request Full details about the request. - * @return bool|WP_Error True if the request has access to delete the item, WP_Error object otherwise. - */ - public function create_item_permissions_check( $request ) { - $parent = $this->get_parent( $request['parent'] ); - if ( is_wp_error( $parent ) ) { - return $parent; - } - - $response = $this->get_items_permissions_check( $request ); - if ( ! $response || is_wp_error( $response ) ) { - return $response; - } - - $post_type = get_post_type_object( 'revision' ); - return current_user_can( $post_type->cap->edit_post, $parent->ID ); - } - - - /** - * Get the parent post, if the ID is valid. - * - * @since 5.0.0 - * - * @param int $id Supplied ID. - * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. - */ - protected function get_parent( $parent ) { - $error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) ); - if ( (int) $parent <= 0 ) { - return $error; - } - - $parent = get_post( (int) $parent ); - if ( empty( $parent ) || empty( $parent->ID ) || $this->parent_post_type !== $parent->post_type ) { - return $error; - } - - return $parent; - } - /** * Get the revision, if the ID is valid. * @@ -237,7 +203,7 @@ protected function get_revision( $id ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function get_items( $request ) { - $parent = $this->get_parent( $request['parent'] ); + $parent = $this->revision_controller->get_parent( $request['parent'] ); if ( is_wp_error( $parent ) ) { return $parent; } @@ -264,84 +230,7 @@ public function get_items( $request ) { * @return array Item schema data. */ public function get_item_schema() { - $schema = array( - '$schema' => 'http://json-schema.org/draft-04/schema#', - 'title' => "{$this->parent_post_type}-autosave", - 'type' => 'object', - // Base properties for every Revision. - 'properties' => array( - 'author' => array( - 'description' => __( 'The ID for the author of the object.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'date' => array( - 'description' => __( "The date the object was published, in the site's timezone." ), - 'type' => 'string', - 'format' => 'date-time', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'date_gmt' => array( - 'description' => __( 'The date the object was published, as GMT.' ), - 'type' => 'string', - 'format' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'guid' => array( - 'description' => __( 'GUID for the object, as it exists in the database.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit' ), - ), - 'id' => array( - 'description' => __( 'Unique identifier for the object.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'modified' => array( - 'description' => __( "The date the object was last modified, in the site's timezone." ), - 'type' => 'string', - 'format' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'modified_gmt' => array( - 'description' => __( 'The date the object was last modified, as GMT.' ), - 'type' => 'string', - 'format' => 'date-time', - 'context' => array( 'view', 'edit' ), - ), - 'parent' => array( - 'description' => __( 'The ID for the parent of the object.' ), - 'type' => 'integer', - 'context' => array( 'view', 'edit', 'embed' ), - ), - 'slug' => array( - 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), - 'type' => 'string', - 'context' => array( 'view', 'edit', 'embed' ), - ), - ), - ); - - $parent_schema = $this->parent_controller->get_item_schema(); - - if ( ! empty( $parent_schema['properties']['title'] ) ) { - $schema['properties']['title'] = $parent_schema['properties']['title']; - } - - if ( ! empty( $parent_schema['properties']['content'] ) ) { - $schema['properties']['content'] = $parent_schema['properties']['content']; - } - - if ( ! empty( $parent_schema['properties']['excerpt'] ) ) { - $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt']; - } - - if ( ! empty( $parent_schema['properties']['guid'] ) ) { - $schema['properties']['guid'] = $parent_schema['properties']['guid']; - } - - return $this->add_additional_fields_schema( $schema ); + return $this->revision_controller->get_item_schema(); } - } From f4f0d47524d6a05bc90a6433ad2061c7b7b36d00 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:10:26 -0500 Subject: [PATCH 14/62] Add a wp.api.getPostTypeAutosaveModel helper --- lib/client-assets.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 31507acc2d493f..3b69706b135bce 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -469,7 +469,6 @@ function gutenberg_extend_wp_api_backbone_client() { $rest_base = ! empty( $taxonomy_object->rest_base ) ? $taxonomy_object->rest_base : $taxonomy_object->name; $taxonomy_rest_base_mapping[ $taxonomy_object->name ] = $rest_base; } - $script = sprintf( 'wp.api.postTypeRestBaseMapping = %s;', wp_json_encode( $post_type_rest_base_mapping ) ); $script .= sprintf( 'wp.api.taxonomyRestBaseMapping = %s;', wp_json_encode( $taxonomy_rest_base_mapping ) ); $script .= <<[\\\\d]+)/autosaves/(?P[\\\\d]+)'; + return _.find( wp.api.models, function( collection ) { + return collection.prototype.route && route === collection.prototype.route.index; + } ); + }; wp.api.getTaxonomyModel = function( taxonomy ) { var route = '/' + wpApiSettings.versionString + this.taxonomyRestBaseMapping[ taxonomy ] + '/(?P[\\\\d]+)'; return _.find( wp.api.models, function( model ) { From e48495865798de0bf14b46490c2f258b1f12a38f Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:11:41 -0500 Subject: [PATCH 15/62] Register autosave controllers for posts types that support it. --- lib/register.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/register.php b/lib/register.php index 5ca0b8c74feae4..de2ba52ea068d1 100644 --- a/lib/register.php +++ b/lib/register.php @@ -408,12 +408,17 @@ function gutenberg_register_rest_routes() { $controller = new WP_REST_Reusable_Blocks_Controller(); $controller->register_routes(); - $autosaves = new WP_REST_Autosaves_Controller( 'post' ); - $autosaves->register_routes(); + foreach ( get_post_types( array(), 'objects' ) as $post_type_object ) { + if( ! empty( $post_type_object->rest_base ) ) { + if ( post_type_supports( $post_type_object->name, 'revisions' ) ) { + $autosaves = new WP_REST_Autosaves_Controller( $post_type_object->name ); + $autosaves->register_routes(); + } + } + } } add_action( 'rest_api_init', 'gutenberg_register_rest_routes' ); - /** * Gets revisions details for the selected post. * From 4d81b62ee7aa0d74405072a59d7b0178c0756ca7 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:16:34 -0500 Subject: [PATCH 16/62] undo some save changes --- editor/store/effects.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index 35c8363401a9e4..2236b37c964108 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -127,9 +127,7 @@ export default { } ); dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); const Model = wp.api.getPostTypeModel( getCurrentPostType( state ) ); - const newModel = new Model( toSend ); - - newModel.save().done( ( newPost ) => { + new Model( toSend ).save().done( ( newPost ) => { dispatch( { type: 'RESET_POST', post: newPost, @@ -178,8 +176,6 @@ export default { private: __( 'Post published privately!' ), future: __( 'Post scheduled!' ), }[ post.status ]; - } else if ( isPublished && willPublish ) { - noticeMessage = false; } else { // Generic fallback notice noticeMessage = __( 'Post updated!' ); From 15e0573eb424198454ddb7f93a3e01c236a42947 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 12:18:35 -0500 Subject: [PATCH 17/62] whitespace/undo change --- lib/client-assets.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/client-assets.php b/lib/client-assets.php index 3b69706b135bce..a435251fd0c126 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -469,6 +469,7 @@ function gutenberg_extend_wp_api_backbone_client() { $rest_base = ! empty( $taxonomy_object->rest_base ) ? $taxonomy_object->rest_base : $taxonomy_object->name; $taxonomy_rest_base_mapping[ $taxonomy_object->name ] = $rest_base; } + $script = sprintf( 'wp.api.postTypeRestBaseMapping = %s;', wp_json_encode( $post_type_rest_base_mapping ) ); $script .= sprintf( 'wp.api.taxonomyRestBaseMapping = %s;', wp_json_encode( $taxonomy_rest_base_mapping ) ); $script .= << Date: Sun, 24 Dec 2017 14:09:43 -0500 Subject: [PATCH 18/62] simplify autosave --- editor/store/effects.js | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index 2236b37c964108..f8cc209679e99a 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -75,13 +75,6 @@ export default { parent: post.id, }; - dispatch( { - type: 'UPDATE_POST', - edits: post, - optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, - } ); - - dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); const Model = wp.api.getPostTypeAutosaveModel( getCurrentPostType( state ) ); const newModel = new Model( toSend ); @@ -90,15 +83,9 @@ export default { type: 'RESET_POST', post: post, } ); - dispatch( { - type: 'REQUEST_POST_UPDATE_SUCCESS', - previousPost: post, - post: post, - optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, - } ); } ).fail( ( err ) => { dispatch( { - type: 'REQUEST_POST_UPDATE_FAILURE', + type: 'REQUEST_POST_AUTOSAVE_FAILURE', error: get( err, 'responseJSON', { code: 'unknown_error', message: __( 'An unknown error occurred.' ), @@ -311,13 +298,15 @@ export default { return; } - if ( ! isEditedPostNew( state ) && ! isEditedPostDirty( state ) ) { - return; - } - // Change status from auto-draft to draft if ( isEditedPostNew( state ) ) { dispatch( editPost( { status: 'draft' } ) ); + dispatch( savePost() ); + return; + } + + if ( ! isEditedPostNew( state ) && ! isEditedPostDirty( state ) ) { + return; } dispatch( autosavePost() ); From 39fb050746ca7e69f06f414dacd55879b8c4d306 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:11:00 -0500 Subject: [PATCH 19/62] autosave controller cleanup --- lib/class-wp-rest-autosave-controller.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/class-wp-rest-autosave-controller.php b/lib/class-wp-rest-autosave-controller.php index f03560e9035453..b90f34ef1f9e7f 100644 --- a/lib/class-wp-rest-autosave-controller.php +++ b/lib/class-wp-rest-autosave-controller.php @@ -84,7 +84,7 @@ public function register_routes() { array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), - 'permission_callback' => '__return_true',//array( $this->revision_controller, 'get_items_permissions_check' ), + 'permission_callback' => array( $this->revision_controller, 'get_items_permissions_check' ), 'args' => $this->get_collection_params(), ), array( @@ -171,25 +171,25 @@ public function create_item( $request ) { } /** - * Get the revision, if the ID is valid. + * Get the autosave, if the ID is valid. * * @since 5.0.0 * * @param int $id Supplied ID. * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. */ - protected function get_revision( $id ) { - $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) ); + public function get_item( $id ) { + $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid autosave ID.' ), array( 'status' => 404 ) ); if ( (int) $id <= 0 ) { return $error; } - $revision = get_post( (int) $id ); - if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) { + $autosave = get_post( (int) $id ); + if ( empty( $autosave ) || empty( $autosave->ID ) || 'autosave' !== $autosave->post_type ) { return $error; } - return $revision; + return $autosave; } /** @@ -214,7 +214,7 @@ public function get_items( $request ) { return array(); } - $response = array(); + $response = array(); $data = $this->prepare_item_for_response( $autosave, $request ); $response[] = $this->prepare_response_for_collection( $data ); From a5b26db84e3c72ebdefc54b4eb10367ed9149972 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:14:53 -0500 Subject: [PATCH 20/62] actions: revert savePost changes --- editor/store/actions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/store/actions.js b/editor/store/actions.js index 033d7a18524cd5..954ad1a24695b7 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -234,10 +234,9 @@ export function editPost( edits ) { }; } -export function savePost( options ) { +export function savePost() { return { type: 'REQUEST_POST_UPDATE', - options, }; } From 17c26cae4a957d29b0c0cd5e33207fa91c900b91 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:17:30 -0500 Subject: [PATCH 21/62] move create_autosave to autosave controller --- gutenberg.php | 55 ---------------------- lib/class-wp-rest-autosave-controller.php | 56 ++++++++++++++++++++++- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index c7f682cb8bafd6..d43e1509f081f8 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -199,61 +199,6 @@ function gutenberg_handle_rest_pre_insert( $prepared_post ) { } -/** - * Creates autosave data for the specified post from $_POST data. - * - * From core post.php. - * - * @since 2.6.0 - * - * @param mixed $post_data Associative array containing the post data or int post ID. - * @return mixed The autosave revision ID. WP_Error or 0 on error. - */ -function gutenberg_create_post_autosave( $post_data ) { - - $post_id = (int) $post_data['ID']; - $post_author = get_current_user_id(); - - // Store one autosave per author. If there is already an autosave, overwrite it. - if ( $old_autosave = wp_get_post_autosave( $post_id, $post_author ) ) { - $new_autosave = _wp_post_revision_data( $post_data, true ); - $new_autosave['ID'] = $old_autosave->ID; - $new_autosave['post_author'] = $post_author; - - // If the new autosave has the same content as the post, delete the autosave. - $post = get_post( $post_id ); - $autosave_is_different = false; - foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { - if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) { - $autosave_is_different = true; - break; - } - } - - if ( ! $autosave_is_different ) { - wp_delete_post_revision( $old_autosave->ID ); - return 0; - } - - /** - * Fires before an autosave is stored. - * - * @since 4.1.0 - * - * @param array $new_autosave Post array - the autosave that is about to be saved. - */ - do_action( 'wp_creating_autosave', $new_autosave ); - - return wp_update_post( $new_autosave ); - } - - // _wp_put_post_revision() expects unescaped. - $post_data = wp_unslash( $post_data ); - - // Otherwise create the new autosave as a special post revision - return _wp_put_post_revision( $post_data, true ); -} - /** * Emulate post.php */ diff --git a/lib/class-wp-rest-autosave-controller.php b/lib/class-wp-rest-autosave-controller.php index b90f34ef1f9e7f..10f01e94e6dd41 100644 --- a/lib/class-wp-rest-autosave-controller.php +++ b/lib/class-wp-rest-autosave-controller.php @@ -156,7 +156,7 @@ public function create_item( $request ) { $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); $prepared_post->ID = $parent->ID; - $autosave_id = gutenberg_create_post_autosave( (array) $prepared_post ); + $autosave_id = $this->create_post_autosave( (array) $prepared_post ); $post = get_post( $autosave_id ); $request->set_param( 'context', 'edit' ); @@ -233,4 +233,58 @@ public function get_item_schema() { return $this->revision_controller->get_item_schema(); } + /** + * Creates autosave data for the specified post from $_POST data. + * + * From core post.php. + * + * @since 2.6.0 + * + * @param mixed $post_data Associative array containing the post data or int post ID. + * @return mixed The autosave revision ID. WP_Error or 0 on error. + */ + public function create_post_autosave( $post_data ) { + + $post_id = (int) $post_data['ID']; + $post_author = get_current_user_id(); + + // Store one autosave per author. If there is already an autosave, overwrite it. + if ( $old_autosave = wp_get_post_autosave( $post_id, $post_author ) ) { + $new_autosave = _wp_post_revision_data( $post_data, true ); + $new_autosave['ID'] = $old_autosave->ID; + $new_autosave['post_author'] = $post_author; + + // If the new autosave has the same content as the post, delete the autosave. + $post = get_post( $post_id ); + $autosave_is_different = false; + foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) { + if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) { + $autosave_is_different = true; + break; + } + } + + if ( ! $autosave_is_different ) { + wp_delete_post_revision( $old_autosave->ID ); + return 0; + } + + /** + * Fires before an autosave is stored. + * + * @since 4.1.0 + * + * @param array $new_autosave Post array - the autosave that is about to be saved. + */ + do_action( 'wp_creating_autosave', $new_autosave ); + + return wp_update_post( $new_autosave ); + } + + // _wp_put_post_revision() expects unescaped. + $post_data = wp_unslash( $post_data ); + + // Otherwise create the new autosave as a special post revision + return _wp_put_post_revision( $post_data, true ); + } } From fd6464c9fea778b40910eaa3f46b4281340cb594 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:17:41 -0500 Subject: [PATCH 22/62] remove some logging --- editor/store/effects.js | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index f8cc209679e99a..163927cd5463ed 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -64,7 +64,6 @@ const SAVE_REUSABLE_BLOCK_NOTICE_ID = 'SAVE_REUSABLE_BLOCK_NOTICE_ID'; export default { REQUEST_POST_AUTOSAVE( action, store ) { - console.log( 'REQUEST_POST_AUTOSAVE' ); const { dispatch, getState } = store; const state = getState(); const post = getCurrentPost( state ); From 51ac91eaac24a7ae9f8cb317b3f1a13f0cb3f915 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:18:54 -0500 Subject: [PATCH 23/62] Docs cleanup --- lib/class-wp-rest-autosave-controller.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-rest-autosave-controller.php b/lib/class-wp-rest-autosave-controller.php index 10f01e94e6dd41..e769a48c45d5c0 100644 --- a/lib/class-wp-rest-autosave-controller.php +++ b/lib/class-wp-rest-autosave-controller.php @@ -1,6 +1,6 @@ Date: Sun, 24 Dec 2017 14:20:53 -0500 Subject: [PATCH 24/62] remove unused addQueryArgs --- editor/store/effects.js | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index 163927cd5463ed..6db6d8c891d7d6 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -17,7 +17,6 @@ import { getDefaultBlockName, } from '@wordpress/blocks'; import { __ } from '@wordpress/i18n'; -import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies From 9d012483760dbaf9bf0339bdd15c50a4326c2fe1 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:22:28 -0500 Subject: [PATCH 25/62] remove gutenberg_handle_rest_pre_insert --- gutenberg.php | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index d43e1509f081f8..4d4351201ca93c 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -162,43 +162,6 @@ function gutenberg_init( $return, $post ) { return true; } -/** - * In rest_pre_insert, possibly create an autosave. - * - * @param stdClass $prepared_post An object representing a single post prepared - * for inserting or updating the database. - * @param WP_REST_Request $request Request object. - * - * @return array The prepared post data. - */ -function gutenberg_handle_rest_pre_insert( $prepared_post ) { - if ( isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] ) { - if ( ! current_user_can( 'edit_post', (int) $prepared_post->ID ) ) { - return $prepared_post; - } - - // Map new fields onto the existing post data. - $existing_post = get_post( (int) $prepared_post->ID ); - foreach( $prepared_post as $key => $value ) { - $existing_post->$key = $value; - } - - $autosave_id = gutenberg_create_post_autosave( (array) $existing_post ); - - // Pass the autosave id as part of the error data so we can return the autosave data later. - return new WP_Error( - 'gutenberg_create_post_autosave_interrupt', - __( 'Interrupt normal post saving to create an autosave for Gutenberg.', 'gutenberg' ), - array( - 'autosave_id' => $autosave_id, - ) - ); - } - - return $prepared_post; - -} - /** * Emulate post.php */ From 9dad2ff318c8bac1c9050d1ed030de2a70d4eb25 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:24:41 -0500 Subject: [PATCH 26/62] rename class file to match class name --- ...ve-controller.php => class-wp-rest-autosaves-controller.php} | 0 lib/load.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/{class-wp-rest-autosave-controller.php => class-wp-rest-autosaves-controller.php} (100%) diff --git a/lib/class-wp-rest-autosave-controller.php b/lib/class-wp-rest-autosaves-controller.php similarity index 100% rename from lib/class-wp-rest-autosave-controller.php rename to lib/class-wp-rest-autosaves-controller.php diff --git a/lib/load.php b/lib/load.php index eec1ca1395c3ab..afc198b8101f91 100644 --- a/lib/load.php +++ b/lib/load.php @@ -13,7 +13,7 @@ require dirname( __FILE__ ) . '/class-wp-block-type.php'; require dirname( __FILE__ ) . '/class-wp-block-type-registry.php'; require dirname( __FILE__ ) . '/class-wp-rest-reusable-blocks-controller.php'; -require dirname( __FILE__ ) . '/class-wp-rest-autosave-controller.php'; +require dirname( __FILE__ ) . '/class-wp-rest-autosaves-controller.php'; require dirname( __FILE__ ) . '/blocks.php'; require dirname( __FILE__ ) . '/client-assets.php'; require dirname( __FILE__ ) . '/compat.php'; From 471b5071716df2e68ffe8644c692c070a185c28a Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Sun, 24 Dec 2017 14:25:42 -0500 Subject: [PATCH 27/62] remove unused gutenberg_add_rest_endpoints --- gutenberg.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/gutenberg.php b/gutenberg.php index 4d4351201ca93c..9d18814676f898 100644 --- a/gutenberg.php +++ b/gutenberg.php @@ -121,15 +121,6 @@ function gutenberg_pre_init() { } } -function gutenberg_add_rest_endpoints() { - - if ( isset( $_GET['gutenberg_autosave'] ) && '1' === $_GET['gutenberg_autosave'] ) { - $post_id = (int) $data->id; - $post = get_post( $post_id ); - } - -} -add_action( 'rest_api_init', 'gutenberg_add_rest_endpoints' ); /** * Initialize Gutenberg. * From 8928813cc13328f382a24da0f09406324f75d17e Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 09:07:15 -0500 Subject: [PATCH 28/62] =?UTF-8?q?mobile:=20don=E2=80=99t=20display=20save?= =?UTF-8?q?=20link=20for=20published=20posts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editor/components/post-saved-state/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/components/post-saved-state/index.js b/editor/components/post-saved-state/index.js index 3c975dd8d61a0c..51be559565329f 100644 --- a/editor/components/post-saved-state/index.js +++ b/editor/components/post-saved-state/index.js @@ -59,7 +59,7 @@ export function PostSavedState( { isNew, isPublished, isDirty, isSaving, isSavea return ( ); From 76e7973939a630f275302a0090e0dd9b35c13107 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 09:08:40 -0500 Subject: [PATCH 29/62] remove unused autosavePost options --- editor/store/actions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/store/actions.js b/editor/store/actions.js index 954ad1a24695b7..2f1807489f8567 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -240,10 +240,9 @@ export function savePost() { }; } -export function autosavePost( options ) { +export function autosavePost() { return { type: 'REQUEST_POST_AUTOSAVE', - options, }; } From bf03be91a44e97ecc7906b8077c5a396a9bb3ceb Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 09:51:59 -0500 Subject: [PATCH 30/62] combine save and autosave actions, removing REQUEST_POST_AUTOSAVE --- editor/store/actions.js | 9 +---- editor/store/effects.js | 81 +++++++++++++++++++++++++++-------------- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/editor/store/actions.js b/editor/store/actions.js index 2f1807489f8567..aba2ecfd11505a 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -234,15 +234,10 @@ export function editPost( edits ) { }; } -export function savePost() { +export function savePost( options ) { return { type: 'REQUEST_POST_UPDATE', - }; -} - -export function autosavePost() { - return { - type: 'REQUEST_POST_AUTOSAVE', + options, }; } diff --git a/editor/store/effects.js b/editor/store/effects.js index 6db6d8c891d7d6..0fff166083d334 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -32,7 +32,6 @@ import { createErrorNotice, removeNotice, savePost, - autosavePost, editPost, requestMetaBoxUpdates, updateReusableBlock, @@ -62,15 +61,15 @@ const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; const SAVE_REUSABLE_BLOCK_NOTICE_ID = 'SAVE_REUSABLE_BLOCK_NOTICE_ID'; export default { - REQUEST_POST_AUTOSAVE( action, store ) { + REQUEST_POST_AUTOSAVE ( action, store ) { const { dispatch, getState } = store; - const state = getState(); - const post = getCurrentPost( state ); - const edits = getPostEdits( state ); + const state = getState(); + const post = getCurrentPost( state ); + const edits = getPostEdits( state ); const toSend = { ...edits, content: getEditedPostContent( state ), - parent: post.id, + parent: post.id, }; const Model = wp.api.getPostTypeAutosaveModel( getCurrentPostType( state ) ); @@ -104,25 +103,51 @@ export default { content: getEditedPostContent( state ), id: post.id, }; + const isAutosave = action.options && action.options.autosave; + let Model, newModel; - dispatch( { - type: 'UPDATE_POST', - edits: toSend, - optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, - } ); - dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); - const Model = wp.api.getPostTypeModel( getCurrentPostType( state ) ); - new Model( toSend ).save().done( ( newPost ) => { - dispatch( { - type: 'RESET_POST', - post: newPost, - } ); + if ( isAutosave ) { + toSend.parent = post.id; + delete toSend.id ; + Model = wp.api.getPostTypeAutosaveModel( getCurrentPostType( state ) ); + newModel = new Model( toSend ); + } else { dispatch( { - type: 'REQUEST_POST_UPDATE_SUCCESS', - previousPost: post, - post: newPost, - optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, + type: 'UPDATE_POST', + edits: toSend, + optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, } ); + dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); + Model = wp.api.getPostTypeModel( getCurrentPostType( state ) ); + newModel = new Model( toSend ); + } + + newModel.save().done( ( newPost ) => { + if ( isAutosave ) { + post.modified = newPost.modified; + post.modified_gmt = newPost.modified_gmt; + dispatch( { + type: 'RESET_POST', + post: post, + } ); + dispatch( { + type: 'REQUEST_POST_UPDATE_SUCCESS', + previousPost: post, + post: post, + isAutosave: true + } ); + } else { + dispatch( { + type: 'RESET_POST', + post: newPost, + } ); + dispatch( { + type: 'REQUEST_POST_UPDATE_SUCCESS', + previousPost: post, + post: newPost, + optimist: { type: COMMIT, id: POST_UPDATE_TRANSACTION_ID }, + } ); + } } ).fail( ( err ) => { dispatch( { type: 'REQUEST_POST_UPDATE_FAILURE', @@ -132,12 +157,12 @@ export default { } ), post, edits, - optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, + optimist: isAutosave ? false : { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, } ); } ); }, REQUEST_POST_UPDATE_SUCCESS( action, store ) { - const { previousPost, post } = action; + const { previousPost, post, isAutosave } = action; const { dispatch, getState } = store; const publishStatus = [ 'publish', 'private', 'future' ]; @@ -162,8 +187,10 @@ export default { future: __( 'Post scheduled!' ), }[ post.status ]; } else { - // Generic fallback notice - noticeMessage = __( 'Post updated!' ); + if ( ! isAutosave ) { + // Generic fallback notice + noticeMessage = __( 'Post updated!' ); + } } if ( noticeMessage ) { @@ -307,7 +334,7 @@ export default { return; } - dispatch( autosavePost() ); + dispatch( savePost( { 'autosave': true } ) ); }, SETUP_EDITOR( action ) { const { post, settings } = action; From 5ba2bf11f23828b49c4c47ab263621553ae294cf Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 11:32:58 -0500 Subject: [PATCH 31/62] isEditedPostNew checked above, remove duplicate check --- editor/store/effects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index 0fff166083d334..a1b9533dadfc5b 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -330,7 +330,7 @@ export default { return; } - if ( ! isEditedPostNew( state ) && ! isEditedPostDirty( state ) ) { + if ( ! isEditedPostDirty( state ) ) { return; } From ccd6ac6654153fd106871fe7364bb34e6138bf6b Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 17:18:44 -0500 Subject: [PATCH 32/62] Add isAutosavingPost selector --- editor/store/selectors.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/editor/store/selectors.js b/editor/store/selectors.js index 50ee60e188a4dd..8c9686ab091baf 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -946,6 +946,16 @@ export function didPostSaveRequestFail( state ) { return !! state.saving.error; } +/** + * Is the post autosaving? + * + * @param {Object} state Global application state + * @return {Boolean} Whether the post is autosaving + */ +export function isAutosavingPost( state ) { + return !! state.isAutosaving.isAutosaving; +} + /** * Returns a suggested post format for the current post, inferred only if there * is a single block within the post and it is of a type known to match a From 2e887a2a34658c4e810b394a950abbd935361295 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 17:19:12 -0500 Subject: [PATCH 33/62] Add isAutosaving reducer --- editor/store/reducer.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/editor/store/reducer.js b/editor/store/reducer.js index 0def9eeed7bd61..1af6e7e8b45a88 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -473,6 +473,19 @@ export function blocksMode( state = {}, action ) { return state; } +export function isAutosaving( state = {}, action ) { + const { isAutosaving } = action; + switch ( action.type ) { + case 'DOING_AUTOSAVE': + return { + ...state, + isAutosaving: isAutosaving, + }; + } + + return state; +} + /** * Reducer returning the block insertion point * @@ -785,6 +798,7 @@ export const reusableBlocks = combineReducers( { export default optimist( combineReducers( { editor, + isAutosaving, currentPost, isTyping, blockSelection, From 22cfcfd436ffbf1b67a938f257a7360031b2868b Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 17:19:45 -0500 Subject: [PATCH 34/62] remove REQUEST_POST_AUTOSAVE, dispatch isAutosaving --- editor/store/effects.js | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index a1b9533dadfc5b..abadd3a0c17ae9 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -32,6 +32,7 @@ import { createErrorNotice, removeNotice, savePost, + isAutosaving, editPost, requestMetaBoxUpdates, updateReusableBlock, @@ -61,38 +62,6 @@ const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; const SAVE_REUSABLE_BLOCK_NOTICE_ID = 'SAVE_REUSABLE_BLOCK_NOTICE_ID'; export default { - REQUEST_POST_AUTOSAVE ( action, store ) { - const { dispatch, getState } = store; - const state = getState(); - const post = getCurrentPost( state ); - const edits = getPostEdits( state ); - const toSend = { - ...edits, - content: getEditedPostContent( state ), - parent: post.id, - }; - - const Model = wp.api.getPostTypeAutosaveModel( getCurrentPostType( state ) ); - const newModel = new Model( toSend ); - - newModel.save().done( ( newPost ) => { - dispatch( { - type: 'RESET_POST', - post: post, - } ); - } ).fail( ( err ) => { - dispatch( { - type: 'REQUEST_POST_AUTOSAVE_FAILURE', - error: get( err, 'responseJSON', { - code: 'unknown_error', - message: __( 'An unknown error occurred.' ), - } ), - post, - edits, - optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID }, - } ); - } ); - }, REQUEST_POST_UPDATE( action, store ) { const { dispatch, getState } = store; const state = getState(); @@ -124,6 +93,7 @@ export default { newModel.save().done( ( newPost ) => { if ( isAutosave ) { + dispatch( isAutosaving( false ) ); post.modified = newPost.modified; post.modified_gmt = newPost.modified_gmt; dispatch( { @@ -149,6 +119,10 @@ export default { } ); } } ).fail( ( err ) => { + if ( isAutosave ) { + dispatch( isAutosaving( false ) ); + } + dispatch( { type: 'REQUEST_POST_UPDATE_FAILURE', error: get( err, 'responseJSON', { @@ -334,6 +308,7 @@ export default { return; } + dispatch( isAutosaving( true ) ); dispatch( savePost( { 'autosave': true } ) ); }, SETUP_EDITOR( action ) { From 96a348b9fa15610f4d282614e156568e10f58619 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 17:20:03 -0500 Subject: [PATCH 35/62] Add isAutosaving action --- editor/store/actions.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/editor/store/actions.js b/editor/store/actions.js index aba2ecfd11505a..4f3503f3778be0 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -267,6 +267,13 @@ export function autosave() { }; } +export function isAutosaving( isAutosaving ) { + return { + type: 'DOING_AUTOSAVE', + isAutosaving, + } +} + /** * Returns an action object used in signalling that undo history should * restore last popped state. From bf1ab9d979c5a567354146933e07d2cbc2eab295 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 17:20:40 -0500 Subject: [PATCH 36/62] Disable update/publish button during autosaves --- .../components/post-publish-button/index.js | 4 +++- .../post-publish-with-dropdown/index.js | 19 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/editor/components/post-publish-button/index.js b/editor/components/post-publish-button/index.js index 20e551f7cc12d1..b9e78e1cd1efa2 100644 --- a/editor/components/post-publish-button/index.js +++ b/editor/components/post-publish-button/index.js @@ -23,6 +23,7 @@ import { getEditedPostVisibility, isEditedPostSaveable, isEditedPostPublishable, + isAutosavingPost, } from '../../store/selectors'; export function PostPublishButton( { @@ -51,7 +52,7 @@ export function PostPublishButton( { } const className = classnames( 'editor-post-publish-button', { - 'is-saving': isSaving, + 'is-saving': isSaving && ! isAutosavingPost, } ); const onClick = () => { @@ -80,6 +81,7 @@ const applyConnect = connect( visibility: getEditedPostVisibility( state ), isSaveable: isEditedPostSaveable( state ), isPublishable: isEditedPostPublishable( state ), + isAutosaving: isAutosavingPost( state ), } ), { onStatusChange: ( status ) => editPost( { status } ), diff --git a/editor/components/post-publish-with-dropdown/index.js b/editor/components/post-publish-with-dropdown/index.js index 43fa01157bab6e..01073c5d7c13fb 100644 --- a/editor/components/post-publish-with-dropdown/index.js +++ b/editor/components/post-publish-with-dropdown/index.js @@ -19,12 +19,20 @@ import { isEditedPostSaveable, isEditedPostPublishable, isCurrentPostPublished, + isAutosavingPost, } from '../../store/selectors'; -function PostPublishWithDropdown( { isSaving, isPublishable, isSaveable, isPublished } ) { - const isButtonEnabled = ( - ! isSaving && isPublishable && isSaveable - ) || isPublished; +function PostPublishWithDropdown( { isSaving, isPublishable, isSaveable, isPublished, isAutosaving } ) { + + const isButtonEnabled = + ( + ( + ! isSaving && + isPublishable && + isSaveable + ) || isPublished + ) && + ! isAutosaving; return ( @@ -54,5 +62,6 @@ export default connect( isSaveable: isEditedPostSaveable( state ), isPublishable: isEditedPostPublishable( state ), isPublished: isCurrentPostPublished( state ), + isAutosaving: isAutosavingPost( state ), } ), )( PostPublishWithDropdown ); From 5bf6c7f665644a2589f6b46af1e5a7951344b57d Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 20:52:36 -0500 Subject: [PATCH 37/62] Add an autosaving indicator --- editor/components/post-saved-state/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/editor/components/post-saved-state/index.js b/editor/components/post-saved-state/index.js index 51be559565329f..800b301db0261e 100644 --- a/editor/components/post-saved-state/index.js +++ b/editor/components/post-saved-state/index.js @@ -23,15 +23,16 @@ import { isEditedPostSaveable, getCurrentPost, getEditedPostAttribute, + isAutosavingPost, } from '../../store/selectors'; -export function PostSavedState( { isNew, isPublished, isDirty, isSaving, isSaveable, status, onStatusChange, onSave } ) { +export function PostSavedState ( { isNew, isPublished, isDirty, isSaving, isSaveable, status, onStatusChange, onSave, isAutosaving } ) { const className = 'editor-post-saved-state'; if ( isSaving ) { return ( - { __( 'Saving' ) } + { isAutosaving ? __( 'Autosaving' ) : __( 'Saving' ) } ); } @@ -74,6 +75,7 @@ export default connect( isSaving: isSavingPost( state ), isSaveable: isEditedPostSaveable( state ), status: getEditedPostAttribute( state, 'status' ), + isAutosaving: isAutosavingPost( state ), } ), { onStatusChange: ( status ) => editPost( { status } ), From a0b5858fe30bcfa12eb04ecfa5ddc6728106a5d3 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Tue, 26 Dec 2017 20:53:26 -0500 Subject: [PATCH 38/62] improve autosave monitor --- editor/components/autosave-monitor/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/editor/components/autosave-monitor/index.js b/editor/components/autosave-monitor/index.js index f47f126ab054d3..7f89ff144fec72 100644 --- a/editor/components/autosave-monitor/index.js +++ b/editor/components/autosave-monitor/index.js @@ -15,12 +15,13 @@ import { autosave } from '../../store/actions'; import { isEditedPostDirty, isEditedPostSaveable, + isAutosavingPost, } from '../../store/selectors'; export class AutosaveMonitor extends Component { componentDidUpdate( prevProps ) { const { isDirty, isSaveable } = this.props; - if ( prevProps.isDirty !== isDirty || + if ( isDirty || prevProps.isSaveable !== isSaveable ) { this.toggleTimer( isDirty && isSaveable ); } @@ -51,6 +52,7 @@ export default connect( return { isDirty: isEditedPostDirty( state ), isSaveable: isEditedPostSaveable( state ), + isAutosaving: isAutosavingPost( state ), }; }, { autosave } From 4d840740b7bf8195cfbd3b33848d3371221ebe67 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 27 Dec 2017 22:12:21 -0500 Subject: [PATCH 39/62] first pass: selectors for isPostAutosavable --- editor/store/selectors.js | 42 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/editor/store/selectors.js b/editor/store/selectors.js index 8c9686ab091baf..fe0f170d78755c 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -352,6 +352,46 @@ export function isEditedPostSaveable( state ) { ); } +/** + * Returns true if the post can be autosaved, or false otherwise. + * + * @param {Object} state Global application state + * @return {Boolean} Whether the post can be autosaved + */ +export function isPostAutosavable( state ) { + + // If the post is autosaving, it is not autosavable. + if ( state.isAutosaving ) { + return false; + } + + // If we don't already have an autosave, the post is autosavable. + if ( ! hasAutosave( state ) ) { + return true; + } + + const title = getEditedPostTitle( state ); + const excerpt = getEditedPostExcerpt( state ); + const content = getEditedPostContent( state ); + const autosave = state.editor.present.autosave; + + // If the title, excerpt or content has changed, the post is autosavable. + if ( + autosave.title && title !== autosave.title || + autosave.excerpt && excerpt !== autosave.excerpt || + autosave.content && content !== autosave.content + ) { + return true; + } + + return false; +} + +export function hasAutosave( state ) { + return !! state.editor.present.autosave; +} + + /** * Return true if the post being edited is being scheduled. Preferring the * unsaved status values. @@ -953,7 +993,7 @@ export function didPostSaveRequestFail( state ) { * @return {Boolean} Whether the post is autosaving */ export function isAutosavingPost( state ) { - return !! state.isAutosaving.isAutosaving; + return !! state.isAutosaving; } /** From 14147c69fbe462774cdc8adb53d72054f6030270 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 27 Dec 2017 22:12:42 -0500 Subject: [PATCH 40/62] autosave reducer; improve isAutosaving --- editor/store/reducer.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/editor/store/reducer.js b/editor/store/reducer.js index 1af6e7e8b45a88..e0a6b70eab8501 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -77,6 +77,15 @@ export const editor = flow( [ // resetting at each post save. partialRight( withChangeDetection, { resetTypes: [ 'SETUP_EDITOR', 'RESET_POST' ] } ), ] )( { + autosave ( state = false, action ) { + const { post } = action; + switch ( action.type ) { + case 'RESET_AUTOSAVE': + return post; + } + + return state; + }, edits( state = {}, action ) { switch ( action.type ) { case 'EDIT_POST': @@ -473,14 +482,11 @@ export function blocksMode( state = {}, action ) { return state; } -export function isAutosaving( state = {}, action ) { - const { isAutosaving } = action; +export function isAutosaving( state = false, action ) { switch ( action.type ) { case 'DOING_AUTOSAVE': - return { - ...state, - isAutosaving: isAutosaving, - }; + const { isAutosaving } = action; + return isAutosaving; } return state; From 557d653b3eefdfe0c8c8c100227e35f58b074e9c Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 27 Dec 2017 22:13:53 -0500 Subject: [PATCH 41/62] store the autosaved data --- editor/store/effects.js | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index abadd3a0c17ae9..2e4d17031e3984 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -45,6 +45,8 @@ import { getDirtyMetaBoxes, getEditedPostContent, getPostEdits, + getEditedPostTitle, + getEditedPostExcerpt, isCurrentPostPublished, isEditedPostDirty, isEditedPostNew, @@ -93,20 +95,27 @@ export default { newModel.save().done( ( newPost ) => { if ( isAutosave ) { - dispatch( isAutosaving( false ) ); - post.modified = newPost.modified; - post.modified_gmt = newPost.modified_gmt; + const autosave = { + id: newPost.id, + title: getEditedPostTitle( state ), + excerpt: getEditedPostExcerpt( state ), + content: getEditedPostContent( state ), + }; dispatch( { - type: 'RESET_POST', - post: post, + type: 'RESET_AUTOSAVE', + post: autosave, } ); + dispatch( isAutosaving( false ) ); + dispatch( { type: 'REQUEST_POST_UPDATE_SUCCESS', previousPost: post, post: post, - isAutosave: true + isAutosave: true, } ); } else { + // dispatch post autosaved false + // delete the autosave dispatch( { type: 'RESET_POST', post: newPost, From a988bb801b9c0e7b2da1e47bbbc408b8cfeada26 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Wed, 27 Dec 2017 22:14:27 -0500 Subject: [PATCH 42/62] autosave monitor - add isAutosavable check --- editor/components/autosave-monitor/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/editor/components/autosave-monitor/index.js b/editor/components/autosave-monitor/index.js index 7f89ff144fec72..f36a403ce86735 100644 --- a/editor/components/autosave-monitor/index.js +++ b/editor/components/autosave-monitor/index.js @@ -15,15 +15,15 @@ import { autosave } from '../../store/actions'; import { isEditedPostDirty, isEditedPostSaveable, - isAutosavingPost, + isPostAutosavable, } from '../../store/selectors'; export class AutosaveMonitor extends Component { componentDidUpdate( prevProps ) { - const { isDirty, isSaveable } = this.props; - if ( isDirty || - prevProps.isSaveable !== isSaveable ) { - this.toggleTimer( isDirty && isSaveable ); + const { isDirty, isSaveable, isAutosavable } = this.props; + + if ( isDirty && isSaveable && isAutosavable ) { + this.toggleTimer( true ); } } @@ -52,7 +52,7 @@ export default connect( return { isDirty: isEditedPostDirty( state ), isSaveable: isEditedPostSaveable( state ), - isAutosaving: isAutosavingPost( state ), + isAutosavable: isPostAutosavable( state ), }; }, { autosave } From dd2d9b4ba10ef53b3e55238ad97b0d5837641454 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 11:54:53 -0500 Subject: [PATCH 43/62] remove unused block controller code after merge --- lib/register.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/register.php b/lib/register.php index ac9484ed2ca36c..fa127b6f04edf9 100644 --- a/lib/register.php +++ b/lib/register.php @@ -413,9 +413,6 @@ function gutenberg_register_post_types() { * @since 0.10.0 */ function gutenberg_register_rest_routes() { - $controller = new WP_REST_Reusable_Blocks_Controller(); - $controller->register_routes(); - foreach ( get_post_types( array(), 'objects' ) as $post_type_object ) { if( ! empty( $post_type_object->rest_base ) ) { if ( post_type_supports( $post_type_object->name, 'revisions' ) ) { From 3ee73263b12d8f5be9f33689578f31d4e48310f8 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 11:55:12 -0500 Subject: [PATCH 44/62] localize autosave data as _wpAutosave --- lib/client-assets.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index af94bc2f552341..6ba98de85c5442 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -733,6 +733,22 @@ function gutenberg_editor_scripts_and_styles( $hook ) { wp_die( $post_to_edit->get_error_message() ); } + // Add autosave data. + $autosave = wp_get_post_autosave( $post->ID ); + wp_localize_script( + 'wp-editor', + '_wpAutosave', + $autosave ? + array( + 'id' => $autosave->ID, + 'title' => $autosave->post_title, + 'excerpt' => $autosave->post_excerpt, + 'content' => $autosave->post_content, + 'edit_link' => get_edit_post_link( $autosave->ID ), + ) : + false + ); + // Set initial title to empty string for auto draft for duration of edit. // Otherwise, title defaults to and displays as "Auto Draft". $is_new_post = 'auto-draft' === $post_to_edit['status']; @@ -839,7 +855,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $script .= sprintf( 'var editorSettings = %s;', wp_json_encode( $editor_settings ) ); $script .= << Date: Thu, 28 Dec 2017 11:55:54 -0500 Subject: [PATCH 45/62] REQUEST_AUTOSAVE_EXISTS effect - show the autosave exists notice --- editor/store/effects.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/editor/store/effects.js b/editor/store/effects.js index a7697994ff310f..5c8d99490ea49d 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -30,6 +30,7 @@ import { replaceBlocks, createSuccessNotice, createErrorNotice, + createWarningNotice, removeNotice, savePost, isAutosaving, @@ -60,6 +61,7 @@ import { * Module Constants */ const SAVE_POST_NOTICE_ID = 'SAVE_POST_NOTICE_ID'; +const AUTOSAVE_POST_NOTICE_ID = 'AUTOSAVE_POST_NOTICE_ID'; const TRASH_POST_NOTICE_ID = 'TRASH_POST_NOTICE_ID'; const SAVE_REUSABLE_BLOCK_NOTICE_ID = 'SAVE_REUSABLE_BLOCK_NOTICE_ID'; @@ -89,6 +91,7 @@ export default { optimist: { type: BEGIN, id: POST_UPDATE_TRANSACTION_ID }, } ); dispatch( removeNotice( SAVE_POST_NOTICE_ID ) ); + dispatch( removeNotice( AUTOSAVE_POST_NOTICE_ID ) ); Model = wp.api.getPostTypeModel( getCurrentPostType( state ) ); newModel = new Model( toSend ); } @@ -144,6 +147,23 @@ export default { } ); } ); }, + REQUEST_AUTOSAVE_EXISTS( action, store ) { + const { autosave } = action; + const { dispatch, getState } = store; + if ( autosave ) { + dispatch( createWarningNotice( +

+ { __( 'There is an autosave of this post that is more recent than the version below.' ) } + { ' ' } + { { __( 'View the autosave' ) } } +

, + { + id: AUTOSAVE_POST_NOTICE_ID, + } + ) ); + + } + }, REQUEST_POST_UPDATE_SUCCESS( action, store ) { const { previousPost, post, isAutosave } = action; const { dispatch, getState } = store; From 91a07c0bc3bd30d783dc8abacb48d1345b89b7e3 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 11:56:14 -0500 Subject: [PATCH 46/62] add an autosaveAlert action --- editor/store/actions.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/editor/store/actions.js b/editor/store/actions.js index d4a6b6f1452a96..661d028a30c41e 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -260,6 +260,13 @@ export function isAutosaving( isAutosaving ) { } } +export function autosaveAlert( autosave ) { + return { + type: 'REQUEST_AUTOSAVE_EXISTS', + autosave, + } +} + /** * Returns an action object used in signalling that undo history should * restore last popped state. From b2b89ec759d9c0204d31cdc4a2f616fb75b5a51e Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 11:58:24 -0500 Subject: [PATCH 47/62] When initializing the editor, show the autosave alert --- editor/components/provider/index.js | 8 +++++++- editor/index.js | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/editor/components/provider/index.js b/editor/components/provider/index.js index 7a0342faf30d3e..a7d035b7773730 100644 --- a/editor/components/provider/index.js +++ b/editor/components/provider/index.js @@ -19,7 +19,7 @@ import { /** * Internal Dependencies */ -import { setupEditor, undo } from '../../store/actions'; +import { setupEditor, undo, autosaveAlert } from '../../store/actions'; import store from '../../store'; /** @@ -52,6 +52,12 @@ class EditorProvider extends Component { // Assume that we don't need to initialize in the case of an error recovery. if ( ! props.recovery ) { this.store.dispatch( setupEditor( props.post, this.settings ) ); + + // @todo only show the autosave alert when there exists an autosave newer than the post + // and if that autosave is different than the post (title, excerpt & content) + if ( props.autosave ) { + this.store.dispatch( autosaveAlert( props.autosave ) ); + } } } diff --git a/editor/index.js b/editor/index.js index 2cd98d2b73bfb6..7a1d3565996290 100644 --- a/editor/index.js +++ b/editor/index.js @@ -80,12 +80,12 @@ export function recreateEditorInstance( target, settings ) { * @param {?Object} settings Editor settings object * @return {Object} Editor interface */ -export function createEditorInstance( id, post, settings ) { +export function createEditorInstance( id, post, settings, autosave ) { const target = document.getElementById( id ); const reboot = recreateEditorInstance.bind( null, target, settings ); render( - + From ecbf4990d179e0e26d1e94df3256855851972d3e Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 14:41:41 -0500 Subject: [PATCH 48/62] Only add autosave data if it is newer and changed. --- lib/client-assets.php | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 6ba98de85c5442..fa7f391132574e 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -733,21 +733,37 @@ function gutenberg_editor_scripts_and_styles( $hook ) { wp_die( $post_to_edit->get_error_message() ); } - // Add autosave data. + // Add autosave data if it is newer and changed. $autosave = wp_get_post_autosave( $post->ID ); - wp_localize_script( - 'wp-editor', - '_wpAutosave', - $autosave ? + $show_autosave = false; + + // Is the autosave newer than the post? + if ( + $autosave && + mysql2date( 'U', $autosave->post_modified_gmt, false ) > mysql2date( 'U', $post->post_modified_gmt, false ) + ) { + foreach ( _wp_post_revision_fields( $post ) as $autosave_field => $_autosave_field ) { + if ( normalize_whitespace( $autosave->$autosave_field ) != normalize_whitespace( $post->$autosave_field ) ) { + $show_autosave = true; + break; + } + } + } + + + if ( $show_autosave ) { + wp_localize_script( + 'wp-editor', + '_wpAutosave', array( - 'id' => $autosave->ID, - 'title' => $autosave->post_title, - 'excerpt' => $autosave->post_excerpt, - 'content' => $autosave->post_content, - 'edit_link' => get_edit_post_link( $autosave->ID ), - ) : - false - ); + 'id' => $autosave->ID, + 'title' => $autosave->post_title, + 'excerpt' => $autosave->post_excerpt, + 'content' => $autosave->post_content, + 'edit_link' => add_query_arg( 'gutenberg', true, get_edit_post_link( $autosave->ID ) ), + ) + ); + } // Set initial title to empty string for auto draft for duration of edit. // Otherwise, title defaults to and displays as "Auto Draft". From 19dabd91b5b0fa3b9497fa63f8bb927753bede74 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 14:41:56 -0500 Subject: [PATCH 49/62] If this autosave isn't newer and different from the current post, remove. --- lib/client-assets.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/client-assets.php b/lib/client-assets.php index fa7f391132574e..5227787d029947 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -750,6 +750,10 @@ function gutenberg_editor_scripts_and_styles( $hook ) { } } + // If this autosave isn't newer and different from the current post, remove. + if ( $autosave && ! $show_autosave ) { + wp_delete_post_revision( $autosave->ID ); + } if ( $show_autosave ) { wp_localize_script( From 79655792b6c983724cbe5b369a8093ddae7c3f2e Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 14:52:48 -0500 Subject: [PATCH 50/62] remove unused localised autosave data --- lib/client-assets.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/client-assets.php b/lib/client-assets.php index 5227787d029947..d0e0f6701c2235 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -761,9 +761,6 @@ function gutenberg_editor_scripts_and_styles( $hook ) { '_wpAutosave', array( 'id' => $autosave->ID, - 'title' => $autosave->post_title, - 'excerpt' => $autosave->post_excerpt, - 'content' => $autosave->post_content, 'edit_link' => add_query_arg( 'gutenberg', true, get_edit_post_link( $autosave->ID ) ), ) ); From 62230fd3046f9524c64d740d42acfa0127bb9adc Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Thu, 28 Dec 2017 14:53:02 -0500 Subject: [PATCH 51/62] update inline docs --- editor/store/effects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index 5c8d99490ea49d..120cae0975d82a 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -326,7 +326,7 @@ export default { return; } - // Change status from auto-draft to draft + // Change status from auto-draft to draft, saving the post. if ( isEditedPostNew( state ) ) { dispatch( editPost( { status: 'draft' } ) ); dispatch( savePost() ); From 2dd8774529c0242dc71372a92729c68f6d178ba5 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 11:03:41 -0500 Subject: [PATCH 52/62] rename autosaveAlert-> showAutosaveAlert --- editor/components/provider/index.js | 4 ++-- editor/store/actions.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/editor/components/provider/index.js b/editor/components/provider/index.js index a7d035b7773730..b9578ea603537c 100644 --- a/editor/components/provider/index.js +++ b/editor/components/provider/index.js @@ -19,7 +19,7 @@ import { /** * Internal Dependencies */ -import { setupEditor, undo, autosaveAlert } from '../../store/actions'; +import { setupEditor, undo, showAutosaveAlert } from '../../store/actions'; import store from '../../store'; /** @@ -56,7 +56,7 @@ class EditorProvider extends Component { // @todo only show the autosave alert when there exists an autosave newer than the post // and if that autosave is different than the post (title, excerpt & content) if ( props.autosave ) { - this.store.dispatch( autosaveAlert( props.autosave ) ); + this.store.dispatch( showAutosaveAlert( props.autosave ) ); } } } diff --git a/editor/store/actions.js b/editor/store/actions.js index 661d028a30c41e..1c5f69c0558987 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -260,7 +260,7 @@ export function isAutosaving( isAutosaving ) { } } -export function autosaveAlert( autosave ) { +export function showAutosaveAlert( autosave ) { return { type: 'REQUEST_AUTOSAVE_EXISTS', autosave, From 765ae5d579b23121bc169a5cf7dc7496f85d5fff Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 11:06:43 -0500 Subject: [PATCH 53/62] rename action isAutosaving->toggleAutosave --- editor/store/actions.js | 2 +- editor/store/effects.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/store/actions.js b/editor/store/actions.js index 1c5f69c0558987..24835a782dc523 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -253,7 +253,7 @@ export function autosave() { }; } -export function isAutosaving( isAutosaving ) { +export function toggleAutosave( isAutosaving ) { return { type: 'DOING_AUTOSAVE', isAutosaving, diff --git a/editor/store/effects.js b/editor/store/effects.js index 120cae0975d82a..ad078bd6178dfd 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -108,7 +108,7 @@ export default { type: 'RESET_AUTOSAVE', post: autosave, } ); - dispatch( isAutosaving( false ) ); + dispatch( toggleAutosave( false ) ); dispatch( { type: 'REQUEST_POST_UPDATE_SUCCESS', @@ -132,7 +132,7 @@ export default { } } ).fail( ( err ) => { if ( isAutosave ) { - dispatch( isAutosaving( false ) ); + dispatch( toggleAutosave( false ) ); } dispatch( { @@ -337,7 +337,7 @@ export default { return; } - dispatch( isAutosaving( true ) ); + dispatch( toggleAutosave( true ) ); dispatch( savePost( { 'autosave': true } ) ); }, SETUP_EDITOR( action ) { From 06be44d68cbd74975d7abca94c6112266fe0b705 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 11:07:24 -0500 Subject: [PATCH 54/62] fix autosaving check in post-publish-button --- editor/components/post-publish-button/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/components/post-publish-button/index.js b/editor/components/post-publish-button/index.js index eed16de27b8ca5..fa6f4ac2265196 100644 --- a/editor/components/post-publish-button/index.js +++ b/editor/components/post-publish-button/index.js @@ -35,6 +35,7 @@ export function PostPublishButton( { visibility, isPublishable, isSaveable, + isAutosaving, user, onSubmit = noop, } ) { @@ -53,7 +54,7 @@ export function PostPublishButton( { } const className = classnames( 'editor-post-publish-button', { - 'is-saving': isSaving && ! isAutosavingPost, + 'is-saving': isSaving && ! isAutosaving, } ); const onClick = () => { From 4bdfad9f8f6f90e525a884bf2b94a8984c38b7f2 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 11:08:14 -0500 Subject: [PATCH 55/62] remove duplicate line, out of date todo --- editor/components/provider/index.js | 2 -- lib/class-wp-rest-autosaves-controller.php | 1 - 2 files changed, 3 deletions(-) diff --git a/editor/components/provider/index.js b/editor/components/provider/index.js index b9578ea603537c..def2d74bd44965 100644 --- a/editor/components/provider/index.js +++ b/editor/components/provider/index.js @@ -53,8 +53,6 @@ class EditorProvider extends Component { if ( ! props.recovery ) { this.store.dispatch( setupEditor( props.post, this.settings ) ); - // @todo only show the autosave alert when there exists an autosave newer than the post - // and if that autosave is different than the post (title, excerpt & content) if ( props.autosave ) { this.store.dispatch( showAutosaveAlert( props.autosave ) ); } diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php index e769a48c45d5c0..911b6bd07f666c 100644 --- a/lib/class-wp-rest-autosaves-controller.php +++ b/lib/class-wp-rest-autosaves-controller.php @@ -154,7 +154,6 @@ public function create_item( $request ) { // Map new fields onto the existing post data. $parent = $this->revision_controller->get_parent( $request['parent'] ); $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); - $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); $prepared_post->ID = $parent->ID; $autosave_id = $this->create_post_autosave( (array) $prepared_post ); $post = get_post( $autosave_id ); From c1ef42c6fc656c9cb59deb4745fa78580708718a Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 12:07:00 -0500 Subject: [PATCH 56/62] If the parent post is in a draft state, autosaving updates it. --- lib/class-wp-rest-autosaves-controller.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php index 911b6bd07f666c..5e4d2866be982f 100644 --- a/lib/class-wp-rest-autosaves-controller.php +++ b/lib/class-wp-rest-autosaves-controller.php @@ -155,9 +155,17 @@ public function create_item( $request ) { $parent = $this->revision_controller->get_parent( $request['parent'] ); $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); $prepared_post->ID = $parent->ID; - $autosave_id = $this->create_post_autosave( (array) $prepared_post ); - $post = get_post( $autosave_id ); + // If the parent post is in a draft state, autosaving updates it. + if ( 'draft' === $parent->post_status ) { + $post_id = wp_update_post( (array) $prepared_post, true ); + if ( ! is_wp_error( $post_id ) ) { + $post = get_post( $post_id ); + } + } else { + $autosave_id = $this->create_post_autosave( (array) $prepared_post ); + $post = get_post( $autosave_id ); + } $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $post, $request ); From c05796cc5463985abd81a94d582109287a23760d Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 13:49:55 -0500 Subject: [PATCH 57/62] complete rename isAutosaving -> toggleAutosave --- editor/store/effects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/store/effects.js b/editor/store/effects.js index ad078bd6178dfd..39ae0035df109c 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -33,7 +33,7 @@ import { createWarningNotice, removeNotice, savePost, - isAutosaving, + toggleAutosave, editPost, requestMetaBoxUpdates, updateReusableBlock, From 95a245bc6cebf5f8139adf260ba2ec5c89072abf Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 13:58:03 -0500 Subject: [PATCH 58/62] =?UTF-8?q?when=20autosaving=20a=20draft,=20update?= =?UTF-8?q?=20the=20draft=20and=20don=E2=80=99t=20create=20a=20revision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/class-wp-rest-autosaves-controller.php | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php index 5e4d2866be982f..d4d05bdb4b9681 100644 --- a/lib/class-wp-rest-autosaves-controller.php +++ b/lib/class-wp-rest-autosaves-controller.php @@ -156,13 +156,22 @@ public function create_item( $request ) { $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); $prepared_post->ID = $parent->ID; - // If the parent post is in a draft state, autosaving updates it. + // If the parent post a draft, autosaving updates it and does not create a revision. if ( 'draft' === $parent->post_status ) { - $post_id = wp_update_post( (array) $prepared_post, true ); - if ( ! is_wp_error( $post_id ) ) { - $post = get_post( $post_id ); + + // Disable revisions. + remove_action( 'post_updated', 'wp_save_post_revision' ); + + $autosave_id = wp_update_post( (array) $prepared_post, true ); + + // Re-enable revisions. + add_action( 'post_updated', 'wp_save_post_revision' ); + if ( ! is_wp_error( $autosave_id ) ) { + $post = get_post( $autosave_id ); } } else { + + // Non-draft posts - update the post, creating an autosave. $autosave_id = $this->create_post_autosave( (array) $prepared_post ); $post = get_post( $autosave_id ); } From 3e559cac8b2ccd415ffb8465d2e1b2ac6fe274e1 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 16:47:39 -0500 Subject: [PATCH 59/62] fixes for phpcs --- lib/class-wp-rest-autosaves-controller.php | 18 +++++++++--------- lib/client-assets.php | 2 +- lib/register.php | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php index d4d05bdb4b9681..75363f7ba50858 100644 --- a/lib/class-wp-rest-autosaves-controller.php +++ b/lib/class-wp-rest-autosaves-controller.php @@ -59,7 +59,7 @@ public function __construct( $parent_post_type ) { $this->parent_post_type = $parent_post_type; $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); $this->revision_controller = new WP_REST_Revisions_Controller( $parent_post_type ); - $this->namespace = 'wp/v2'; + $this->rest_namespace = 'wp/v2'; $this->rest_base = 'autosaves'; $post_type_object = get_post_type_object( $parent_post_type ); $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; @@ -74,10 +74,10 @@ public function __construct( $parent_post_type ) { */ public function register_routes() { register_rest_route( - $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, array( + $this->rest_namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base, array( 'args' => array( 'parent' => array( - 'description' => __( 'The ID for the parent of the object.' ), + 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), 'type' => 'integer', ), ), @@ -98,14 +98,14 @@ public function register_routes() { ); register_rest_route( - $this->namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', array( + $this->rest_namespace, '/' . $this->parent_base . '/(?P[\d]+)/' . $this->rest_base . '/(?P[\d]+)', array( 'args' => array( 'parent' => array( - 'description' => __( 'The ID for the parent of the object.' ), + 'description' => __( 'The ID for the parent of the object.', 'gutenberg' ), 'type' => 'integer', ), 'id' => array( - 'description' => __( 'Unique identifier for the object.' ), + 'description' => __( 'Unique identifier for the object.', 'gutenberg' ), 'type' => 'integer', ), ), @@ -125,7 +125,7 @@ public function register_routes() { 'force' => array( 'type' => 'boolean', 'default' => false, - 'description' => __( 'Required to be true, as autosaves do not support trashing.' ), + 'description' => __( 'Required to be true, as autosaves do not support trashing.', 'gutenberg' ), ), ), ), @@ -181,7 +181,7 @@ public function create_item( $request ) { $response = rest_ensure_response( $response ); $response->set_status( 201 ); - $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $autosave_id ) ) ); + $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->rest_namespace, $this->rest_base, $autosave_id ) ) ); return $response; } @@ -195,7 +195,7 @@ public function create_item( $request ) { * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. */ public function get_item( $id ) { - $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid autosave ID.' ), array( 'status' => 404 ) ); + $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid autosave ID.', 'gutenberg' ), array( 'status' => 404 ) ); if ( (int) $id <= 0 ) { return $error; } diff --git a/lib/client-assets.php b/lib/client-assets.php index 496e72b4355f8f..62b4990c564938 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -734,7 +734,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) { } // Add autosave data if it is newer and changed. - $autosave = wp_get_post_autosave( $post->ID ); + $autosave = wp_get_post_autosave( $post->ID ); $show_autosave = false; // Is the autosave newer than the post? diff --git a/lib/register.php b/lib/register.php index fa127b6f04edf9..3ed4a332c7fac2 100644 --- a/lib/register.php +++ b/lib/register.php @@ -414,7 +414,7 @@ function gutenberg_register_post_types() { */ function gutenberg_register_rest_routes() { foreach ( get_post_types( array(), 'objects' ) as $post_type_object ) { - if( ! empty( $post_type_object->rest_base ) ) { + if ( ! empty( $post_type_object->rest_base ) ) { if ( post_type_supports( $post_type_object->name, 'revisions' ) ) { $autosaves = new WP_REST_Autosaves_Controller( $post_type_object->name ); $autosaves->register_routes(); From 7ace629b00bada29f59545a965e202192c1e28c9 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 16:51:36 -0500 Subject: [PATCH 60/62] fixes for phpcs --- lib/class-wp-rest-autosaves-controller.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php index 75363f7ba50858..d964e29f20a173 100644 --- a/lib/class-wp-rest-autosaves-controller.php +++ b/lib/class-wp-rest-autosaves-controller.php @@ -261,11 +261,12 @@ public function get_item_schema() { */ public function create_post_autosave( $post_data ) { - $post_id = (int) $post_data['ID']; + $post_id = (int) $post_data['ID']; $post_author = get_current_user_id(); // Store one autosave per author. If there is already an autosave, overwrite it. - if ( $old_autosave = wp_get_post_autosave( $post_id, $post_author ) ) { + $old_autosave = wp_get_post_autosave( $post_id, $post_author ); + if ( $old_autosave ) { $new_autosave = _wp_post_revision_data( $post_data, true ); $new_autosave['ID'] = $old_autosave->ID; $new_autosave['post_author'] = $post_author; From 12d7225d10b70562ff9d5f10b981be5cb9549123 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 21:21:00 -0500 Subject: [PATCH 61/62] ensure tests pass --- editor/components/autosave-monitor/index.js | 9 ++++++--- editor/components/autosave-monitor/test/index.js | 2 +- editor/store/test/effects.js | 8 ++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/editor/components/autosave-monitor/index.js b/editor/components/autosave-monitor/index.js index f36a403ce86735..89e026f8c4fad4 100644 --- a/editor/components/autosave-monitor/index.js +++ b/editor/components/autosave-monitor/index.js @@ -21,9 +21,12 @@ import { export class AutosaveMonitor extends Component { componentDidUpdate( prevProps ) { const { isDirty, isSaveable, isAutosavable } = this.props; - - if ( isDirty && isSaveable && isAutosavable ) { - this.toggleTimer( true ); + if ( + prevProps.isDirty !== isDirty || + prevProps.isSaveable !== isSaveable || + prevProps.isAutosavable !== isAutosavable + ) { + this.toggleTimer( isDirty && isSaveable && isAutosavable ); } } diff --git a/editor/components/autosave-monitor/test/index.js b/editor/components/autosave-monitor/test/index.js index 14c843f4ce5d25..a9e1790d8cd2ea 100644 --- a/editor/components/autosave-monitor/test/index.js +++ b/editor/components/autosave-monitor/test/index.js @@ -23,7 +23,7 @@ describe( 'AutosaveMonitor', () => { describe( '#componentDidUpdate()', () => { it( 'should start autosave timer when having become dirty and saveable', () => { - wrapper.setProps( { isDirty: true, isSaveable: true } ); + wrapper.setProps( { isDirty: true, isSaveable: true, isAutosavable: true } ); expect( toggleTimer ).toHaveBeenCalledWith( true ); } ); diff --git a/editor/store/test/effects.js b/editor/store/test/effects.js index 95ec8b946cbc7f..52e3b922c18853 100644 --- a/editor/store/test/effects.js +++ b/editor/store/test/effects.js @@ -194,6 +194,7 @@ describe( 'effects', () => { beforeAll( () => { selectors.isEditedPostSaveable = jest.spyOn( selectors, 'isEditedPostSaveable' ); selectors.isEditedPostDirty = jest.spyOn( selectors, 'isEditedPostDirty' ); + selectors.isPostAutosavable = jest.spyOn( selectors, 'isPostAutosavable' ); selectors.isCurrentPostPublished = jest.spyOn( selectors, 'isCurrentPostPublished' ); selectors.isEditedPostNew = jest.spyOn( selectors, 'isEditedPostNew' ); } ); @@ -202,6 +203,7 @@ describe( 'effects', () => { dispatch.mockReset(); selectors.isEditedPostSaveable.mockReset(); selectors.isEditedPostDirty.mockReset(); + selectors.isPostAutosavable.mockReset(); selectors.isCurrentPostPublished.mockReset(); selectors.isEditedPostNew.mockReset(); } ); @@ -209,6 +211,7 @@ describe( 'effects', () => { afterAll( () => { selectors.isEditedPostSaveable.mockRestore(); selectors.isEditedPostDirty.mockRestore(); + selectors.isPostAutosavable.mockRestore(); selectors.isCurrentPostPublished.mockRestore(); selectors.isEditedPostNew.mockRestore(); } ); @@ -270,13 +273,14 @@ describe( 'effects', () => { it( 'should return update action for saveable, dirty draft', () => { selectors.isEditedPostSaveable.mockReturnValue( true ); selectors.isEditedPostDirty.mockReturnValue( true ); + selectors.isPostAutosavable.mockReturnValue( true ); selectors.isCurrentPostPublished.mockReturnValue( false ); selectors.isEditedPostNew.mockReturnValue( false ); handler( {}, store ); - expect( dispatch ).toHaveBeenCalledTimes( 1 ); - expect( dispatch ).toHaveBeenCalledWith( savePost() ); + expect( dispatch ).toHaveBeenCalledTimes( 2 ); + expect( dispatch ).toHaveBeenCalledWith( savePost( { 'autosave': true } ) ); } ); } ); From 4d0307223f4434b93f37102816d22f0fac6fbd92 Mon Sep 17 00:00:00 2001 From: Adam Silverstein Date: Fri, 29 Dec 2017 21:46:14 -0500 Subject: [PATCH 62/62] fixes for eslint --- editor/components/autosave-monitor/index.js | 4 ++-- editor/components/post-publish-button/index.js | 2 +- editor/components/post-saved-state/index.js | 4 ++-- editor/index.js | 1 + editor/store/actions.js | 6 +++--- editor/store/effects.js | 16 ++++++---------- editor/store/reducer.js | 6 +++--- editor/store/selectors.js | 12 +++++------- editor/store/test/actions.js | 6 +++--- editor/store/test/effects.js | 2 +- 10 files changed, 27 insertions(+), 32 deletions(-) diff --git a/editor/components/autosave-monitor/index.js b/editor/components/autosave-monitor/index.js index 89e026f8c4fad4..80102a066b15ff 100644 --- a/editor/components/autosave-monitor/index.js +++ b/editor/components/autosave-monitor/index.js @@ -11,7 +11,7 @@ import { Component } from '@wordpress/element'; /** * Internal dependencies */ -import { autosave } from '../../store/actions'; +import { doAutosave } from '../../store/actions'; import { isEditedPostDirty, isEditedPostSaveable, @@ -58,5 +58,5 @@ export default connect( isAutosavable: isPostAutosavable( state ), }; }, - { autosave } + { doAutosave } )( AutosaveMonitor ); diff --git a/editor/components/post-publish-button/index.js b/editor/components/post-publish-button/index.js index fa6f4ac2265196..4cdef0ed3fe786 100644 --- a/editor/components/post-publish-button/index.js +++ b/editor/components/post-publish-button/index.js @@ -35,7 +35,7 @@ export function PostPublishButton( { visibility, isPublishable, isSaveable, - isAutosaving, + isAutosaving, user, onSubmit = noop, } ) { diff --git a/editor/components/post-saved-state/index.js b/editor/components/post-saved-state/index.js index 800b301db0261e..610eb3f1a8dc78 100644 --- a/editor/components/post-saved-state/index.js +++ b/editor/components/post-saved-state/index.js @@ -26,13 +26,13 @@ import { isAutosavingPost, } from '../../store/selectors'; -export function PostSavedState ( { isNew, isPublished, isDirty, isSaving, isSaveable, status, onStatusChange, onSave, isAutosaving } ) { +export function PostSavedState( { isNew, isPublished, isDirty, isSaving, isSaveable, status, onStatusChange, onSave, isAutosaving } ) { const className = 'editor-post-saved-state'; if ( isSaving ) { return ( - { isAutosaving ? __( 'Autosaving' ) : __( 'Saving' ) } + { isAutosaving ? __( 'Autosaving' ) : __( 'Saving' ) } ); } diff --git a/editor/index.js b/editor/index.js index 7a1d3565996290..1e67e90be7b792 100644 --- a/editor/index.js +++ b/editor/index.js @@ -78,6 +78,7 @@ export function recreateEditorInstance( target, settings ) { * @param {String} id Unique identifier for editor instance * @param {Object} post API entity for post to edit * @param {?Object} settings Editor settings object + * @param {?Object} autosave The autosave data * @return {Object} Editor interface */ export function createEditorInstance( id, post, settings, autosave ) { diff --git a/editor/store/actions.js b/editor/store/actions.js index 24835a782dc523..429dfef8f4ada4 100644 --- a/editor/store/actions.js +++ b/editor/store/actions.js @@ -247,7 +247,7 @@ export function mergeBlocks( blockA, blockB ) { * * @return {Object} Action object */ -export function autosave() { +export function doAutosave() { return { type: 'AUTOSAVE', }; @@ -257,14 +257,14 @@ export function toggleAutosave( isAutosaving ) { return { type: 'DOING_AUTOSAVE', isAutosaving, - } + }; } export function showAutosaveAlert( autosave ) { return { type: 'REQUEST_AUTOSAVE_EXISTS', autosave, - } + }; } /** diff --git a/editor/store/effects.js b/editor/store/effects.js index 39ae0035df109c..480403efecee46 100644 --- a/editor/store/effects.js +++ b/editor/store/effects.js @@ -48,7 +48,6 @@ import { getPostEdits, getEditedPostTitle, getEditedPostExcerpt, - isCurrentPostPublished, isEditedPostDirty, isEditedPostNew, isEditedPostSaveable, @@ -81,7 +80,7 @@ export default { if ( isAutosave ) { toSend.parent = post.id; - delete toSend.id ; + delete toSend.id; Model = wp.api.getPostTypeAutosaveModel( getCurrentPostType( state ) ); newModel = new Model( toSend ); } else { @@ -149,7 +148,7 @@ export default { }, REQUEST_AUTOSAVE_EXISTS( action, store ) { const { autosave } = action; - const { dispatch, getState } = store; + const { dispatch } = store; if ( autosave ) { dispatch( createWarningNotice(

@@ -161,7 +160,6 @@ export default { id: AUTOSAVE_POST_NOTICE_ID, } ) ); - } }, REQUEST_POST_UPDATE_SUCCESS( action, store ) { @@ -189,11 +187,9 @@ export default { private: __( 'Post published privately!' ), future: __( 'Post scheduled!' ), }[ post.status ]; - } else { - if ( ! isAutosave ) { - // Generic fallback notice - noticeMessage = __( 'Post updated!' ); - } + } else if ( ! isAutosave ) { + // Generic fallback notice + noticeMessage = __( 'Post updated!' ); } if ( noticeMessage ) { @@ -338,7 +334,7 @@ export default { } dispatch( toggleAutosave( true ) ); - dispatch( savePost( { 'autosave': true } ) ); + dispatch( savePost( { autosave: true } ) ); }, SETUP_EDITOR( action ) { const { post, settings } = action; diff --git a/editor/store/reducer.js b/editor/store/reducer.js index 8e4625c511570f..f4a2dd9f745a0a 100644 --- a/editor/store/reducer.js +++ b/editor/store/reducer.js @@ -77,7 +77,7 @@ export const editor = flow( [ // resetting at each post save. partialRight( withChangeDetection, { resetTypes: [ 'SETUP_EDITOR', 'RESET_POST' ] } ), ] )( { - autosave ( state = false, action ) { + autosave( state = false, action ) { const { post } = action; switch ( action.type ) { case 'RESET_AUTOSAVE': @@ -482,7 +482,7 @@ export function blocksMode( state = {}, action ) { return state; } -export function isAutosaving( state = false, action ) { +export function currentlyAutosaving( state = false, action ) { switch ( action.type ) { case 'DOING_AUTOSAVE': const { isAutosaving } = action; @@ -799,7 +799,7 @@ export const reusableBlocks = combineReducers( { export default optimist( combineReducers( { editor, - isAutosaving, + currentlyAutosaving, currentPost, isTyping, blockSelection, diff --git a/editor/store/selectors.js b/editor/store/selectors.js index d5130f1affcdb7..bac8a2b558343d 100644 --- a/editor/store/selectors.js +++ b/editor/store/selectors.js @@ -371,9 +371,8 @@ export function isEditedPostSaveable( state ) { * @return {Boolean} Whether the post can be autosaved */ export function isPostAutosavable( state ) { - // If the post is autosaving, it is not autosavable. - if ( state.isAutosaving ) { + if ( state.currentlyAutosaving ) { return false; } @@ -389,9 +388,9 @@ export function isPostAutosavable( state ) { // If the title, excerpt or content has changed, the post is autosavable. if ( - autosave.title && title !== autosave.title || - autosave.excerpt && excerpt !== autosave.excerpt || - autosave.content && content !== autosave.content + ( autosave.title && title !== autosave.title ) || + ( autosave.excerpt && excerpt !== autosave.excerpt ) || + ( autosave.content && content !== autosave.content ) ) { return true; } @@ -403,7 +402,6 @@ export function hasAutosave( state ) { return !! state.editor.present.autosave; } - /** * Return true if the post being edited is being scheduled. Preferring the * unsaved status values. @@ -1005,7 +1003,7 @@ export function didPostSaveRequestFail( state ) { * @return {Boolean} Whether the post is autosaving */ export function isAutosavingPost( state ) { - return !! state.isAutosaving; + return !! state.currentlyAutosaving; } /** diff --git a/editor/store/test/actions.js b/editor/store/test/actions.js index 146a38b92ff8b2..0dab8a3393f15c 100644 --- a/editor/store/test/actions.js +++ b/editor/store/test/actions.js @@ -36,7 +36,7 @@ import { savePost, trashPost, mergeBlocks, - autosave, + doAutosave, redo, undo, removeBlocks, @@ -306,9 +306,9 @@ describe( 'actions', () => { } ); } ); - describe( 'autosave', () => { + describe( 'doAutosave', () => { it( 'should return AUTOSAVE action', () => { - expect( autosave() ).toEqual( { + expect( doAutosave() ).toEqual( { type: 'AUTOSAVE', } ); } ); diff --git a/editor/store/test/effects.js b/editor/store/test/effects.js index 52e3b922c18853..82ec2fbc66f650 100644 --- a/editor/store/test/effects.js +++ b/editor/store/test/effects.js @@ -280,7 +280,7 @@ describe( 'effects', () => { handler( {}, store ); expect( dispatch ).toHaveBeenCalledTimes( 2 ); - expect( dispatch ).toHaveBeenCalledWith( savePost( { 'autosave': true } ) ); + expect( dispatch ).toHaveBeenCalledWith( savePost( { autosave: true } ) ); } ); } );