-
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
Editor: Factor initial edits to own state #11267
Conversation
ff8f2f9
to
5f457aa
Compare
Edit: Added in rebased e59e7d0. |
Related: #10827 I'd incorporated |
e59e7d0
to
76b0097
Compare
76b0097
to
8d38ce4
Compare
* | ||
* @return {Object} Mutation-safe object. | ||
*/ | ||
function getMutateSafeObject( original, working ) { |
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.
Can we consider https://github.com/mweststrate/immer instead of this?
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.
Can we consider https://github.com/mweststrate/immer instead of this?
I'm not too opposed to the idea of it as a general pattern, but for one-off use it seems maybe a bit much both in terms of bundle size impact and developer knowledge barrier. Should we discuss separate to this pull request? I do think Immer seems to tackle well a pattern which is very prevalent in our stores.
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.
getMutateSafeObject
can also be considered "something to learn" but yes definitely a topic for a separate discussion.
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.
There are degrees of learning 😆
|
||
return action.edits; | ||
|
||
case 'SETUP_EDITOR_STATE': |
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.
This is a bit confusing? Why do we need to consider two "setup" actions here? can't we just merge both in one? It probably means only use SETUP_EDITOR_STATE
here.
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.
This is a bit confusing? Why do we need to consider two "setup" actions here? can't we just merge both in one? It probably means only use
SETUP_EDITOR_STATE
here.
Right, this confused me as well, and I left it as a note in the original comment to circle back to this:
Follow-up tasks:
[...]
SETUP_EDITOR
handling of blocks should be eliminated in favor of a consistent, singleRESET_BLOCKS
action.
This is a bit of consistency for consistency's sake. You can see similar use in untouched existing reducers of editor
.
|
||
return state; | ||
|
||
case 'UPDATE_POST': |
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.
Not sure I understand why we need to address this here? my thinking is that initialEdits should be "overriden" by regular edits anyway when saving, why do we need to change the "initial edits" when we "edit" a post?
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.
The issue we need to avoid is having an initial edit resurface when an edits
key is unset by UPDATE_POST
.
An alternative here (perhaps better even) is to unset keys from initialEdits
when an (assumed-user-initiated) EDIT_POST
occurs (i.e. the initial edit can be discarded at the point where a user defines their own 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.
How does a user unset
an edit? editPost( { something: undefined } )
? Can this be considered as revert to initial value, which means use initialEdit if defined?
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.
It becomes unset when the edit matches the saved value of the post. So if there's an initial edit lingering, it will become (wrongly) assumed as an edit.
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.
It becomes unset when the edit matches the saved value of the post
Should this be: "become unset if it matches the initial edit if defined, the saved value otherwise?
Isn't it a bug now?
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.
It becomes more an issue when considering the demo post, which should be immediately saveable even if technically editor.edits
would be empty (since it's all initial edits).
(We should have an E2E test for this)
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.
One thing I noticed which is weird though in our current behavior is that I'd have expected the "save" button to be disabled when you open the demo page without any modification. And if the current behavior is the right one, that I'm completely confused :P
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 wrote the last comment without reading your response :P )
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 think I may have mispoke here:
Edit title back to empty:
post = { title: '' }; initialEdits = { title: 'demo' }; edits = {}; // title omitted because matches post value
Data is never unset from edits
when the user makes a change in the interface:
gutenberg/packages/editor/src/store/reducer.js
Lines 228 to 243 in 3511dd8
case 'EDIT_POST': | |
case 'SETUP_EDITOR_STATE': | |
return reduce( action.edits, ( result, value, key ) => { | |
// Only assign into result if not already same value | |
if ( value !== state[ key ] ) { | |
// Avoid mutating original state by creating shallow | |
// clone. Should only occur once per reduce. | |
if ( result === state ) { | |
result = { ...state }; | |
} | |
result[ key ] = value; | |
} | |
return result; | |
}, state ); |
This only happens when they save the post, or the post is otherwise reset:
gutenberg/packages/editor/src/store/reducer.js
Lines 255 to 272 in 3511dd8
case 'UPDATE_POST': | |
case 'RESET_POST': | |
const getCanonicalValue = action.type === 'UPDATE_POST' ? | |
( key ) => action.edits[ key ] : | |
( key ) => getPostRawValue( action.post[ key ] ); | |
return reduce( state, ( result, value, key ) => { | |
if ( value !== getCanonicalValue( key ) ) { | |
return result; | |
} | |
if ( state === result ) { | |
result = { ...state }; | |
} | |
delete result[ key ]; | |
return result; | |
}, state ); |
Since these are the same triggers for resetting initialEdits
, there shouldn't be an issue here.
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'm working to fix up the failing tests. Must have gone wrong in the rebase. |
8d38ce4
to
8d21a69
Compare
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 still don't like that the initialEdits should consider any user edits. I think we should just keep them as initial edits until the first save.
But that said, that's not a big concern, I'm approving the PR. Let's move forward.
I'm not too opposed to dropping this. I don't think it has much of an impact in either case, given that user edits will always take precedence and never be unset until the save. I'm going to do a final pass over the changes here to see if it makes sense / if we need more test coverage / etc. |
In looking over the proposed changes, |
dc88ebd
to
3cb31b6
Compare
Aside: I wanted to refactor this test to use the query arguments for prepopulating a title ( gutenberg/test/e2e/specs/new-post.test.js Lines 55 to 75 in 2dddcc9
But in fact it fails, since the test makes the wrong assumption that only non-new posts can have a title. It still focuses the title for a new post, even if the title is non-empty. |
Extracted from / complements: #10844
Partially addresses: #7409
This pull request seeks to refactor the treatment of "initial edits" out of the
state.editor.present.edits
to allow for the existingedits
state to represent only user-initiated edits to be considered for change detection. This will enable some simplification of change detection reset conditions, and works toward #10844's goal of considering "dirtiness" as a condition of a non-emptyedits
state.Implementation notes:
While I think this partly moves away from a goal of eliminating the editor setup actions, it concedes in acknowledging that there should be at most a single parse which occurs at the start of an editing session, with consideration of both edits and content. Thus, it is implemented here as part of the effect handler for
SETUP_EDITOR
. I still have some faith this could be refactored in the future, but have chosen the most direct option to supporting #10844 here.Testing instructions:
Verify there are no regressions in the behavior of "initial edits", notably:
Ensure tests pass:
Follow-up tasks:
SETUP_EDITOR
handling of blocks should be eliminated in favor of a consistent, singleRESET_BLOCKS
action.