From 0353feb75ce1e5be4e7c2aa633cae3da6d7f044e Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 3 Apr 2018 16:49:14 +1000 Subject: [PATCH 1/7] Add first draft of autosaves --- text/0000-autosaves.md | 271 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 text/0000-autosaves.md diff --git a/text/0000-autosaves.md b/text/0000-autosaves.md new file mode 100644 index 0000000..40604aa --- /dev/null +++ b/text/0000-autosaves.md @@ -0,0 +1,271 @@ +- Start Date: 2018-04-03 +- RFC PR: (leave this empty) +- WordPress Trac Ticket: https://core.trac.wordpress.org/ticket/43316 + +# Summary + +This RFC adds the ability to build autosave-capable editors atop the REST API. This RFC proposes adding a "future revision" concept, which integrates with the current revision interface. This provides a way to autosave a post separately to full, atomic saves. + + +# Motivation + +When users are using a modern editor, they expect to never lose their work in the middle of a thought. Editors implement this in many different ways, including client-side persistence and server-side persistence. Typically, client-side persistence is used for second-to-second updates, while server-side persistence is used for minute-to-minute updates. + +Currently in the REST API, the only way to update a post is to send a full update to the post (via a PUT request). This creates a revision on every save, which can be confusing to users. Revisions are intended primarily as a user-facing mechanism to restore content to a previous state, and using this same mechanism for autosaves reveals unnecessary detail about how the editor is saving. + +The primary motivation for this feature is Gutenberg, which aims to implement autosave purely via the REST API. + + +# Detailed design + + + +The design extends the existing Revision resource to include autosaves. Autosaves are treated as a special type of revision, indicated by their `revision_type` being set to `autosave`. + +The general flow for a client is: + +* User opens editor and makes first change, client starts a new autosave revision: `POST /wp/v2/posts/{id}/revisions` +* User edits some text, client updates the autosave revision: `PUT /wp/v2/posts/{id}/revisions/{rev_id}` +* User clicks save, client commits the autosave: `POST /wp/v2/posts/{id}/revisions/{rev_id}` +* If user makes another change, repeat from start + +The editor will typically wait until the first change of the post to start the autosave revision. + +If the editor is abandoned (for example, the client crashes), the client can retrieve the previous autosave for the user by querying `GET /wp/v2/posts/{id}/revisions?author={user}&revision_type=autosave`. + + +## Alterations to existing Revision resources + +The design adds the following properties to Revision resources, denoted with JSON Schema: + +```json +{ + "revision_type": { + "description": "Type of revision." + "type": "string", + "readonly": true, + "enum": [ + "autosave", + "revision" + ], + "context": [ + "view", + "edit" + ] + } +} +``` + + +## Alterations to existing Revision endpoints + +This design adds the following query parameters to the Revision list endpoint (`GET /wp/v2/posts/{id}/revisions`): + +* `author` (integer): Filter to just revisions by this author. +* `revision_type` (string or array, default `revision`): Types of revision to include. By default, only includes full revisions. + + +## New "Create Revision" endpoint + +`POST /wp/v2/posts/{id}/revisions` + +Create a new revision. + +The request body takes the following properties, denoted in JSON Schema format: + +```json +{ + "revision_type": { + "description": "Type of revision.", + "type": "string", + "enum": [ + "autosave" + ], + "default": "autosave", + "context": [ + "view", + "edit" + ] + }, + "title": { + "description": "The title for the object.", + "type": "object", + "context": [ + "view", + "edit", + "embed" + ], + "properties": { + "raw": { + "description": "Title for the object, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML title for the object, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + } + }, + "content": { + "description": "The content for the object.", + "type": "object", + "context": [ + "view", "edit" + ], + "properties": { + "raw": { + "description": "Content for the object, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML content for the object, transformed for display.", + "type": "string", + "context": [ + "view", + "edit" + ], + "readonly": true + } + } + }, + "excerpt": { + "description": "The excerpt for the object.", + "type": "object", + "context": [ + "view", + "edit", + "embed" + ], + "properties": { + "raw": { + "description": "Excerpt for the object, as it exists in the database.", + "type": "string", + "context": [ + "edit" + ] + }, + "rendered": { + "description": "HTML excerpt for the object, transformed for display.", + "type": "string", + "context": [ + "view", + "edit", + "embed" + ], + "readonly": true + } + } + } +} +``` + +The `title`, `content`, and `excerpt` fields mirror the Post resource's fields. Additional fields may be specified by plugins, such as `meta`. + +Only `autosave` revisions can be created externally, as regular revisions are maintained internally by WordPress instead. + +This endpoint creates a new autosave revision internally, and returns the representation of that revision. All the usual checks are applied to the autosave revision, including the one-autosave-per-user check. + +If an existing autosave for the user already exists, a `409 Conflict` status is returned, indicating that the user already has an autosave. This error should include the ID of the existing autosave revision. + + +## New "Commit Revision" Endpoint + +`POST /wp/v2/posts/{id}/revisions/{rev_id}` + +Commits a revision. + +This endpoint takes the revision identified by the ID, and updates the post to match that revision. This is used for both "committing" autosave revisions (which turns them into real revisions), as well as restoring old revisions. + +This endpoint also deletes the autosave revision on commit, as internally autosave revisions are deleted by WordPress when the post is saved. + + +# How do we communicate this? + + + +This should be communicated as part of the 5.0 Developer Field Guide, as well as with additions to the REST API Handbook. The communication should focus on how autosaves conceptually fit into the revision concept, as well as the practical aspects of implementing autosave in editors. It should be noted that autosaves are an opt-in feature, and regular post updates can continue to be used for clients which don't need autosaving. + +The communication should also note that this adds the ability to restore revisions. + +End user-facing communication is not needed or desired, so does not need to be included in the release notes. + + +## Field Guide content + +> # Autosaving in the REST API in 5.0 +> +> WordPress 5.0 introduces autosaves to the WordPress REST API. This has been added to power the autosave feature of Gutenberg to ensure users' work is never lost. +> +> Autosaving is an advanced feature of the REST API, and is opt-in for clients. Existing and new clients can continue to use the existing post update functionality in the REST API. Autosave functionality should generally only be used when making frequent, automatic saves without user interaction. +> +> The primary difference between autosaves and regular post saves is that autosaves represent a "future revision" of the post. Editors create an autosave automatically for users and continually update this to persist changes before the post is actually updated. When the user chooses to actually save their changes, this "future revision" can be "committed"; that is, the staged changes can be applied to the post. +> +> +> ## Typical Autosave Flow +> +> *TODO*: Finish field guide content. + + +## Handbook content + +*TODO*: Write handbook content. + + +# Drawbacks + + + +*TODO*: Discuss drawbacks. + + +# Alternatives + + + +*TODO*: Discuss alternative designs from Trac ticket. + + +# Unresolved questions + + + +* Continue to discuss and refine the flow. + + +# Credits + + + +All text, code, and other contributions related to this proposal are licensed under the [GNU General Public License, v2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html) or later. From 3b29fa2fa2e282f55847d510e2e551729d8d3a2b Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 3 Apr 2018 16:51:41 +1000 Subject: [PATCH 2/7] Correct JSON syntax error --- text/0000-autosaves.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-autosaves.md b/text/0000-autosaves.md index 40604aa..ca8cf28 100644 --- a/text/0000-autosaves.md +++ b/text/0000-autosaves.md @@ -41,7 +41,7 @@ The design adds the following properties to Revision resources, denoted with JSO ```json { "revision_type": { - "description": "Type of revision." + "description": "Type of revision.", "type": "string", "readonly": true, "enum": [ From e74e20910854a0306124e49c467f8bfd7ab7b1f8 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 3 Apr 2018 17:20:25 +1000 Subject: [PATCH 3/7] Expand field guide content --- text/0000-autosaves.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/text/0000-autosaves.md b/text/0000-autosaves.md index ca8cf28..4c4addb 100644 --- a/text/0000-autosaves.md +++ b/text/0000-autosaves.md @@ -221,8 +221,41 @@ End user-facing communication is not needed or desired, so does not need to be i > The primary difference between autosaves and regular post saves is that autosaves represent a "future revision" of the post. Editors create an autosave automatically for users and continually update this to persist changes before the post is actually updated. When the user chooses to actually save their changes, this "future revision" can be "committed"; that is, the staged changes can be applied to the post. > > +> ## New and Changed Functionality +> +> The revisions endpoints in the REST API have been changed to support autosaves as a "future revision". The biggest change to revisions for existing users is a new `revision_type` field. This can be either `revision` for regular revisions, or `autosave` for autosaves. Both revisions and autosaves are available via the `GET /wp/v2/posts/{id}/revisions` endpoint. +> +> A new `revision_type` query parameter has been added to the `GET /wp/v2/posts/{id}/revisions` endpoint. This allows querying each type of revision, either individually (`?revision_type=autosave`) or together (`?revision_type=autosave,revision`). By default, this parameter is set to `revision`, which preserves the existing behaviour for clients. An `author` query parameter has also been added to allow querying revisions by author. +> +> Three new write endpoints have been added: +> +> * `POST /wp/v2/posts/{id}/revisions` creates a new autosave +> * `PUT /wp/v2/posts/{id}/revisions/{rev_id}` updates an autosave +> * `POST /wp/v2/posts/{id}/revisions/{rev_id}` commits an autosave (i.e. applies the autosaved changes to the post) +> +> `POST /wp/v2/posts/{id}/revisions/{rev_id}` is also useful for revisions, and it restores the post to this revision. When calling this endpoint, the autosave or revision may be deleted as a side-effect. +> +> It is important to note that `PUT` and `POST` have differing functionality on the same route; for clients that don't support HTTP/1.1 methods like `PUT`, you may need to send a [`POST` with a method override](https://developer.wordpress.org/rest-api/using-the-rest-api/global-parameters/#_method-or-x-http-method-override-header). +> +> > ## Typical Autosave Flow > +> Editors will typically follow an autosave flow similar to the following: +> +> * User opens a post to edit +> * Editor loads `GET /wp/v2/posts/{id}/revisions?revision_type=autosave&author={id}` to check for existing autosaves +> * User makes their first change to the post +> * Editor starts an autosave revision: `POST /wp/v2/posts/{id}/revisions` +> * User continues editing +> * Editor updates the autosave revision: `PUT /wp/v2/posts/{id}/revisions/{rev_id}` +> * User hits Save to commit their changes +> * Editor commits the autosave revision: `POST /wp/v2/posts/{id}/revisions/{rev_id}` +> * As a side-effect, WordPress deletes the autosave revision, as it is now no longer needed +> * User makes another change +> * Editor repeats above steps +> +> If the user discards their changes, the editor can delete the autosave with `DELETE /wp/v2/posts/{id}/revisions/{rev_id}` +> > *TODO*: Finish field guide content. From 1cd6f8b38999293d40e17fb33270204fd614d8f0 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 3 Apr 2018 17:22:18 +1000 Subject: [PATCH 4/7] Add further unresolved questions --- text/0000-autosaves.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-autosaves.md b/text/0000-autosaves.md index 4c4addb..e45d512 100644 --- a/text/0000-autosaves.md +++ b/text/0000-autosaves.md @@ -295,6 +295,9 @@ Optional, but suggested for first drafts. What parts of the design are still TBD --> * Continue to discuss and refine the flow. +* How does committing an autosave work when the user has other changes to save? + * Does the editor have to send a `POST /revisions/{rev_id}`, then also send a `PUT /posts/{id}` to update the other options? + * Should autosaves be extended to support other fields instead? # Credits From 5cbc692b8d71b9f8dc940666cc261a3975c54405 Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Tue, 3 Apr 2018 17:33:53 +1000 Subject: [PATCH 5/7] Add drawbacks --- text/0000-autosaves.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/text/0000-autosaves.md b/text/0000-autosaves.md index e45d512..f45ca29 100644 --- a/text/0000-autosaves.md +++ b/text/0000-autosaves.md @@ -266,13 +266,22 @@ End user-facing communication is not needed or desired, so does not need to be i # Drawbacks - +This isn't believed to be a huge concern, as currently autosave functionality is not available outside the TinyMCE-based editor. Most developers cannot currently work with autosaves at all, so they have no existing mental model of how autosaves work with which this would clash. + +Additionally, as autosaves are expected to be advanced functionality only used by complex editing interfaces, most developers will not need to think about how they work. + + +## Exposing internals + +Making autosaves available as revisions may expose the internal behaviour of how autosaves work. Under the hood, autosaves really are stored as revisions, and are differentiated based on a slug suffix. If the underlying storage mechanism for autosaves changes, this may break the public interface via the REST API. + +It is unlikely that this will be a concern. With the `revision_type` property, we can distinguish between varying types of revisions and implement different code paths internally. The implementation will likely take the path of using the native autosave functions (e.g. `wp_create_post_autosave`) instead of using the revisions API directly, which will shield it from underlying changes. + +For this to cause problems with the REST API would also likely necessitate WordPress breaking backwards compatibility for internal APIs, which is also unlikely. *TODO*: Discuss drawbacks. From 74f4ea4f71c99169dc5673b5e010a65de3d02e8d Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Wed, 4 Apr 2018 15:38:44 +1000 Subject: [PATCH 6/7] Update default for revision_type --- text/0000-autosaves.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-autosaves.md b/text/0000-autosaves.md index f45ca29..fd8ae88 100644 --- a/text/0000-autosaves.md +++ b/text/0000-autosaves.md @@ -62,7 +62,7 @@ The design adds the following properties to Revision resources, denoted with JSO This design adds the following query parameters to the Revision list endpoint (`GET /wp/v2/posts/{id}/revisions`): * `author` (integer): Filter to just revisions by this author. -* `revision_type` (string or array, default `revision`): Types of revision to include. By default, only includes full revisions. +* `revision_type` (string or array, default `autosave,revision`): Types of revision to include. ## New "Create Revision" endpoint @@ -225,7 +225,7 @@ End user-facing communication is not needed or desired, so does not need to be i > > The revisions endpoints in the REST API have been changed to support autosaves as a "future revision". The biggest change to revisions for existing users is a new `revision_type` field. This can be either `revision` for regular revisions, or `autosave` for autosaves. Both revisions and autosaves are available via the `GET /wp/v2/posts/{id}/revisions` endpoint. > -> A new `revision_type` query parameter has been added to the `GET /wp/v2/posts/{id}/revisions` endpoint. This allows querying each type of revision, either individually (`?revision_type=autosave`) or together (`?revision_type=autosave,revision`). By default, this parameter is set to `revision`, which preserves the existing behaviour for clients. An `author` query parameter has also been added to allow querying revisions by author. +> A new `revision_type` query parameter has been added to the `GET /wp/v2/posts/{id}/revisions` endpoint. This allows querying each type of revision, either individually (`?revision_type=autosave`) or together (`?revision_type=autosave,revision`). By default, this parameter is set to `autosave,revision`, which preserves the existing behaviour for clients. An `author` query parameter has also been added to allow querying revisions by author. > > Three new write endpoints have been added: > From 1fee3508aa06802c8f80ce23cab0c7bd88dc7f0a Mon Sep 17 00:00:00 2001 From: Ryan McCue Date: Wed, 4 Apr 2018 16:00:23 +1000 Subject: [PATCH 7/7] Fail if saving a draft --- text/0000-autosaves.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/text/0000-autosaves.md b/text/0000-autosaves.md index fd8ae88..a7188f6 100644 --- a/text/0000-autosaves.md +++ b/text/0000-autosaves.md @@ -177,8 +177,13 @@ Only `autosave` revisions can be created externally, as regular revisions are ma This endpoint creates a new autosave revision internally, and returns the representation of that revision. All the usual checks are applied to the autosave revision, including the one-autosave-per-user check. + +### Error Cases + If an existing autosave for the user already exists, a `409 Conflict` status is returned, indicating that the user already has an autosave. This error should include the ID of the existing autosave revision. +If the post is a draft, a `400 Bad Request` status is returned, indicating that autosaves cannot be created for draft posts. + ## New "Commit Revision" Endpoint