-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Fix autosave while editing a post using the Text Mode editor #5755
Conversation
Store the value of the PostTextEditor text area in local state. This prevents text from being lost if the post is autosaved during editing.
This fixes the bug but I find it a bit weird that there are moments where I'm considering changing Thoughts? |
Yes, this is a hard problem to solve with constant syncing. We tried to avoid this for performance reasons. Maybe it's fine I don't know, we have to try for long posts as well. cc @aduth |
Previously it would be an issue because parsing an incomplete text would probably alter the serialized output of the reset blocks. Using state to ensure that the textarea value isn't clobbered should remedy this as far as the user is concerned, but the parsed data is probably still often out of sync with what the user is typing. |
}; | ||
} | ||
|
||
onChange( event ) { | ||
this.props.onChange( event.target.value ); | ||
handleFocus() { |
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.
FWIW I find these sorts of function names aren't very informative and encourage developers to not clearly separate the responsibilities of functions. As an outsider looking at this function later, I'd have no idea what it's intending to do, at least not without reading through and interpreting the logic (and who has time for that?). If it were named setStateValue
with a DocBlock explaining how we track a local copy of the value to avoid clobbering edits from global state, I'd feel much more informed.
There's a few places where I've tried to set more explicit patterns for some of these common behaviors. One in particular is handling onKeyDown
, since we usually want distinct functionality to take place on individual key codes. While maybe a bit extreme (especially since it only handles as a single key code at the moment), NavigableToolbar
is one such example:
gutenberg/editor/components/navigable-toolbar/index.js
Lines 32 to 34 in 4bac4fe
this.switchOnKeyDown = cond( [ | |
[ matchesProperty( [ 'keyCode' ], ESCAPE ), this.focusSelection ], | |
] ); |
gutenberg/editor/components/navigable-toolbar/index.js
Lines 51 to 73 in 4bac4fe
/** | |
* Programmatically shifts focus to the element where the current selection | |
* exists, if there is a selection. | |
*/ | |
focusSelection() { | |
// Ensure that a selection exists. | |
const selection = getSelection(); | |
if ( ! selection ) { | |
return; | |
} | |
// Focus node may be a text node, which cannot be focused directly. | |
// Find its parent element instead. | |
const { focusNode } = selection; | |
let focusElement = focusNode; | |
if ( focusElement.nodeType !== Node.ELEMENT_NODE ) { | |
focusElement = focusElement.parentElement; | |
} | |
if ( focusElement ) { | |
focusElement.focus(); | |
} | |
} |
onKeyDown={ this.switchOnKeyDown } |
(And yes, arguments can be made that _.cond
is not a very obvious / readable utility, but better abstractions could exist. The general point stands all the same)
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.
👍 noted. I've moved away from 'handle-' method names in c4035be.
initialValue: value, | ||
} ); | ||
handleBlur() { | ||
if ( this.state.isDirty ) { |
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.
Do we need to track isDirty
? Can't we just check if ( this.state.value !== this.props.value ) {
?
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.
Since we change the global value by dispatching EDIT_POST
in our onChange
handler,
this.state.value !== this.props.value
will always evaluate to false.
Try to describe what the method does instead of merely what event it handles.
Makes sense. Let's stick with this approach for now, then. |
Are we good to go ahead and get this in? |
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.
What about external updates, in the current implementation they are ignored completely, I think an option could be:
-
If we receive new content and the editor is not dirty, replace the state value if existant
-
If we receive new content and the editor is dirty, an option would be to compare the values and if different show a notice saying if you want to revert changes (new external updates) you can.
The second point can probably be done later, I see its value especially for plugins that could update the content independently from the UI. Granted we don't have this use case right now.
If the post text editor isn't dirty, update the editor with any external updates.
I've implemented this in af209c2 and tested it by:
setTimeout( () => { wp.data.dispatch( 'core/editor' ).editPost( { content: '<!-- wp:paragraph -->\n<p>hey there! How are you?</p>\n<!-- /wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Pretty well, thank you</p>\n<!-- /wp:paragraph -->' } ) }, 2000 )
I agree that this can be done later since it's not (yet) common that the post is edited externally. Thanks for reviewing! ❤️ |
Description
Fixes #2978.
Text that was typed into the Text Mode editor would be wiped away if the post was autosaved. Here's what was happening:
editor.present.edits.content
. Note thateditor.present.blocksByUid
is not modified until the text area is blurred.REQUEST_POST_UPDATE_SUCCESS
is dispatched.REQUEST_POST_UPDATE_SUCCESS
dispatchesRESET_POST
, which clears out the edits ineditor.present.edits
.editor.present.edits.content
is undefined,getEditedPostContent( state )
returns a serialisation of the blocks ineditor.present.blocksByUid
. But, since the editor was never blurred, this does not reflect the recent changes that were made! We therefore lose those changes.I've fixed this by patching
PostTextEditor
so that it copiesvalue
into local state when focused. This means that the contents of the text field won't change ifvalue
changes because of a dispatch.How Has This Been Tested?