-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Post Editor: when saving post, overwrite the local edits with post version from server #26247
Conversation
`mergePostEdits` now takes a variable number of arguments and merges them all into one object. Also supports the "save marker" concept: string markers in the edits log that are skipped when merging.
The new function appends new edits to an existing log, merging with the last one if it's not a save marker.
It will be used by the `saveEdited` action to insert a save marker to edit log when save is initiated.
…markers Updates the `edits` reducer to work with the new shape of post edits: edits log instead of just one edits object. Also adds support for adding a save marker on `EDITOR_SAVE` and removing it on `POST_SAVE_SUCCESS`.
Test the `saveEdited` flow with the real reducer and mocked network requests. Verifies that the post edits log gets properly updated when server returns a transformed content.
….posts.edits shape
This is working brilliantly for me. All attributes I've tried (including page slugs) seem to only trigger the dialog at appropriate times on this branch. There's still an issue with switching between editor modes & inline styles, but it's much more solvable with this in place. Thanks! |
I'm glad to hear that @jblz! I added a proper PR description and now it's ready for review and more testing.
Switching between editor modes is a different beast that will require a different fix. On every switch, functions Also, the |
I'm still seeing some issues with [code] shortcodes and switching editor modes but I otherwise can't trigger the AYS prompt - so if this PR makes the editor code cleaner and easier to maintain it's great 🎉 Thank you |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM from a testing perspective
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to reproduce on WP.com (staged) with the instructions inn the description and didn't see the notice. Possibly I wasn't reproducing correctly though as I'm sure I've seen it under those circumstances before.
Either way this does work as described and the method you describe sounds sensible and smart :)
Awesome work @jsnajdr 💚
@@ -415,7 +418,10 @@ export function edits( state = {}, action ) { | |||
// Receive a new version of a post object, in most cases returned in the POST | |||
// response after a successful save. Removes the edits that have been applied | |||
// and leaves only the ones that are not noops. | |||
const postEdits = get( memoState, [ post.site_ID, post.ID ] ); | |||
const postEditsLog = get( memoState, [ post.site_ID, post.ID ] ) || []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny nit, []
default could be given as the 3rd argument here, get
will return that as the default if the path (2nd argument) isn't found. FFTI though, this is inconsequential.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no, that would actually be a horrible bug 😉 The value in state can be null
in case when there are no active edits on the post. And get
only uses the default value for undefined
, nothing else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh ofc! I should have known there'd be good reason! Thanks for clarifying :D
🤔 it looks like this PR introduced a race condition regression in the editor accordions where if you have the categories open and you save or an autosave happens the accordion is now closed: Interestingly the e2e tests have started picking this up on Production, however they didn't find it on this PR when the tests were run - I think this is because of a race condition where if the post is saved before you open the category accordion then it remains open on subsequent saves I pinpointed it to this PR by |
This PR seeks to fix the dirty-after-save issues reported in #9833, namely the following incarnations:
[code]
escaping by @alisterscott in Editor: prompts to restore/discard changes when there are no changes #9833 (comment) and Editor: prompts to restore/discard changes when there are no changes #9833 (comment)This doesn't fix any of the Markdown issues mentioned by @lancewillett in Editor: prompts to restore/discard changes when there are no changes #9833 (comment) -- I can't reproduce the Markdown issue at all -- I never see the AYS dialog. Maybe someone can provide a reliable STR?
What changed is the algorithm how post edits are stored and discarded on save.
Before this patch:
Save with server-transformed content, without race condition:
state.posts.edits
become{ content: 'Hello' }
POST
save request with{ content: 'Hello' }
{ content '<p>Hello</p>' }
state.posts.edits
and determine thatcontent
wasn't saved yet.state.posts.edits
is still{ content: 'Hello' }
and the post is dirty. This is BAD!Save without server-transformed content, but with race condition:
state.posts.edits
become{ content: 'Hello' }
POST
save request with{ content: 'Hello' }
state.posts.edits
become{ content: 'Hello world' }
{ content 'Hello' }
state.posts.edits
and determine thatcontent
wasn't saved yet.state.posts.edits
is still{ content: 'Hello world' }
and the post is dirty. This is GOOD!The same algorithm provides a bad result for transformed content, but a good result for the race condition.
This patch changes the edit algorithm to cover both cases correctly.
After the patch:
Save with server-transformed content, without race condition:
state.posts.edits
are a log (i.e., array) rather that a single edits object:[ { content: 'Hello' } ]
[ { content: 'Hello' }, 'save-marker-1' ]
POST
save request with{ content: 'Hello' }
and the server returns transformed content{ content '<p>Hello</p>' }
state.posts.edits
by removing all edits before the save marker. Thestate.posts.edits
log therefore becomes empty. The post is no longer dirty. That's GOODSave without server-transformed content, but with race condition:
state.posts.edits
becomes[ { content: 'Hello' } ]
POST
save request with{ content: 'Hello' }
state.posts.edits
becomes[ { content: 'Hello', 'save-marker-2', { content: 'Hello world' } ]
{ content 'Hello' }
, we update the post instate.posts
and truncate the edits log to[ { content: 'Hello world' } ]
The key changes to the code are:
mergePostEdits
function now takes arbitrary number of arguments and we pass it the spread edits log. It will merge all edits together, skipping save markers.appendToPostEditsLog
appends a new edit to the log, merging the last two edits if there is no marker at the end. This prevents the log from getting too long.EDITOR_SAVE
action that inserts the save markerPOST_SAVE_SUCCESS
action will remove the markerstate.posts.edits
is updated to work with an array of edits instead of just one edit.There's also a very useful new test suite in
client/state/ui/editor/test/edit-save-flow.js
that tests the whole flow described above. It uses a realistic Redux store, mocks the network requests withnock
, dispatches thesaveEdited
action and checks the results.