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} pages List of pages. - * @param {Object} metadata Metadata. - * @return {Element} Story markup. - */ -const getStoryMarkup = (story, pages, metadata) => { - return renderToStaticMarkup( - - ); -}; +import getStoryPropsToSave from '../utils/getStoryPropsToSave'; /** * Custom hook to save story. @@ -73,27 +58,9 @@ function useSaveStory({ storyId, pages, story, updateStory }) { const saveStory = useCallback( (props) => { setIsSaving(true); - const propsToSave = objectPick(story, [ - 'title', - 'status', - 'author', - 'date', - 'modified', - 'slug', - 'excerpt', - 'featuredMedia', - 'password', - 'publisherLogo', - 'stylePresets', - 'autoAdvance', - 'defaultPageDuration', - ]); - const content = getStoryMarkup(story, pages, metadata); return saveStoryById({ storyId, - content, - pages, - ...propsToSave, + ...getStoryPropsToSave({ story, pages, metadata }), ...props, }) .then((post) => { diff --git a/assets/src/edit-story/app/story/storyProvider.js b/assets/src/edit-story/app/story/storyProvider.js index b41d9e21f5f6..5dd592400d43 100644 --- a/assets/src/edit-story/app/story/storyProvider.js +++ b/assets/src/edit-story/app/story/storyProvider.js @@ -32,6 +32,7 @@ import useHistoryReplay from './effects/useHistoryReplay'; import usePageBackgrounds from './effects/usePageBackgrounds'; import useStoryReducer from './useStoryReducer'; import useDeleteStory from './actions/useDeleteStory'; +import useAutoSave from './actions/useAutoSave'; function StoryProvider({ storyId, children }) { const { @@ -108,6 +109,12 @@ function StoryProvider({ storyId, children }) { story, updateStory, }); + + const { autoSave, isAutoSaving } = useAutoSave({ + storyId, + pages, + story, + }); const { deleteStory } = useDeleteStory({ storyId }); const state = { @@ -123,11 +130,12 @@ function StoryProvider({ storyId, children }) { story, capabilities, meta: { - isSaving, + isSaving: isSaving || isAutoSaving, }, }, actions: { ...api, + autoSave, saveStory, deleteStory, }, diff --git a/assets/src/edit-story/app/story/utils/getStoryPropsToSave.js b/assets/src/edit-story/app/story/utils/getStoryPropsToSave.js new file mode 100644 index 000000000000..0f0a5cf3b5db --- /dev/null +++ b/assets/src/edit-story/app/story/utils/getStoryPropsToSave.js @@ -0,0 +1,46 @@ +/* + * 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. + */ +/** + * Internal dependencies + */ +import objectPick from '../../../utils/objectPick'; +import getStoryMarkup from '../../../output/utils/getStoryMarkup'; + +function getStoryPropsToSave({ story, pages, metadata }) { + const propsFromStory = objectPick(story, [ + 'title', + 'status', + 'author', + 'date', + 'modified', + 'slug', + 'excerpt', + 'featuredMedia', + 'password', + 'publisherLogo', + 'stylePresets', + 'autoAdvance', + 'defaultPageDuration', + ]); + const content = getStoryMarkup(story, pages, metadata); + return { + content, + pages, + ...propsFromStory, + }; +} + +export default getStoryPropsToSave; diff --git a/assets/src/edit-story/app/story/utils/test/getStoryPropsToSave.js b/assets/src/edit-story/app/story/utils/test/getStoryPropsToSave.js new file mode 100644 index 000000000000..5ab79f773991 --- /dev/null +++ b/assets/src/edit-story/app/story/utils/test/getStoryPropsToSave.js @@ -0,0 +1,62 @@ +/* + * 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. + */ +/** + * Internal dependencies + */ +import getStoryPropsToSave from '../getStoryPropsToSave'; +import getStoryMarkup from '../../../../output/utils/getStoryMarkup'; + +jest.mock('../../../../output/utils/getStoryMarkup', () => jest.fn()); + +describe('getStoryPropsToSave', () => { + it('should return correct story properties', () => { + const neededProps = { + title: 'Story!', + author: 1, + slug: 'story', + publisherLogo: 1, + status: 'publish', + date: '2020-04-10T07:06:26', + modified: '', + excerpt: '', + featuredMedia: 0, + password: '', + stylePresets: '', + autoAdvance: 'manual', + defaultPageDuration: 7, + }; + const extraProps = { + storyId: 1, + foo: 'bar', + }; + const story = { + ...neededProps, + ...extraProps, + }; + const pages = [{ id: '1' }, { id: '2' }]; + const metadata = {}; + getStoryMarkup.mockImplementation(() => { + return 'Hello World!'; + }); + const props = getStoryPropsToSave({ story, pages, metadata }); + + expect(props).toStrictEqual({ + content: 'Hello World!', + pages, + ...neededProps, + }); + }); +}); diff --git a/assets/src/edit-story/components/header/buttons.js b/assets/src/edit-story/components/header/buttons.js index 90d659a94394..418fa78d9220 100644 --- a/assets/src/edit-story/components/header/buttons.js +++ b/assets/src/edit-story/components/header/buttons.js @@ -29,7 +29,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import addQueryArgs from '../../utils/addQueryArgs'; -import { useStory, useMedia } from '../../app'; +import { useStory, useMedia, useConfig } from '../../app'; import useRefreshPostEditURL from '../../utils/useRefreshPostEditURL'; import { Outline, Plain, Primary } from '../button'; import CircularProgress from '../circularProgress'; @@ -57,20 +57,25 @@ function PreviewButton() { const { state: { meta: { isSaving }, - story: { link }, + story: { link, status }, }, - actions: { saveStory }, + actions: { autoSave, saveStory }, } = useStory(); + const { previewLink: autoSaveLink } = useConfig(); const [previewLinkToOpenViaDialog, setPreviewLinkToOpenViaDialog] = useState( null ); + const isDraft = 'draft' === status; /** * Open a preview of the story in current window. */ const openPreviewLink = () => { - const previewLink = addQueryArgs(link, { preview: 'true' }); + // Display the actual link in case of a draft. + const previewLink = isDraft + ? addQueryArgs(link, { preview: 'true' }) + : autoSaveLink; // Start a about:blank popup with waiting message until we complete // the saving operation. That way we will not bust the popup timeout. @@ -104,27 +109,21 @@ function PreviewButton() { // will be resolved after the story is saved. } - // @todo: See https://github.com/google/web-stories-wp/issues/1149. This - // has the effect of pushing the changes to a published story. - saveStory().then(() => { - let previewOpened = false; - if (popup && !popup.closed) { - try { - // The `popup.location.href` will fail if the expected window has - // been naviagted to a different origin. + // Save story directly if draft, otherwise, use auto-save. + const updateFunc = isDraft ? saveStory : autoSave; + updateFunc() + .then((update) => { + if (popup && !popup.closed) { if (popup.location.href) { - popup.location.replace(previewLink); - previewOpened = true; + // Auto-save sends an updated preview link, use that instead if available. + const updatedPreviewLink = update?.preview_link ?? previewLink; + popup.location.replace(updatedPreviewLink); } - } catch (e) { - // Ignore the errors. They will simply trigger the "try again" - // dialog. } - } - if (!previewOpened) { + }) + .catch(() => { setPreviewLinkToOpenViaDialog(previewLink); - } - }); + }); }; const openPreviewLinkSync = (evt) => { @@ -138,7 +137,7 @@ function PreviewButton() { return ( <> - {__('Save & Preview', 'web-stories')} + {__('Preview', 'web-stories')} + saveStory()} isDisabled={isSaving || isUploading}> {text} ); diff --git a/assets/src/edit-story/components/header/test/buttons.js b/assets/src/edit-story/components/header/test/buttons.js index 61136caad759..7db92c730b8c 100644 --- a/assets/src/edit-story/components/header/test/buttons.js +++ b/assets/src/edit-story/components/header/test/buttons.js @@ -24,35 +24,53 @@ import { ThemeProvider } from 'styled-components'; * Internal dependencies */ import StoryContext from '../../../app/story/context'; +import ConfigContext from '../../../app/config/context'; import Buttons from '../buttons'; import theme from '../../../theme'; function setupButtons(extraStoryProps, extraMetaProps) { const saveStory = jest.fn(); + const autoSave = jest.fn(); const storyContextValue = { state: { meta: { isSaving: false, ...extraMetaProps }, story: { status: 'draft', storyId: 123, date: null, ...extraStoryProps }, }, - actions: { saveStory }, + actions: { saveStory, autoSave }, + }; + const configValue = { + previewLink: + 'https://example.com?preview_id=1679&preview_nonce=b5ea827939&preview=true', }; const { getByText, container } = render( - - - + + + + + ); return { container, getByText, + autoSave, saveStory, }; } describe('buttons', () => { const FUTURE_DATE = '9999-01-01T20:20:20'; + const PREVIEW_POPUP = { + document: { + write: jest.fn(), + }, + location: { + href: 'about:blank', + replace: jest.fn(), + }, + }; it('should display Publish button when in draft mode', () => { const { getByText } = setupButtons(); @@ -105,17 +123,20 @@ describe('buttons', () => { expect(getByRole(container, 'progressbar')).toBeInTheDocument(); }); - it('should open preview when clicking on Preview via about:blank', () => { + it('should open draft preview when clicking on Preview via about:blank', () => { const { getByText, saveStory } = setupButtons({ link: 'https://example.com', }); - const previewButton = getByText('Save & Preview'); + const previewButton = getByText('Preview'); expect(previewButton).toBeDefined(); saveStory.mockImplementation(() => ({ then(callback) { callback(); + return { + catch: () => {}, + }; }, })); @@ -127,15 +148,7 @@ describe('buttons', () => { open: mockedOpen, })); - const popup = { - document: { - write: jest.fn(), - }, - location: { - href: 'about:blank', - replace: jest.fn(), - }, - }; + const popup = PREVIEW_POPUP; mockedOpen.mockImplementation(() => popup); fireEvent.click(previewButton); @@ -148,4 +161,40 @@ describe('buttons', () => { windowSpy.mockRestore(); }); + + it('should open preview for a published story when clicking on Preview via about:blank', () => { + const { getByText, autoSave } = setupButtons({ + link: 'https://example.com', + status: 'publish', + }); + const previewButton = getByText('Preview'); + autoSave.mockImplementation(() => ({ + then(callback) { + callback(); + return { + catch: () => {}, + }; + }, + })); + + const mockedOpen = jest.fn(); + const originalWindow = { ...window }; + const windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + ...originalWindow, + open: mockedOpen, + })); + + const popup = PREVIEW_POPUP; + mockedOpen.mockImplementation(() => popup); + + fireEvent.click(previewButton); + + expect(autoSave).toHaveBeenCalledWith(); + expect(popup.location.replace).toHaveBeenCalledWith( + 'https://example.com?preview_id=1679&preview_nonce=b5ea827939&preview=true' + ); + + windowSpy.mockRestore(); + }); }); diff --git a/assets/src/edit-story/output/utils/getStoryMarkup.js b/assets/src/edit-story/output/utils/getStoryMarkup.js new file mode 100644 index 000000000000..06aa369f2e97 --- /dev/null +++ b/assets/src/edit-story/output/utils/getStoryMarkup.js @@ -0,0 +1,39 @@ +/* + * 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 { renderToStaticMarkup } from 'react-dom/server'; + +/** + * Internal dependencies + */ +import OutputStory from '../story'; + +/** + * Creates AMP HTML markup for saving to DB for rendering in the FE. + * + * @param {import('../../../types').Story} story Story object. + * @param {Array} pages List of pages. + * @param {Object} metadata Metadata. + * @return {string} Story markup. + */ +export default function getStoryMarkup(story, pages, metadata) { + return renderToStaticMarkup( + + ); +} diff --git a/assets/src/edit-story/output/utils/test/getStoryMarkup.js b/assets/src/edit-story/output/utils/test/getStoryMarkup.js new file mode 100644 index 000000000000..b003e1e8da64 --- /dev/null +++ b/assets/src/edit-story/output/utils/test/getStoryMarkup.js @@ -0,0 +1,88 @@ +/* + * 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. + */ + +/** + * Internal dependencies + */ +import getStoryMarkup from '../getStoryMarkup'; + +describe('getStoryMarkup', () => { + it('should generate expected story markup', () => { + const story = { + storyId: 1, + title: 'Story!', + author: 1, + slug: 'story', + link: 'https://example.com', + publisherLogoUrl: 'https://example.com', + defaultPageDuration: 7, + status: 'publish', + date: '2020-04-10T07:06:26', + modified: '', + excerpt: '', + featuredMedia: 0, + password: '', + }; + const meta = { + publisher: { + name: 'AMP', + logo: 'https://example.com/fallback-wordpress-publisher-logo.png', + }, + logoPlaceholder: + 'https://example.com/fallback-wordpress-publisher-logo.png', + fallbackPoster: 'https://example.com/fallback-poster.jpg', + }; + const pages = [ + { + type: 'page', + id: '2', + elements: [ + { + id: '2', + type: 'text', + x: 0, + y: 0, + width: 211, + height: 221, + rotationAngle: 1, + content: 'Hello World', + color: { + color: { + r: 255, + g: 255, + b: 255, + a: 0.5, + }, + }, + padding: { + vertical: 0, + horizontal: 0, + }, + }, + ], + }, + ]; + const markup = getStoryMarkup(story, pages, meta); + expect(markup).toContain('Hello World'); + expect(markup).toContain('transform:rotate(1deg)'); + expect(markup).toContain( + '' + ); + expect(markup).toContain( + 'poster-portrait-src="https://example.com/fallback-poster.jpg"' + ); + }); +}); diff --git a/includes/REST_API/Stories_Controller.php b/includes/REST_API/Stories_Controller.php index 684ca71d0a94..627e6c961e33 100644 --- a/includes/REST_API/Stories_Controller.php +++ b/includes/REST_API/Stories_Controller.php @@ -178,6 +178,7 @@ public function get_item_schema() { $schema['properties']['story_data'] = [ 'description' => __( 'Story data stored as a JSON object. Stored in post_content_filtered field.', 'web-stories' ), + 'type' => 'object', 'context' => [ 'edit' ], 'default' => [], ]; diff --git a/includes/Story_Post_Type.php b/includes/Story_Post_Type.php index 425b7298b17f..40586aa2139c 100644 --- a/includes/Story_Post_Type.php +++ b/includes/Story_Post_Type.php @@ -304,6 +304,11 @@ public static function admin_enqueue_scripts( $hook ) { $max_upload_size = 0; } + $preview_query_args = [ + 'preview_id' => $story_id, + // Leveraging the default WP post preview logic. + 'preview_nonce' => wp_create_nonce( 'post_preview_' . $story_id ), + ]; wp_localize_script( self::WEB_STORIES_SCRIPT_HANDLE, 'webStoriesEditorSettings', @@ -316,7 +321,7 @@ public static function admin_enqueue_scripts( $hook ) { 'allowedFileTypes' => self::get_allowed_file_types(), 'postType' => self::POST_TYPE_SLUG, 'storyId' => $story_id, - 'previewLink' => get_preview_post_link( $story_id ), + 'previewLink' => get_preview_post_link( $story_id, $preview_query_args ), 'maxUpload' => $max_upload_size, 'pluginDir' => WEBSTORIES_PLUGIN_DIR_URL, 'api' => [