Skip to content
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

Preview: Enable published posts preview #7189

Merged
merged 13 commits into from
Jun 11, 2018
Merged
59 changes: 33 additions & 26 deletions editor/components/post-preview-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,43 +16,49 @@ export class PostPreviewButton extends Component {
super( ...arguments );

this.saveForPreview = this.saveForPreview.bind( this );

this.state = {
isAwaitingSave: false,
};
}

componentWillReceiveProps( nextProps ) {
const { modified, link } = nextProps;
const { isAwaitingSave } = this.state;
const hasFinishedSaving = (
isAwaitingSave &&
modified !== this.props.modified
);
componentDidUpdate( prevProps ) {
const { previewLink } = this.props;

if ( hasFinishedSaving && this.previewWindow ) {
this.previewWindow.location = link;
this.setState( { isAwaitingSave: false } );
// This relies on the window being responsible to unset itself when
// navigation occurs or a new preview window is opened, to avoid
// unintentional forceful redirects.
if ( previewLink && previewLink !== prevProps.previewLink ) {
this.setPreviewWindowLink( previewLink );
}
}

/**
* Sets the preview window's location to the given URL, if a preview window
* exists and is not already at that location.
*
* @param {string} url URL to assign as preview window location.
*/
setPreviewWindowLink( url ) {
const { previewWindow } = this;
if ( ! previewWindow || previewWindow.location === url ) {
return;
}

previewWindow.location = url;
}

getWindowTarget() {
const { postId } = this.props;
return `wp-preview-${ postId }`;
}

saveForPreview( event ) {
const { isDirty, isNew } = this.props;

// Let default link behavior occur if no changes to saved post
if ( ! isDirty && ! isNew ) {
return;
}

// Save post prior to opening window
this.props.autosave();
this.setState( {
isAwaitingSave: true,
} );

// Open a popup, BUT: Set it to a blank page until save completes. This
// is necessary because popups can only be opened in response to user
Expand All @@ -63,10 +69,6 @@ export class PostPreviewButton extends Component {
this.getWindowTarget()
);

// When popup is closed, delete reference to avoid later assignment of
// location in a post update.
this.previewWindow.onbeforeunload = () => delete this.previewWindow;

const markup = `
<div>
<p>Please wait&hellip;</p>
Expand All @@ -92,16 +94,20 @@ export class PostPreviewButton extends Component {

this.previewWindow.document.write( markup );
this.previewWindow.document.close();

// When popup is closed or redirected by setPreviewWindowLink, delete
// reference to avoid later assignment of location in a post update.
this.previewWindow.onbeforeunload = () => delete this.previewWindow;
}

render() {
const { link, isSaveable } = this.props;
const { currentPostLink, isSaveable } = this.props;

return (
<Button
className="editor-post-preview"
isLarge
href={ link }
href={ currentPostLink }
onClick={ this.saveForPreview }
target={ this.getWindowTarget() }
disabled={ ! isSaveable }
Expand All @@ -116,7 +122,8 @@ export default compose( [
withSelect( ( select ) => {
const {
getCurrentPostId,
getEditedPostPreviewLink,
getCurrentPostAttribute,
getAutosaveAttribute,
getEditedPostAttribute,
isEditedPostDirty,
isEditedPostNew,
Expand All @@ -128,12 +135,12 @@ export default compose( [
const postType = getPostType( getEditedPostAttribute( 'type' ) );
return {
postId: getCurrentPostId(),
link: getEditedPostPreviewLink(),
currentPostLink: getCurrentPostAttribute( 'link' ),
previewLink: getAutosaveAttribute( 'preview_link' ),
isDirty: isEditedPostDirty(),
isNew: isEditedPostNew(),
isSaveable: isEditedPostSaveable(),
isViewable: get( postType, [ 'viewable' ], false ),
modified: getEditedPostAttribute( 'modified' ),
};
} ),
withDispatch( ( dispatch ) => ( {
Expand Down
22 changes: 5 additions & 17 deletions editor/components/post-preview-button/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,6 @@ import { shallow } from 'enzyme';
import { PostPreviewButton } from '../';

describe( 'PostPreviewButton', () => {
describe( 'constructor()', () => {
it( 'should initialize with non-awaiting-save', () => {
const instance = new PostPreviewButton( {} );

expect( instance.state.isAwaitingSave ).toBe( false );
} );
} );

describe( 'getWindowTarget()', () => {
it( 'returns a string unique to the post id', () => {
const instance = new PostPreviewButton( {
Expand All @@ -28,23 +20,21 @@ describe( 'PostPreviewButton', () => {
} );

describe( 'componentDidUpdate()', () => {
it( 'should change popup location if save finishes', () => {
it( 'should change popup location if preview link is available', () => {
const wrapper = shallow(
<PostPreviewButton
postId={ 1 }
link="https://wordpress.org/?p=1"
currentPostLink="https://wordpress.org/?p=1"
isSaveable
modified="2017-08-03T15:05:50" />
);
wrapper.instance().previewWindow = {};
wrapper.setState( { isAwaitingSave: true } );

wrapper.setProps( { modified: '2017-08-03T15:05:52' } );
wrapper.setProps( { previewLink: 'https://wordpress.org/?p=1' } );

expect(
wrapper.instance().previewWindow.location
).toBe( 'https://wordpress.org/?p=1' );
expect( wrapper.state( 'isAwaitingSave' ) ).toBe( false );
} );
} );

Expand All @@ -71,13 +61,11 @@ describe( 'PostPreviewButton', () => {
if ( isExpectingSave ) {
expect( autosave ).toHaveBeenCalled();
expect( preventDefault ).toHaveBeenCalled();
expect( wrapper.state( 'isAwaitingSave' ) ).toBe( true );
expect( window.open ).toHaveBeenCalled();
expect( wrapper.instance().previewWindow.document.write ).toHaveBeenCalled();
} else {
expect( autosave ).not.toHaveBeenCalled();
expect( preventDefault ).not.toHaveBeenCalled();
expect( wrapper.state( 'isAwaitingSave' ) ).not.toBe( true );
expect( window.open ).not.toHaveBeenCalled();
}

Expand Down Expand Up @@ -118,7 +106,7 @@ describe( 'PostPreviewButton', () => {
<PostPreviewButton
postId={ 1 }
isSaveable
link="https://wordpress.org/?p=1" />
currentPostLink="https://wordpress.org/?p=1" />
);

expect( wrapper.prop( 'href' ) ).toBe( 'https://wordpress.org/?p=1' );
Expand All @@ -130,7 +118,7 @@ describe( 'PostPreviewButton', () => {
const wrapper = shallow(
<PostPreviewButton
postId={ 1 }
link="https://wordpress.org/?p=1" />
currentPostLink="https://wordpress.org/?p=1" />
);

expect( wrapper.prop( 'disabled' ) ).toBe( true );
Expand Down
2 changes: 1 addition & 1 deletion editor/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ export function editPost( edits ) {
*
* @return {Object} Action object.
*/
export function savePost( options ) {
export function savePost( options = {} ) {
return {
type: 'REQUEST_POST_UPDATE',
options,
Expand Down
43 changes: 4 additions & 39 deletions editor/store/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default {
const { dispatch, getState } = store;
const state = getState();
const post = getCurrentPost( state );
const isAutosave = get( action.options, [ 'autosave' ], false );
const isAutosave = !! action.options.autosave;

// Prevent save if not saveable.
const isSaveable = isAutosave ? isEditedPostAutosaveable : isEditedPostSaveable;
Expand Down Expand Up @@ -157,49 +157,14 @@ export default {

request.then(
( newPost ) => {
const reset = isAutosave ? resetAutosave : resetPost;
dispatch( reset( newPost ) );

// An autosave may be processed by the server as a regular save
// when its update is requested by the author and the post was
// draft or auto-draft.
const isRevision = newPost.id !== post.id;

// Thus, the following behaviors occur:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these removed changes were introduced as part of the base branch to which this merges (#7130) so there is effectively no difference between this callback and what's in master at the moment.

//
// - If it was a revision, it is treated as latest autosave
// and updates optimistically applied are reverted.
// - If it was an autosave but not revision under the above
// noted conditions, cherry-pick updated properties since
// the received revision entity shares some but not all
// properties of a post.
// - Otherwise, it was a full save and the received entity
// can be considered the new reset post.
let updateAction;
if ( isRevision ) {
updateAction = resetAutosave( newPost );
} else if ( isAutosave ) {
const revisionEdits = pick( newPost, [
// Autosave content fields.
'title',
'content',
'excerpt',

// UI considers save to have occurred if modified date
// of post changes (e.g. PostPreviewButton).
//
// TODO: Consider formalized pattern for identifying a
// save as having completed.
'date',
'date_gmt',
'modified',
'modified_gmt',
] );

updateAction = updatePost( revisionEdits );
} else {
updateAction = resetPost( newPost );
}

dispatch( updateAction );

dispatch( {
type: 'REQUEST_POST_UPDATE_SUCCESS',
previousPost: post,
Expand Down
14 changes: 13 additions & 1 deletion editor/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,19 @@ export function autosave( state = null, action ) {
'content',
].map( ( field ) => getPostRawValue( post[ field ] ) );

return { title, excerpt, content };
return {
title,
excerpt,
content,
preview_link: post.preview_link,
};

case 'REQUEST_POST_UPDATE':
// Invalidate known preview link when autosave starts.
if ( state && action.options.autosave ) {
return omit( state, 'preview_link' );
}
break;
}

return state;
Expand Down
43 changes: 40 additions & 3 deletions editor/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ export function getPostEdits( state ) {
return get( state, [ 'editor', 'present', 'edits' ], {} );
}

/**
* Returns an attribute value of the saved post.
*
* @param {Object} state Global application state.
* @param {string} attributeName Post attribute name.
*
* @return {*} Post attribute value.
*/
export function getCurrentPostAttribute( state, attributeName ) {
const post = getCurrentPost( state );
if ( post.hasOwnProperty( attributeName ) ) {
return post[ attributeName ];
}
}

/**
* Returns a single attribute of the post being edited, preferring the unsaved
* edit if one exists, but falling back to the attribute for the last known
Expand All @@ -201,9 +216,31 @@ export function getEditedPostAttribute( state, attributeName ) {
return getEditedPostContent( state );
}

return edits[ attributeName ] === undefined ?
state.currentPost[ attributeName ] :
edits[ attributeName ];
if ( ! edits.hasOwnProperty( attributeName ) ) {
return getCurrentPostAttribute( state, attributeName );
}

return edits[ attributeName ];
}

/**
* Returns an attribute value of the current autosave revision for a post, or
* null if there is no autosave for the post.
*
* @param {Object} state Global application state.
* @param {string} attributeName Autosave attribute name.
*
* @return {*} Autosave attribute value.
*/
export function getAutosaveAttribute( state, attributeName ) {
if ( ! hasAutosave( state ) ) {
return null;
}

const autosave = getAutosave( state );
if ( autosave.hasOwnProperty( attributeName ) ) {
return autosave[ attributeName ];
}
}

/**
Expand Down
8 changes: 8 additions & 0 deletions editor/store/test/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,14 @@ describe( 'actions', () => {
it( 'should return REQUEST_POST_UPDATE action', () => {
expect( savePost() ).toEqual( {
type: 'REQUEST_POST_UPDATE',
options: {},
} );
} );

it( 'should pass through options argument', () => {
expect( savePost( { autosave: true } ) ).toEqual( {
type: 'REQUEST_POST_UPDATE',
options: { autosave: true },
} );
} );
} );
Expand Down
2 changes: 2 additions & 0 deletions editor/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2298,13 +2298,15 @@ describe( 'state', () => {
raw: 'The Excerpt',
},
status: 'draft',
preview_link: 'https://wordpress.org/?p=1&preview=true',
},
} );

expect( state ).toEqual( {
title: 'The Title',
content: 'The Content',
excerpt: 'The Excerpt',
preview_link: 'https://wordpress.org/?p=1&preview=true',
} );
} );
} );
Expand Down
Loading