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

State: Trigger autosave as standard save for draft by current user #7130

Merged
merged 11 commits into from
Jun 20, 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 @@ -17,43 +17,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.href === 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 @@ -64,10 +70,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 @@ -93,16 +95,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 @@ -120,7 +126,8 @@ export default compose( [
withSelect( ( select ) => {
const {
getCurrentPostId,
getEditedPostPreviewLink,
getCurrentPostAttribute,
getAutosaveAttribute,
getEditedPostAttribute,
isEditedPostDirty,
isEditedPostNew,
Expand All @@ -132,12 +139,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
68 changes: 54 additions & 14 deletions editor/components/post-preview-button/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,55 @@ import { shallow } from 'enzyme';
import { PostPreviewButton } from '../';

describe( 'PostPreviewButton', () => {
describe( 'constructor()', () => {
it( 'should initialize with non-awaiting-save', () => {
const instance = new PostPreviewButton( {} );
describe( 'setPreviewWindowLink()', () => {
it( 'should do nothing if there is no preview window', () => {
const url = 'https://wordpress.org';
const setter = jest.fn();
const wrapper = shallow( <PostPreviewButton /> );

expect( instance.state.isAwaitingSave ).toBe( false );
wrapper.instance().setPreviewWindowLink( url );

expect( setter ).not.toHaveBeenCalled();
} );

it( 'should do nothing if the preview window is already at url location', () => {
const url = 'https://wordpress.org';
const setter = jest.fn();
const wrapper = shallow( <PostPreviewButton /> );
wrapper.instance().previewWindow = {
get location() {
return {
href: url,
};
},
set location( value ) {
setter( value );
},
};

wrapper.instance().setPreviewWindowLink( url );

expect( setter ).not.toHaveBeenCalled();
} );

it( 'set preview window location to url', () => {
const url = 'https://wordpress.org';
const setter = jest.fn();
const wrapper = shallow( <PostPreviewButton /> );
wrapper.instance().previewWindow = {
get location() {
return {
href: 'about:blank',
};
},
set location( value ) {
setter( value );
},
};

wrapper.instance().setPreviewWindowLink( url );

expect( setter ).toHaveBeenCalledWith( url );
} );
} );

Expand All @@ -28,23 +72,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.instance().previewWindow = { location: {} };

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 +113,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 +158,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 +170,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
17 changes: 16 additions & 1 deletion editor/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ export function resetAutosave( post ) {
};
}

/**
* Returns an action object used in signalling that a patch of updates for the
* latest version of the post have been received.
*
* @param {Object} edits Updated post fields.
*
* @return {Object} Action object.
*/
export function updatePost( edits ) {
return {
type: 'UPDATE_POST',
edits,
};
}

/**
* Returns an action object used to setup the editor state when first opening an editor.
*
Expand Down Expand Up @@ -376,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
Loading