diff --git a/docs/designers-developers/developers/data/data-core.md b/docs/designers-developers/developers/data/data-core.md
index c2d840ada30b8..bdfbe68438a39 100644
--- a/docs/designers-developers/developers/data/data-core.md
+++ b/docs/designers-developers/developers/data/data-core.md
@@ -343,6 +343,21 @@ _Returns_
- `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made.
+# **isAutosavingEntityRecord**
+
+Returns true if the specified entity record is autosaving, and false otherwise.
+
+_Parameters_
+
+- _state_ `Object`: State tree.
+- _kind_ `string`: Entity kind.
+- _name_ `string`: Entity name.
+- _recordId_ `number`: Record ID.
+
+_Returns_
+
+- `boolean`: Whether the entity record is autosaving or not.
+
# **isPreviewEmbedFallback**
Determines if the returned preview is an oEmbed link fallback.
@@ -387,7 +402,7 @@ _Parameters_
_Returns_
-- `?Object`: Whether the entity record is saving or not.
+- `boolean`: Whether the entity record is saving or not.
@@ -545,6 +560,7 @@ _Parameters_
- _kind_ `string`: Kind of the entity.
- _name_ `string`: Name of the entity.
- _recordId_ `Object`: ID of the record.
+- _options_ `Object`: Saving options.
# **saveEntityRecord**
@@ -555,6 +571,7 @@ _Parameters_
- _kind_ `string`: Kind of the received entity.
- _name_ `string`: Name of the received entity.
- _record_ `Object`: Record to be saved.
+- _options_ `Object`: Saving options.
# **undo**
diff --git a/packages/core-data/README.md b/packages/core-data/README.md
index 73bac19d076d0..cc89ec095f511 100644
--- a/packages/core-data/README.md
+++ b/packages/core-data/README.md
@@ -191,6 +191,7 @@ _Parameters_
- _kind_ `string`: Kind of the entity.
- _name_ `string`: Name of the entity.
- _recordId_ `Object`: ID of the record.
+- _options_ `Object`: Saving options.
# **saveEntityRecord**
@@ -201,6 +202,7 @@ _Parameters_
- _kind_ `string`: Kind of the received entity.
- _name_ `string`: Name of the received entity.
- _record_ `Object`: Record to be saved.
+- _options_ `Object`: Saving options.
# **undo**
@@ -552,6 +554,21 @@ _Returns_
- `boolean`: Whether or not the user can upload media. Defaults to `true` if the OPTIONS request is being made.
+# **isAutosavingEntityRecord**
+
+Returns true if the specified entity record is autosaving, and false otherwise.
+
+_Parameters_
+
+- _state_ `Object`: State tree.
+- _kind_ `string`: Entity kind.
+- _name_ `string`: Entity name.
+- _recordId_ `number`: Record ID.
+
+_Returns_
+
+- `boolean`: Whether the entity record is autosaving or not.
+
# **isPreviewEmbedFallback**
Determines if the returned preview is an oEmbed link fallback.
@@ -596,7 +613,7 @@ _Parameters_
_Returns_
-- `?Object`: Whether the entity record is saving or not.
+- `boolean`: Whether the entity record is saving or not.
diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js
index 8e78957bbb675..45e42d7b36673 100644
--- a/packages/core-data/src/actions.js
+++ b/packages/core-data/src/actions.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { castArray, merge, isEqual, find } from 'lodash';
+import { castArray, get, merge, isEqual, find } from 'lodash';
/**
* Internal dependencies
@@ -127,7 +127,11 @@ export function receiveEmbedPreview( url, preview ) {
* @return {Object} Action object.
*/
export function* editEntityRecord( kind, name, recordId, edits ) {
- const { transientEdits = {}, mergedEdits = {} } = yield select( 'getEntity', kind, name );
+ const { transientEdits = {}, mergedEdits = {} } = yield select(
+ 'getEntity',
+ kind,
+ name
+ );
const record = yield select( 'getEntityRecord', kind, name, recordId );
const editedRecord = yield select(
'getEditedEntityRecord',
@@ -143,10 +147,11 @@ 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 value = mergedEdits[ key ] ?
- merge( record[ key ], edits[ key ] ) :
+ merge( recordValue, edits[ key ] ) :
edits[ key ];
- acc[ key ] = isEqual( record[ key ], value ) ? undefined : value;
+ acc[ key ] = isEqual( recordValue, value ) ? undefined : value;
return acc;
}, {} ),
transientEdits,
@@ -209,29 +214,73 @@ export function* redo() {
* @param {string} kind Kind of the received entity.
* @param {string} name Name of the received entity.
* @param {Object} record Record to be saved.
+ * @param {Object} options Saving options.
*/
-export function* saveEntityRecord( kind, name, record ) {
+export function* saveEntityRecord(
+ kind,
+ name,
+ record,
+ { isAutosave = false } = { isAutosave: false }
+) {
const entities = yield getKindEntities( kind );
const entity = find( entities, { kind, name } );
if ( ! entity ) {
return;
}
- const key = entity.key || DEFAULT_ENTITY_KEY;
- const recordId = record[ key ];
+ const entityIdKey = entity.key || DEFAULT_ENTITY_KEY;
+ const recordId = record[ entityIdKey ];
- yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId };
+ yield { type: 'SAVE_ENTITY_RECORD_START', kind, name, recordId, isAutosave };
let error;
try {
- const updatedRecord = yield apiFetch( {
- path: `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`,
- method: recordId ? 'PUT' : 'POST',
- data: record,
- } );
- yield receiveEntityRecords( kind, name, updatedRecord, undefined, true );
+ const path = `${ entity.baseURL }${ recordId ? '/' + recordId : '' }`;
+ if ( isAutosave ) {
+ const persistedRecord = yield select(
+ 'getEntityRecord',
+ kind,
+ name,
+ recordId
+ );
+ const currentUser = yield select( 'getCurrentUser' );
+ const currentUserId = currentUser ? currentUser.id : undefined;
+ const autosavePost = yield select(
+ 'getAutosave',
+ persistedRecord.type,
+ persistedRecord.id,
+ currentUserId
+ );
+ 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;
+ }, {} );
+ const autosave = yield apiFetch( {
+ path: `${ path }/autosaves`,
+ method: 'POST',
+ data,
+ } );
+ yield receiveAutosaves( persistedRecord.id, autosave );
+ } else {
+ const updatedRecord = yield apiFetch( {
+ path,
+ method: recordId ? 'PUT' : 'POST',
+ data: record,
+ } );
+ yield receiveEntityRecords( kind, name, updatedRecord, undefined, true );
+ }
} catch ( _error ) {
error = _error;
}
- yield { type: 'SAVE_ENTITY_RECORD_FINISH', kind, name, recordId, error };
+ yield {
+ type: 'SAVE_ENTITY_RECORD_FINISH',
+ kind,
+ name,
+ recordId,
+ error,
+ isAutosave,
+ };
}
/**
@@ -240,8 +289,9 @@ export function* saveEntityRecord( kind, name, record ) {
* @param {string} kind Kind of the entity.
* @param {string} name Name of the entity.
* @param {Object} recordId ID of the record.
+ * @param {Object} options Saving options.
*/
-export function* saveEditedEntityRecord( kind, name, recordId ) {
+export function* saveEditedEntityRecord( kind, name, recordId, options ) {
if ( ! ( yield select( 'hasEditsForEntityRecord', kind, name, recordId ) ) ) {
return;
}
@@ -252,7 +302,7 @@ export function* saveEditedEntityRecord( kind, name, recordId ) {
recordId
);
const record = { id: recordId, ...edits };
- yield* saveEntityRecord( kind, name, record );
+ yield* saveEntityRecord( kind, name, record, options );
}
/**
diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js
index 96ed12e5ef78c..7f0972e19e481 100644
--- a/packages/core-data/src/reducer.js
+++ b/packages/core-data/src/reducer.js
@@ -213,6 +213,7 @@ function entity( entityConfig ) {
[ action.recordId ]: {
pending: action.type === 'SAVE_ENTITY_RECORD_START',
error: action.error,
+ isAutosave: action.isAutosave,
},
};
}
diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js
index bc418e3482ba6..c8aec286a1107 100644
--- a/packages/core-data/src/selectors.js
+++ b/packages/core-data/src/selectors.js
@@ -207,6 +207,25 @@ export const getEditedEntityRecord = createSelector(
( state ) => [ state.entities.data ]
);
+/**
+ * Returns true if the specified entity record is autosaving, and false otherwise.
+ *
+ * @param {Object} state State tree.
+ * @param {string} kind Entity kind.
+ * @param {string} name Entity name.
+ * @param {number} recordId Record ID.
+ *
+ * @return {boolean} Whether the entity record is autosaving or not.
+ */
+export function isAutosavingEntityRecord( state, kind, name, recordId ) {
+ const { pending, isAutosave } = get(
+ state.entities.data,
+ [ kind, name, 'saving', recordId ],
+ {}
+ );
+ return Boolean( pending && isAutosave );
+}
+
/**
* Returns true if the specified entity record is saving, and false otherwise.
*
@@ -215,14 +234,15 @@ export const getEditedEntityRecord = createSelector(
* @param {string} name Entity name.
* @param {number} recordId Record ID.
*
- * @return {Object?} Whether the entity record is saving or not.
+ * @return {boolean} Whether the entity record is saving or not.
*/
export function isSavingEntityRecord( state, kind, name, recordId ) {
- return get(
+ const { pending, isAutosave } = get(
state.entities.data,
- [ kind, name, 'saving', recordId, 'pending' ],
- false
+ [ kind, name, 'saving', recordId ],
+ {}
);
+ return Boolean( pending && ! isAutosave );
}
/**