-
Notifications
You must be signed in to change notification settings - Fork 4.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
Editor: Update the store to use Core Data entities. #16932
Changes from all commits
241a8e1
3daf3a0
90d5e78
4bd2fb1
db8bcab
0190c97
2cdd65d
d2f2320
24cb965
eec1666
b5f9d5f
1d48cd8
1e91c9b
16e69c8
d85aada
8f70fbe
e85a793
a3a7f55
3ef3016
0e76e4c
c271d86
6c96372
5cde8c8
f884aed
aeff970
af24680
aa81118
4630675
a020241
272dfd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,7 +132,7 @@ export function* editEntityRecord( kind, name, recordId, edits ) { | |
kind, | ||
name | ||
); | ||
const record = yield select( 'getEntityRecord', kind, name, recordId ); | ||
const record = yield select( 'getRawEntityRecord', kind, name, recordId ); | ||
const editedRecord = yield select( | ||
'getEditedEntityRecord', | ||
kind, | ||
|
@@ -147,9 +147,9 @@ export function* editEntityRecord( kind, name, recordId, edits ) { | |
// Clear edits when they are equal to their persisted counterparts | ||
// so that the property is not considered dirty. | ||
edits: Object.keys( edits ).reduce( ( acc, key ) => { | ||
const recordValue = get( record[ key ], 'raw', record[ key ] ); | ||
const recordValue = record[ key ]; | ||
const value = mergedEdits[ key ] ? | ||
merge( recordValue, edits[ key ] ) : | ||
merge( {}, recordValue, edits[ key ] ) : | ||
edits[ key ]; | ||
acc[ key ] = isEqual( recordValue, value ) ? undefined : value; | ||
return acc; | ||
|
@@ -235,9 +235,14 @@ export function* saveEntityRecord( | |
let error; | ||
try { | ||
const path = `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`; | ||
|
||
if ( isAutosave ) { | ||
// Most of this autosave logic is very specific to posts. | ||
// This is fine for now as it is the only supported autosave, | ||
// but ideally this should all be handled in the back end, | ||
// so the client just sends and receives objects. | ||
const persistedRecord = yield select( | ||
'getEntityRecord', | ||
'getRawEntityRecord', | ||
kind, | ||
name, | ||
recordId | ||
|
@@ -255,18 +260,49 @@ export function* saveEntityRecord( | |
// to the actual persisted entity if the edits don't | ||
// have a value. | ||
let data = { ...persistedRecord, ...autosavePost, ...record }; | ||
data = Object.keys( data ).reduce( ( acc, key ) => { | ||
if ( key in [ 'title', 'excerpt', 'content' ] ) { | ||
acc[ key ] = get( data[ key ], 'raw', data[ key ] ); | ||
} | ||
return acc; | ||
}, {} ); | ||
data = Object.keys( data ).reduce( | ||
( acc, key ) => { | ||
if ( [ 'title', 'excerpt', 'content' ].includes( key ) ) { | ||
// Edits should be the "raw" attribute values. | ||
acc[ key ] = get( data[ key ], 'raw', data[ key ] ); | ||
} | ||
return acc; | ||
}, | ||
{ status: data.status === 'auto-draft' ? 'draft' : data.status } | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This part is very specific to the "post" autosave. I don't mind for it being in core-data right now because that's the only auto-save but in a generic way, this could be handled on the backend somehow, just send everything and let the backend do its thing. For now maybe we can just add some comments to clarify. Also we might want to add e2e tests for the URL changes... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is probably a filter we could use. |
||
updatedRecord = yield apiFetch( { | ||
path: `${ path }/autosaves`, | ||
method: 'POST', | ||
data, | ||
} ); | ||
yield receiveAutosaves( persistedRecord.id, updatedRecord ); | ||
// An autosave may be processed by the server as a regular save | ||
// when its update is requested by the author and the post had | ||
// draft or auto-draft status. | ||
if ( persistedRecord.id === updatedRecord.id ) { | ||
let newRecord = { ...persistedRecord, ...data, ...updatedRecord }; | ||
newRecord = Object.keys( newRecord ).reduce( ( acc, key ) => { | ||
// These properties are persisted in autosaves. | ||
if ( [ 'title', 'excerpt', 'content' ].includes( key ) ) { | ||
// Edits should be the "raw" attribute values. | ||
acc[ key ] = get( newRecord[ key ], 'raw', newRecord[ key ] ); | ||
} else if ( key === 'status' ) { | ||
// Status is only persisted in autosaves when going from | ||
// "auto-draft" to "draft". | ||
acc[ key ] = | ||
persistedRecord.status === 'auto-draft' && | ||
newRecord.status === 'draft' ? | ||
newRecord.status : | ||
persistedRecord.status; | ||
} else { | ||
// These properties are not persisted in autosaves. | ||
acc[ key ] = get( persistedRecord[ key ], 'raw', persistedRecord[ key ] ); | ||
} | ||
return acc; | ||
}, {} ); | ||
yield receiveEntityRecords( kind, name, newRecord, undefined, true ); | ||
} else { | ||
yield receiveAutosaves( persistedRecord.id, updatedRecord ); | ||
} | ||
} else { | ||
updatedRecord = yield apiFetch( { | ||
path, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,6 +107,34 @@ export function getEntityRecord( state, kind, name, key ) { | |
return get( state.entities.data, [ kind, name, 'queriedData', 'items', key ] ); | ||
} | ||
|
||
/** | ||
* Returns the entity's record object by key, | ||
* with its attributes mapped to their raw values. | ||
* | ||
* @param {Object} state State tree. | ||
* @param {string} kind Entity kind. | ||
* @param {string} name Entity name. | ||
* @param {number} key Record's key. | ||
* | ||
* @return {Object?} Object with the entity's raw attributes. | ||
*/ | ||
export const getRawEntityRecord = createSelector( | ||
( state, kind, name, key ) => { | ||
const record = getEntityRecord( state, kind, name, key ); | ||
return ( | ||
record && | ||
Object.keys( record ).reduce( ( acc, _key ) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tabbing is a little wild here. Not sure why ESLint didn't catch this. |
||
// Because edits are the "raw" attribute values, | ||
// we return those from record selectors to make rendering, | ||
// comparisons, and joins with edits easier. | ||
acc[ _key ] = get( record[ _key ], 'raw', record[ _key ] ); | ||
return acc; | ||
}, {} ) | ||
); | ||
}, | ||
( state ) => [ state.entities.data ] | ||
); | ||
|
||
/** | ||
* Returns the Entity's records. | ||
* | ||
|
@@ -156,8 +184,7 @@ export function getEntityRecordEdits( state, kind, name, recordId ) { | |
export const getEntityRecordNonTransientEdits = createSelector( | ||
( state, kind, name, recordId ) => { | ||
const { transientEdits = {} } = getEntity( state, kind, name ); | ||
const edits = | ||
getEntityRecordEdits( state, kind, name, recordId ) || []; | ||
const edits = getEntityRecordEdits( state, kind, name, recordId ) || []; | ||
return Object.keys( edits ).reduce( ( acc, key ) => { | ||
if ( ! transientEdits[ key ] ) { | ||
acc[ key ] = edits[ key ]; | ||
|
@@ -194,16 +221,10 @@ export function hasEditsForEntityRecord( state, kind, name, recordId ) { | |
* @return {Object?} The entity record, merged with its edits. | ||
*/ | ||
export const getEditedEntityRecord = createSelector( | ||
( state, kind, name, recordId ) => { | ||
const record = getEntityRecord( state, kind, name, recordId ); | ||
return { | ||
...Object.keys( record ).reduce( ( acc, key ) => { | ||
acc[ key ] = get( record[ key ], 'raw', record[ key ] ); | ||
return acc; | ||
}, {} ), | ||
...getEntityRecordEdits( state, kind, name, recordId ), | ||
}; | ||
}, | ||
( state, kind, name, recordId ) => ( { | ||
...getRawEntityRecord( state, kind, name, recordId ), | ||
...getEntityRecordEdits( state, kind, name, recordId ), | ||
} ), | ||
( state ) => [ state.entities.data ] | ||
); | ||
|
||
|
@@ -237,12 +258,11 @@ export function isAutosavingEntityRecord( state, kind, name, recordId ) { | |
* @return {boolean} Whether the entity record is saving or not. | ||
*/ | ||
export function isSavingEntityRecord( state, kind, name, recordId ) { | ||
const { pending, isAutosave } = get( | ||
return get( | ||
state.entities.data, | ||
[ kind, name, 'saving', recordId ], | ||
{} | ||
[ kind, name, 'saving', recordId, 'pending' ], | ||
false | ||
); | ||
return Boolean( pending && ! isAutosave ); | ||
} | ||
|
||
/** | ||
|
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.
Any reason for this change? I wouldn't have expected this refactoring to touch the components directly?
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.
We need those replacements to be persistent so that
undo
works as expected in the E2E tests.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 mean what's changed? Why do we need this to make them persistent? How was it before?
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.
To be honest, I'm surprised it ever worked for this test case specifically:
If you do it at a normal pace, it works as expected and undos back to "1. ". But, if you do it super fast, the block editor never marks the last change before the transform as persistent and it goes back to "1". Could something be making this faster so now we can't rely on the time out trigerred
__unstableMarkLastChangeAsPersistent
?