diff --git a/packages/e2e-tests/specs/editor/various/preview.test.js b/packages/e2e-tests/specs/editor/various/preview.test.js index db92a0ff26e8c2..004c3944e9c86f 100644 --- a/packages/e2e-tests/specs/editor/various/preview.test.js +++ b/packages/e2e-tests/specs/editor/various/preview.test.js @@ -23,6 +23,8 @@ async function openPreviewPage( editorPage ) { let openTabs = await browser.pages(); const expectedTabsCount = openTabs.length + 1; await editorPage.click( '.editor-post-preview' ); + await editorPage.waitForSelector( '.editor-post-preview__new-tab' ); + await editorPage.click( '.editor-post-preview__new-tab' ); // Wait for the new tab to open. while ( openTabs.length < expectedTabsCount ) { @@ -31,9 +33,7 @@ async function openPreviewPage( editorPage ) { } const previewPage = last( openTabs ); - // Wait for the preview to load. We can't do interstitial detection here, - // because it might load too quickly for us to pick up, so we wait for - // the preview to load by waiting for the title to appear. + // Wait for the preview to load by waiting for the title to appear. await previewPage.waitForSelector( '.entry-title' ); return previewPage; } @@ -48,6 +48,8 @@ async function openPreviewPage( editorPage ) { */ async function waitForPreviewNavigation( previewPage ) { await page.click( '.editor-post-preview' ); + await page.waitForSelector( '.editor-post-preview__new-tab' ); + await page.click( '.editor-post-preview__new-tab' ); return previewPage.waitForNavigation(); } @@ -117,6 +119,7 @@ describe( 'Preview', () => { // Return to editor to change title. await editorPage.bringToFront(); + await editorPage.click( '[aria-label="Close dialog"' ); await editorPage.type( '.editor-post-title__input', '!' ); await waitForPreviewNavigation( previewPage ); @@ -127,12 +130,14 @@ describe( 'Preview', () => { // Pressing preview without changes should bring same preview window to // front and reload, but should not show interstitial. await editorPage.bringToFront(); + await editorPage.click( '[aria-label="Close dialog"' ); await waitForPreviewNavigation( previewPage ); previewTitle = await previewPage.$eval( '.entry-title', ( node ) => node.textContent ); expect( previewTitle ).toBe( 'Hello World!' ); // Preview for published post (no unsaved changes) directs to canonical URL for post. await editorPage.bringToFront(); + await editorPage.click( '[aria-label="Close dialog"' ); await publishPost(); // Return to editor to change title. @@ -156,6 +161,7 @@ describe( 'Preview', () => { // // See: https://github.com/WordPress/gutenberg/issues/7561 await editorPage.bringToFront(); + await editorPage.click( '[aria-label="Close dialog"' ); await waitForPreviewNavigation( previewPage ); // Title in preview should match updated input. @@ -185,6 +191,7 @@ describe( 'Preview', () => { // Return to editor. await editorPage.bringToFront(); + await editorPage.click( '[aria-label="Close dialog"' ); // Append bbbbb to the title, and tab away from the title so blur event is triggered. await editorPage.type( '.editor-post-title__input', 'bbbbb' ); @@ -238,6 +245,7 @@ describe( 'Preview with Custom Fields enabled', () => { // Return to editor and modify the title and content. await editorPage.bringToFront(); + await editorPage.click( '[aria-label="Close dialog"' ); await editorPage.click( '.editor-post-title__input' ); await pressKeyWithModifier( 'primary', 'a' ); await editorPage.keyboard.press( 'Delete' ); @@ -258,5 +266,6 @@ describe( 'Preview with Custom Fields enabled', () => { // Make sure the editor is active for the afterEach function. await editorPage.bringToFront(); + await editorPage.click( '[aria-label="Close dialog"' ); } ); } ); diff --git a/packages/editor/src/components/post-preview-button/index.js b/packages/editor/src/components/post-preview-button/index.js index 415d9da9ba070d..6b36ad4312c428 100644 --- a/packages/editor/src/components/post-preview-button/index.js +++ b/packages/editor/src/components/post-preview-button/index.js @@ -6,96 +6,35 @@ import { get } from 'lodash'; /** * WordPress dependencies */ -import { Component, renderToString } from '@wordpress/element'; -import { Button, Path, SVG } from '@wordpress/components'; +import { Component } from '@wordpress/element'; +import { Button, IconButton, Modal, Path, SVG } from '@wordpress/components'; import { __, _x } from '@wordpress/i18n'; import { withSelect, withDispatch } from '@wordpress/data'; import { ifCondition, compose } from '@wordpress/compose'; -import { applyFilters } from '@wordpress/hooks'; - -function writeInterstitialMessage( targetDocument ) { - let markup = renderToString( -
- - - - -

{ __( 'Generating preview…' ) }

-
- ); - - markup += ` - - `; - /** - * Filters the interstitial message shown when generating previews. - * - * @param {string} markup The preview interstitial markup. - */ - markup = applyFilters( 'editor.PostPreview.interstitialMarkup', markup ); - - targetDocument.write( markup ); - targetDocument.title = __( 'Generating preview…' ); - targetDocument.close(); -} +const DesktopIcon = ; +const MobileIcon = ; +const TabletIcon = ; export class PostPreviewButton extends Component { constructor() { super( ...arguments ); - this.openPreviewWindow = this.openPreviewWindow.bind( this ); + this.state = { + isPreviewOpen: false, + previewSize: { + width: '1200px', + height: '800px', + }, + }; + this.openPreviewInNewTab = this.openPreviewInNewTab.bind( this ); + this.getWindowTarget = this.getWindowTarget.bind( this ); + this.setPreviewWindowLink = this.setPreviewWindowLink.bind( this ); + this.openPreviewModal = this.openPreviewModal.bind( this ); + this.closePreviewModal = this.closePreviewModal.bind( this ); + this.setDesktopPreview = this.setDesktopPreview.bind( this ); + this.setTabletPreview = this.setTabletPreview.bind( this ); + this.setMobilePreview = this.setMobilePreview.bind( this ); } componentDidUpdate( prevProps ) { @@ -128,7 +67,24 @@ export class PostPreviewButton extends Component { return `wp-preview-${ postId }`; } - openPreviewWindow( event ) { + openPreviewModal() { + this.setState( { isPreviewOpen: true } ); + + // If we don't need to autosave the post before previewing, do nothing. + if ( ! this.props.isAutosaveable ) { + return; + } + + // Request an autosave. This happens asynchronously and causes the component + // to update when finished. + if ( this.props.isDraft ) { + this.props.savePost( { isPreview: true } ); + } else { + this.props.autosave( { isPreview: true } ); + } + } + + openPreviewInNewTab( event ) { // Our Preview button has its 'href' and 'target' set correctly for a11y // purposes. Unfortunately, though, we can't rely on the default 'click' // handler since sometimes it incorrectly opens a new tab instead of reusing @@ -138,7 +94,7 @@ export class PostPreviewButton extends Component { // Open up a Preview tab if needed. This is where we'll show the preview. if ( ! this.previewWindow || this.previewWindow.closed ) { - this.previewWindow = window.open( '', this.getWindowTarget() ); + this.previewWindow = window.open( '', '_blank' ); } // Focus the Preview tab. This might not do anything, depending on the browser's @@ -146,51 +102,123 @@ export class PostPreviewButton extends Component { // https://html.spec.whatwg.org/multipage/interaction.html#dom-window-focus this.previewWindow.focus(); - // If we don't need to autosave the post before previewing, then we simply - // load the Preview URL in the Preview tab. - if ( ! this.props.isAutosaveable ) { - this.setPreviewWindowLink( event.target.href ); - return; - } + // Load the Preview URL in the Preview tab. + this.setPreviewWindowLink( event.target.href ); + } - // Request an autosave. This happens asynchronously and causes the component - // to update when finished. - if ( this.props.isDraft ) { - this.props.savePost( { isPreview: true } ); - } else { - this.props.autosave( { isPreview: true } ); - } + setDesktopPreview() { + this.setState( { + previewSize: { + width: '1200px', + height: '800px', + }, + } ); + } + + setTabletPreview() { + this.setState( { + previewSize: { + width: '768px', + height: '1024px', + }, + } ); + } - // Display a 'Generating preview' message in the Preview tab while we wait for the - // autosave to finish. - writeInterstitialMessage( this.previewWindow.document ); + setMobilePreview() { + this.setState( { + previewSize: { + width: '375px', + height: '812px', + }, + } ); + } + + closePreviewModal() { + this.setState( { isPreviewOpen: false } ); } render() { const { previewLink, currentPostLink, isSaveable } = this.props; - // Link to the `?preview=true` URL if we have it, since this lets us see // changes that were autosaved since the post was last published. Otherwise, // just link to the post's URL. const href = previewLink || currentPostLink; return ( - + __( '(opens in a new tab)' ) + } + + + { this.state.isPreviewOpen && + +
+ + + + +
+
+ +
+
+ } + ); } } diff --git a/packages/editor/src/components/post-preview-button/style.scss b/packages/editor/src/components/post-preview-button/style.scss new file mode 100644 index 00000000000000..507a25dd734176 --- /dev/null +++ b/packages/editor/src/components/post-preview-button/style.scss @@ -0,0 +1,33 @@ +.editor-post-preview-button__preview-modal { + @include break-small() { + top: 60px; + right: 60px; + bottom: 60px; + left: 60px; + transform: none; + } +} + +.editor-post-preview__controls { + display: flex; + align-items: center; + margin: $grid-size-large 0; + + * + * { + margin-left: $grid-size; + } +} + +.editor-post-preview__new-tab { + margin-left: $grid-size-large; +} + +.editor-post-preview__frame-container { + overflow-x: auto; +} + +.editor-post-preview__frame { + display: block; + margin: 0 auto; + max-height: calc(100vh - 280px); +} diff --git a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap index 587f30bf8a91bb..0f165fcacfb7ae 100644 --- a/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap +++ b/packages/editor/src/components/post-preview-button/test/__snapshots__/index.js.snap @@ -1,37 +1,114 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PostPreviewButton render() should render currentPostLink otherwise 1`] = ` - - Preview - + - (opens in a new tab) - - -`; - -exports[`PostPreviewButton render() should render previewLink if provided 1`] = ` - - Preview - + (opens in a new tab) + + + - (opens in a new tab) - - +
+ + + + + } + label="Preview desktop screen" + onClick={[Function]} + /> + + + + + } + label="Preview tablet screen" + onClick={[Function]} + /> + + + + + } + label="Preview phone screen" + onClick={[Function]} + /> + + Open preview in new tab + +
+
+