diff --git a/editor/store/actions.js b/editor/store/actions.js
index b5004e4db6e2c8..429dfef8f4ada4 100644
--- a/editor/store/actions.js
+++ b/editor/store/actions.js
@@ -220,9 +220,10 @@ export function editPost( edits ) {
};
}
-export function savePost() {
+export function savePost( options ) {
return {
type: 'REQUEST_POST_UPDATE',
+ options,
};
}
@@ -246,12 +247,26 @@ export function mergeBlocks( blockA, blockB ) {
*
* @return {Object} Action object
*/
-export function autosave() {
+export function doAutosave() {
return {
type: 'AUTOSAVE',
};
}
+export function toggleAutosave( isAutosaving ) {
+ return {
+ type: 'DOING_AUTOSAVE',
+ isAutosaving,
+ };
+}
+
+export function showAutosaveAlert( autosave ) {
+ return {
+ type: 'REQUEST_AUTOSAVE_EXISTS',
+ autosave,
+ };
+}
+
/**
* Returns an action object used in signalling that undo history should
* restore last popped state.
diff --git a/editor/store/effects.js b/editor/store/effects.js
index c6777602c4f244..480403efecee46 100644
--- a/editor/store/effects.js
+++ b/editor/store/effects.js
@@ -30,8 +30,10 @@ import {
replaceBlocks,
createSuccessNotice,
createErrorNotice,
+ createWarningNotice,
removeNotice,
savePost,
+ toggleAutosave,
editPost,
requestMetaBoxUpdates,
updateReusableBlock,
@@ -44,7 +46,8 @@ import {
getDirtyMetaBoxes,
getEditedPostContent,
getPostEdits,
- isCurrentPostPublished,
+ getEditedPostTitle,
+ getEditedPostExcerpt,
isEditedPostDirty,
isEditedPostNew,
isEditedPostSaveable,
@@ -57,6 +60,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';
@@ -71,26 +75,65 @@ 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 ) );
+ dispatch( removeNotice( AUTOSAVE_POST_NOTICE_ID ) );
+ Model = wp.api.getPostTypeModel( getCurrentPostType( state ) );
+ newModel = new Model( toSend );
+ }
+
+ newModel.save().done( ( newPost ) => {
+ if ( isAutosave ) {
+ const autosave = {
+ id: newPost.id,
+ title: getEditedPostTitle( state ),
+ excerpt: getEditedPostExcerpt( state ),
+ content: getEditedPostContent( state ),
+ };
+ dispatch( {
+ type: 'RESET_AUTOSAVE',
+ post: autosave,
+ } );
+ dispatch( toggleAutosave( false ) );
+
+ dispatch( {
+ type: 'REQUEST_POST_UPDATE_SUCCESS',
+ previousPost: post,
+ post: post,
+ isAutosave: true,
+ } );
+ } else {
+ // dispatch post autosaved false
+ // delete the autosave
+ 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 ) => {
+ if ( isAutosave ) {
+ dispatch( toggleAutosave( false ) );
+ }
+
dispatch( {
type: 'REQUEST_POST_UPDATE_FAILURE',
error: get( err, 'responseJSON', {
@@ -99,12 +142,28 @@ export default {
} ),
post,
edits,
- optimist: { type: REVERT, id: POST_UPDATE_TRANSACTION_ID },
+ optimist: isAutosave ? false : { type: REVERT, id: POST_UPDATE_TRANSACTION_ID },
} );
} );
},
+ REQUEST_AUTOSAVE_EXISTS( action, store ) {
+ const { autosave } = action;
+ const { dispatch } = 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 } = action;
+ const { previousPost, post, isAutosave } = action;
const { dispatch, getState } = store;
const publishStatus = [ 'publish', 'private', 'future' ];
@@ -128,7 +187,7 @@ export default {
private: __( 'Post published privately!' ),
future: __( 'Post scheduled!' ),
}[ post.status ];
- } else {
+ } else if ( ! isAutosave ) {
// Generic fallback notice
noticeMessage = __( 'Post updated!' );
}
@@ -263,25 +322,19 @@ export default {
return;
}
- if ( ! isEditedPostNew( state ) && ! isEditedPostDirty( state ) ) {
+ // Change status from auto-draft to draft, saving the post.
+ if ( isEditedPostNew( state ) ) {
+ dispatch( editPost( { status: 'draft' } ) );
+ dispatch( savePost() );
return;
}
- 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)
+ if ( ! isEditedPostDirty( state ) ) {
return;
}
- // Change status from auto-draft to draft
- if ( isEditedPostNew( state ) ) {
- dispatch( editPost( { status: 'draft' } ) );
- }
-
- dispatch( savePost() );
+ dispatch( toggleAutosave( 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 9d7e53178658fa..f4a2dd9f745a0a 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,6 +482,16 @@ export function blocksMode( state = {}, action ) {
return state;
}
+export function currentlyAutosaving( state = false, action ) {
+ switch ( action.type ) {
+ case 'DOING_AUTOSAVE':
+ const { isAutosaving } = action;
+ return isAutosaving;
+ }
+
+ return state;
+}
+
/**
* Reducer returning the block insertion point
*
@@ -780,6 +799,7 @@ export const reusableBlocks = combineReducers( {
export default optimist( combineReducers( {
editor,
+ currentlyAutosaving,
currentPost,
isTyping,
blockSelection,
diff --git a/editor/store/selectors.js b/editor/store/selectors.js
index 99dd17ce588f66..bac8a2b558343d 100644
--- a/editor/store/selectors.js
+++ b/editor/store/selectors.js
@@ -364,6 +364,44 @@ 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.currentlyAutosaving ) {
+ 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.
@@ -958,6 +996,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.currentlyAutosaving;
+}
+
/**
* 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
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 95ec8b946cbc7f..82ec2fbc66f650 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 } ) );
} );
} );
diff --git a/lib/class-wp-rest-autosaves-controller.php b/lib/class-wp-rest-autosaves-controller.php
new file mode 100644
index 00000000000000..d964e29f20a173
--- /dev/null
+++ b/lib/class-wp-rest-autosaves-controller.php
@@ -0,0 +1,307 @@
+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->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;
+ }
+
+ /**
+ * Registers routes for autosaves based on post types supporting autosaves.
+ *
+ * @since 5.0.0
+ *
+ * @see register_rest_route()
+ */
+ public function register_routes() {
+ register_rest_route(
+ $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.', 'gutenberg' ),
+ 'type' => 'integer',
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_items' ),
+ 'permission_callback' => 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->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' ),
+ )
+ );
+
+ register_rest_route(
+ $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.', 'gutenberg' ),
+ 'type' => 'integer',
+ ),
+ 'id' => array(
+ 'description' => __( 'Unique identifier for the object.', 'gutenberg' ),
+ 'type' => 'integer',
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::READABLE,
+ 'callback' => array( $this, 'get_item' ),
+ 'permission_callback' => array( $this->revision_controller, '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->revision_controller, 'delete_item_permissions_check' ),
+ 'args' => array(
+ 'force' => array(
+ 'type' => 'boolean',
+ 'default' => false,
+ 'description' => __( 'Required to be true, as autosaves do not support trashing.', 'gutenberg' ),
+ ),
+ ),
+ ),
+ 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' ),
+ )
+ );
+
+ }
+
+ /**
+ * 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->revision_controller->get_parent( $request['parent'] );
+ $prepared_post = $this->parent_controller->prepare_item_for_database( $request );
+ $prepared_post->ID = $parent->ID;
+
+ // If the parent post a draft, autosaving updates it and does not create a revision.
+ if ( 'draft' === $parent->post_status ) {
+
+ // 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 );
+ }
+ $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->rest_namespace, $this->rest_base, $autosave_id ) ) );
+
+ return $response;
+ }
+
+ /**
+ * 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.
+ */
+ public function get_item( $id ) {
+ $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid autosave ID.', 'gutenberg' ), array( 'status' => 404 ) );
+ if ( (int) $id <= 0 ) {
+ return $error;
+ }
+
+ $autosave = get_post( (int) $id );
+ if ( empty( $autosave ) || empty( $autosave->ID ) || 'autosave' !== $autosave->post_type ) {
+ return $error;
+ }
+
+ return $autosave;
+ }
+
+ /**
+ * 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->revision_controller->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() {
+ 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.
+ $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;
+
+ // 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 );
+ }
+}
diff --git a/lib/client-assets.php b/lib/client-assets.php
index 513e886551d811..62b4990c564938 100644
--- a/lib/client-assets.php
+++ b/lib/client-assets.php
@@ -479,6 +479,12 @@ function gutenberg_extend_wp_api_backbone_client() {
return model.prototype.route && route === model.prototype.route.index;
} );
};
+ wp.api.getPostTypeAutosaveModel = function( postType ) {
+ var route = '/' + wpApiSettings.versionString + wp.api.postTypeRestBaseMapping[ postType ] + '/(?P[\\\\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 ) {
@@ -727,6 +733,39 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
wp_die( $post_to_edit->get_error_message() );
}
+ // Add autosave data if it is newer and changed.
+ $autosave = wp_get_post_autosave( $post->ID );
+ $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 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(
+ 'wp-editor',
+ '_wpAutosave',
+ array(
+ 'id' => $autosave->ID,
+ '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".
$is_new_post = 'auto-draft' === $post_to_edit['status'];
@@ -833,7 +872,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
$script .= sprintf( 'var editorSettings = %s;', wp_json_encode( $editor_settings ) );
$script .= <<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.
*