Skip to content

Commit

Permalink
Post Preview: Redirect to preview by autosave property availability
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed Jun 6, 2018
1 parent 578c345 commit 2134269
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 81 deletions.
50 changes: 28 additions & 22 deletions editor/components/post-preview-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,32 @@ 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;

// 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 && ! prevProps.previewLink ) {
this.setPreviewWindowLink( previewLink );
}
}

if ( hasFinishedSaving && this.previewWindow ) {
this.previewWindow.location = link;
this.setState( { isAwaitingSave: false } );
/**
* 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() {
Expand All @@ -50,9 +58,6 @@ export class PostPreviewButton extends Component {

// 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 Down Expand Up @@ -95,13 +100,13 @@ export class PostPreviewButton extends Component {
}

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 +121,8 @@ export default compose( [
withSelect( ( select ) => {
const {
getCurrentPostId,
getEditedPostPreviewLink,
getCurrentPostAttribute,
getAutosaveAttribute,
getEditedPostAttribute,
isEditedPostDirty,
isEditedPostNew,
Expand All @@ -128,12 +134,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
41 changes: 3 additions & 38 deletions editor/store/effects.js
Original file line number Diff line number Diff line change
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:
//
// - 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
7 changes: 6 additions & 1 deletion editor/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,12 @@ 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,
};
}

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
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',
},
} );

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

0 comments on commit 2134269

Please sign in to comment.