diff --git a/assets/src/edit-story/app/api/apiProvider.js b/assets/src/edit-story/app/api/apiProvider.js
index 5e8c7553095b..127b9420fbc8 100644
--- a/assets/src/edit-story/app/api/apiProvider.js
+++ b/assets/src/edit-story/app/api/apiProvider.js
@@ -46,6 +46,29 @@ function APIProvider({ children }) {
[stories]
);
+ const getStorySaveData = ({
+ pages,
+ featuredMedia,
+ stylePresets,
+ publisherLogo,
+ autoAdvance,
+ defaultPageDuration,
+ ...rest
+ }) => {
+ return {
+ story_data: {
+ version: DATA_VERSION,
+ pages,
+ autoAdvance,
+ defaultPageDuration,
+ },
+ featured_media: featuredMedia,
+ style_presets: stylePresets,
+ publisher_logo: publisherLogo,
+ ...rest,
+ };
+ };
+
const saveStoryById = useCallback(
/**
* Fire REST API call to save story.
@@ -53,46 +76,29 @@ function APIProvider({ children }) {
* @param {import('../../types').Story} story Story object.
* @return {Promise} Return apiFetch promise.
*/
- ({
- storyId,
- title,
- status,
- pages,
- author,
- slug,
- date,
- modified,
- content,
- excerpt,
- featuredMedia,
- password,
- stylePresets,
- publisherLogo,
- autoAdvance,
- defaultPageDuration,
- }) => {
+ (story) => {
+ const { storyId } = story;
return apiFetch({
path: `${stories}/${storyId}`,
- data: {
- title,
- status,
- author,
- password,
- slug,
- date,
- modified,
- content,
- excerpt,
- story_data: {
- version: DATA_VERSION,
- pages,
- autoAdvance,
- defaultPageDuration,
- },
- featured_media: featuredMedia,
- style_presets: stylePresets,
- publisher_logo: publisherLogo,
- },
+ data: getStorySaveData(story),
+ method: 'POST',
+ });
+ },
+ [stories]
+ );
+
+ const autoSaveById = useCallback(
+ /**
+ * Fire REST API call to save story.
+ *
+ * @param {import('../../types').Story} story Story object.
+ * @return {Promise} Return apiFetch promise.
+ */
+ (story) => {
+ const { storyId } = story;
+ return apiFetch({
+ path: `${stories}/${storyId}/autosaves`,
+ data: getStorySaveData(story),
method: 'POST',
});
},
@@ -225,6 +231,7 @@ function APIProvider({ children }) {
const state = {
actions: {
+ autoSaveById,
getStoryById,
getMedia,
getLinkMetadata,
diff --git a/assets/src/edit-story/app/story/actions/test/useAutoSave.js b/assets/src/edit-story/app/story/actions/test/useAutoSave.js
new file mode 100644
index 000000000000..9ba44c93115e
--- /dev/null
+++ b/assets/src/edit-story/app/story/actions/test/useAutoSave.js
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies
+ */
+import { renderHook, act } from '@testing-library/react-hooks';
+
+/**
+ * Internal dependencies
+ */
+import APIContext from '../../../api/context';
+import ConfigContext from '../../../config/context';
+import useAutoSave from '../useAutoSave';
+import getStoryMarkup from '../../../../output/utils/getStoryMarkup';
+
+jest.mock('../../../../output/utils/getStoryMarkup', () => jest.fn());
+
+function setup(args) {
+ const configValue = {
+ metadata: 'meta',
+ };
+ const autoSaveById = jest.fn();
+ const apiContextValue = {
+ actions: { autoSaveById },
+ };
+ const wrapper = (params) => (
+
+
+ {params.children}
+
+
+ );
+ const { result } = renderHook(() => useAutoSave(args), { wrapper });
+ return {
+ autoSave: result.current.autoSave,
+ autoSaveById,
+ };
+}
+
+describe('useAutoSave', () => {
+ it('should properly call autoSaveById when using autoSave', () => {
+ getStoryMarkup.mockImplementation(() => {
+ return 'Hello World!';
+ });
+ const story = {
+ storyId: 1,
+ title: 'Story!',
+ author: 1,
+ slug: 'story',
+ publisherLogo: 1,
+ defaultPageDuration: 7,
+ status: 'publish',
+ date: '2020-04-10T07:06:26',
+ modified: '',
+ excerpt: '',
+ featuredMedia: 0,
+ password: '',
+ stylePresets: '',
+ };
+ const pages = [
+ {
+ type: 'page',
+ id: '2',
+ elements: [
+ {
+ id: '2',
+ type: 'text',
+ x: 0,
+ y: 0,
+ },
+ ],
+ },
+ ];
+ const { autoSave, autoSaveById } = setup({
+ storyId: 1,
+ story,
+ pages,
+ });
+
+ autoSaveById.mockImplementation(() => ({
+ finally(callback) {
+ callback();
+ },
+ }));
+
+ act(() => {
+ autoSave();
+ });
+ expect(autoSaveById).toHaveBeenCalledTimes(1);
+
+ const expected = {
+ ...story,
+ pages,
+ content: 'Hello World!',
+ };
+ expect(autoSaveById).toHaveBeenCalledWith(expected);
+ });
+});
diff --git a/assets/src/edit-story/app/story/actions/useAutoSave.js b/assets/src/edit-story/app/story/actions/useAutoSave.js
new file mode 100644
index 000000000000..c967876bb32d
--- /dev/null
+++ b/assets/src/edit-story/app/story/actions/useAutoSave.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * External dependencies
+ */
+import { useCallback, useState } from 'react';
+
+/**
+ * Internal dependencies
+ */
+import { useAPI } from '../../api';
+import { useConfig } from '../../config';
+import getStoryPropsToSave from '../utils/getStoryPropsToSave';
+
+/**
+ * Custom hook to auto-save a story.
+ *
+ * @param {Object} properties Properties to update.
+ * @param {number} properties.storyId Story post id.
+ * @param {Array} properties.pages Array of all pages.
+ * @param {Object} properties.story Story-global properties
+ * @return {Function} Function that can be called to save a story.
+ */
+function useAutoSave({ storyId, pages, story }) {
+ const {
+ actions: { autoSaveById },
+ } = useAPI();
+ const { metadata } = useConfig();
+ const [isAutoSaving, setIsAutoSaving] = useState(false);
+
+ const autoSave = useCallback(
+ (props) => {
+ setIsAutoSaving(true);
+ return autoSaveById({
+ storyId,
+ ...getStoryPropsToSave({ story, pages, metadata }),
+ ...props,
+ }).finally(() => setIsAutoSaving(false));
+ },
+ [story, pages, metadata, autoSaveById, storyId]
+ );
+
+ return { autoSave, isAutoSaving };
+}
+
+export default useAutoSave;
diff --git a/assets/src/edit-story/app/story/actions/useSaveStory.js b/assets/src/edit-story/app/story/actions/useSaveStory.js
index 502aee466bbf..2db7ff68c743 100644
--- a/assets/src/edit-story/app/story/actions/useSaveStory.js
+++ b/assets/src/edit-story/app/story/actions/useSaveStory.js
@@ -18,7 +18,6 @@
* External dependencies
*/
import { useCallback, useState } from 'react';
-import { renderToStaticMarkup } from 'react-dom/server';
/**
* WordPress dependencies
@@ -31,24 +30,10 @@ import { __ } from '@wordpress/i18n';
import objectPick from '../../../utils/objectPick';
import { useAPI } from '../../api';
import { useConfig } from '../../config';
-import OutputStory from '../../../output/story';
import useRefreshPostEditURL from '../../../utils/useRefreshPostEditURL';
import { useSnackbar } from '../../snackbar';
import usePreventWindowUnload from '../../../utils/usePreventWindowUnload';
-
-/**
- * Creates AMP HTML markup for saving to DB for rendering in the FE.
- *
- * @param {import('../../../types').Story} story Story object.
- * @param {Array