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

Improved pre/post publish flow. #5767

Merged
merged 1 commit into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions editor/components/post-publish-button/label.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export function PublishButtonLabel( {
return __( 'Publishing…' );
} else if ( isPublished && isSaving ) {
return __( 'Updating…' );
} else if ( isBeingScheduled && isSaving ) {
Copy link
Member

Choose a reason for hiding this comment

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

There are very simple corresponding tests which help to avoid regressions. Can we cover this case, too?

return __( 'Scheduling…' );
}

if ( isContributor ) {
Expand Down
5 changes: 5 additions & 0 deletions editor/components/post-publish-button/test/label.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ describe( 'PublishButtonLabel', () => {
expect( label ).toBe( 'Updating…' );
} );

it( 'should show scheduling if scheduled and saving in progress', () => {
const label = PublishButtonLabel( { user, isBeingScheduled: true, isSaving: true } );
expect( label ).toBe( 'Scheduling…' );
} );

it( 'should show publish if not published and saving in progress', () => {
const label = PublishButtonLabel( { user, isPublished: false, isSaving: true } );
expect( label ).toBe( 'Publish' );
Expand Down
45 changes: 27 additions & 18 deletions editor/components/post-publish-panel/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,29 @@ import PostPublishPanelPostpublish from './postpublish';
import {
getCurrentPostType,
isCurrentPostPublished,
isCurrentPostScheduled,
isSavingPost,
isEditedPostDirty,
} from '../../store/selectors';

class PostPublishPanel extends Component {
constructor() {
super( ...arguments );
this.onPublish = this.onPublish.bind( this );
this.onSubmit = this.onSubmit.bind( this );
this.state = {
loading: false,
published: false,
submitted: false,
};
}

componentWillReceiveProps( newProps ) {
if (
newProps.isPublished &&
! this.state.published &&
! newProps.isSaving
! this.state.submitted &&
! newProps.isSaving &&
( newProps.isPublished || newProps.isScheduled )
) {
this.setState( {
published: true,
submitted: true,
loading: false,
} );
}
Expand All @@ -56,25 +57,32 @@ class PostPublishPanel extends Component {
}
}

onPublish() {
onSubmit() {
const { user, onClose } = this.props;
const userCanPublishPosts = get( user.data, [ 'post_type_capabilities', 'publish_posts' ], false );
const isContributor = user.data && ! userCanPublishPosts;
if ( isContributor ) {
Copy link
Member

Choose a reason for hiding this comment

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

Nicely moved to one place. I hope we move that logic to selector one day. Not possible at the moment without a bigger refactor :)

onClose();
return;
}
this.setState( { loading: true } );
}

render() {
const { onClose, user, forceIsDirty, forceIsSaving } = this.props;
const { loading, published } = this.state;
const canPublish = get( user.data, [ 'post_type_capabilities', 'publish_posts' ], false );

const { isScheduled, onClose, forceIsDirty, forceIsSaving } = this.props;
const { loading, submitted } = this.state;
return (
<div className="editor-post-publish-panel">
<div className="editor-post-publish-panel__header">
{ ! published && (
{ ! submitted && (
<div className="editor-post-publish-panel__header-publish-button">
<PostPublishButton onSubmit={ this.onPublish } forceIsDirty={ forceIsDirty } forceIsSaving={ forceIsSaving } />
<PostPublishButton onSubmit={ this.onSubmit } forceIsDirty={ forceIsDirty } forceIsSaving={ forceIsSaving } />
</div>
) }
{ published && (
<div className="editor-post-publish-panel__header-published">{ __( 'Published' ) }</div>
{ submitted && (
<div className="editor-post-publish-panel__header-published">
{ isScheduled ? __( 'Scheduled' ) : __( 'Published' ) }
</div>
) }
<IconButton
onClick={ onClose }
Expand All @@ -83,9 +91,9 @@ class PostPublishPanel extends Component {
/>
</div>
<div className="editor-post-publish-panel__content">
{ canPublish && ! loading && ! published && <PostPublishPanelPrepublish /> }
{ loading && ! published && <Spinner /> }
{ published && <PostPublishPanelPostpublish /> }
{ ! loading && ! submitted && <PostPublishPanelPrepublish /> }
{ loading && ! submitted && <Spinner /> }
{ submitted && <PostPublishPanelPostpublish /> }
</div>
</div>
);
Expand All @@ -97,6 +105,7 @@ const applyConnect = connect(
return {
postType: getCurrentPostType( state ),
isPublished: isCurrentPostPublished( state ),
isScheduled: isCurrentPostScheduled( state ),
isSaving: isSavingPost( state ),
isDirty: isEditedPostDirty( state ),
};
Expand Down
27 changes: 19 additions & 8 deletions editor/components/post-publish-panel/postpublish.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { connect } from 'react-redux';
*/
import { PanelBody, Button, ClipboardButton, withAPIData } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Component, compose } from '@wordpress/element';
import { Component, compose, Fragment } from '@wordpress/element';

/**
* Internal Dependencies
*/
import { getCurrentPost, getCurrentPostType } from '../../store/selectors';
import PostScheduleLabel from '../post-schedule/label';
import { getCurrentPost, getCurrentPostType, isCurrentPostScheduled } from '../../store/selectors';

class PostPublishPanelPostpublish extends Component {
constructor() {
Expand Down Expand Up @@ -48,26 +49,35 @@ class PostPublishPanelPostpublish extends Component {
}

render() {
const { post, postType } = this.props;
const { isScheduled, post, postType } = this.props;
const viewPostLabel = get( postType, [ 'data', 'labels', 'view_item' ] );

const postPublishNonLinkHeader = isScheduled ?
<Fragment>{ __( 'is now scheduled. It will go live on' ) } <PostScheduleLabel />.</Fragment> :
__( 'is now live.' );
const postPublishBodyText = isScheduled ?
__( 'The post address will be:' ) :
__( 'What\'s next?' );

return (
<div className="post-publish-panel__postpublish">
<PanelBody className="post-publish-panel__postpublish-header">
<a href={ post.link }>{ post.title || __( '(no title)' ) }</a>{ __( ' is now live.' ) }
<a href={ post.link }>{ post.title || __( '(no title)' ) }</a> { postPublishNonLinkHeader }
Copy link
Member

@aduth aduth Sep 12, 2018

Choose a reason for hiding this comment

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

We should not concatenate translated strings, because the order of the concatenated parts can change in localization:

Use format strings instead of string concatenation—sprintf(__('Replace %1$s with %2$s'), $a, $b); is always better than __('Replace ').$a.__(' with ').$b; .

https://codex.wordpress.org/I18n_for_WordPress_Developers#Best_Practices

Copy link
Member

Choose a reason for hiding this comment

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

To preempt: It is unsolved this need to include React elements in translated strings. We can look to some other efforts as prior art:

https://github.com/Automattic/interpolate-components

But if we don't want to solve the larger problem, we're probably better off not localizing at all here.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should definitely fix this — cc @jorgefilipecosta

Copy link
Member

Choose a reason for hiding this comment

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

I might take it upon myself to look at what would be needed for component interpolation.

Copy link
Member

Choose a reason for hiding this comment

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

Description of the issue / task at #9846

</PanelBody>
<PanelBody>
<div><strong>{ __( 'What\'s next?' ) }</strong></div>
<div><strong>{ postPublishBodyText }</strong></div>
<input
className="post-publish-panel__postpublish-link-input"
readOnly
value={ post.link }
onFocus={ this.onSelectInput }
/>
<div className="post-publish-panel__postpublish-buttons">
<Button className="button" href={ post.link }>
{ viewPostLabel }
</Button>
{ ! isScheduled && (
<Button className="button" href={ post.link }>
{ viewPostLabel }
</Button>
) }

<ClipboardButton className="button" text={ post.link } onCopy={ this.onCopy }>
{ this.state.showCopyConfirmation ? __( 'Copied!' ) : __( 'Copy Link' ) }
Expand All @@ -84,6 +94,7 @@ const applyConnect = connect(
return {
post: getCurrentPost( state ),
postTypeSlug: getCurrentPostType( state ),
isScheduled: isCurrentPostScheduled( state ),
};
}
);
Expand Down
10 changes: 8 additions & 2 deletions editor/components/post-publish-panel/toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import {
isSavingPost,
isEditedPostSaveable,
isEditedPostPublishable,
isCurrentPostPending,
isCurrentPostPublished,
isEditedPostBeingScheduled,
isCurrentPostScheduled,
getCurrentPostType,
} from '../../store/selectors';

Expand All @@ -31,6 +33,8 @@ function PostPublishPanelToggle( {
isSaveable,
isPublished,
isBeingScheduled,
isPending,
isScheduled,
onToggle,
isOpen,
forceIsDirty,
Expand All @@ -42,7 +46,7 @@ function PostPublishPanelToggle( {

const userCanPublishPosts = get( user.data, [ 'post_type_capabilities', 'publish_posts' ], false );
const isContributor = user.data && ! userCanPublishPosts;
const showToggle = ! isContributor && ! isPublished && ! isBeingScheduled;
const showToggle = ! isPublished && ! ( isScheduled && isBeingScheduled ) && ! ( isPending && isContributor );

if ( ! showToggle ) {
return <PostPublishButton forceIsDirty={ forceIsDirty } forceIsSaving={ forceIsSaving } />;
Expand All @@ -57,7 +61,7 @@ function PostPublishPanelToggle( {
disabled={ ! isButtonEnabled }
isBusy={ isSaving && isPublished }
>
{ __( 'Publish...' ) }
{ isBeingScheduled ? __( 'Schedule...' ) : __( 'Publish...' ) }
</Button>
);
}
Expand All @@ -67,7 +71,9 @@ const applyConnect = connect(
isSaving: isSavingPost( state ),
isSaveable: isEditedPostSaveable( state ),
isPublishable: isEditedPostPublishable( state ),
isPending: isCurrentPostPending( state ),
isPublished: isCurrentPostPublished( state ),
isScheduled: isCurrentPostScheduled( state ),
isBeingScheduled: isEditedPostBeingScheduled( state ),
postType: getCurrentPostType( state ),
} ),
Expand Down
22 changes: 22 additions & 0 deletions editor/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,17 @@ export function getEditedPostVisibility( state ) {
return 'public';
}

/**
* Returns true if post is pending review.
*
* @param {Object} state Global application state.
*
* @return {boolean} Whether current post is pending review.
*/
export function isCurrentPostPending( state ) {
return getCurrentPost( state ).status === 'pending';
}

/**
* Return true if the current post has already been published.
*
Expand All @@ -230,6 +241,17 @@ export function isCurrentPostPublished( state ) {
( post.status === 'future' && moment( post.date ).isBefore( moment() ) );
}

/**
* Returns true if post is already scheduled.
*
* @param {Object} state Global application state.
*
* @return {boolean} Whether current post is scheduled to be posted.
*/
export function isCurrentPostScheduled( state ) {
return getCurrentPost( state ).status === 'future' && ! isCurrentPostPublished( state );
}

/**
* Return true if the post being edited can be published.
*
Expand Down
84 changes: 84 additions & 0 deletions editor/store/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ const {
getDocumentTitle,
getEditedPostExcerpt,
getEditedPostVisibility,
isCurrentPostPending,
isCurrentPostPublished,
isCurrentPostScheduled,
isEditedPostPublishable,
isEditedPostSaveable,
isEditedPostEmpty,
Expand Down Expand Up @@ -587,6 +589,36 @@ describe( 'selectors', () => {
} );
} );

describe( 'isCurrentPostPending', () => {
it( 'should return true for posts in pending state', () => {
const state = {
currentPost: {
status: 'pending',
},
};

expect( isCurrentPostPending( state ) ).toBe( true );
} );

it( 'should return false for draft posts', () => {
const state = {
currentPost: {
status: 'draft',
},
};

expect( isCurrentPostPending( state ) ).toBe( false );
} );

it( 'should return false if status is unknown', () => {
const state = {
currentPost: {},
};

expect( isCurrentPostPending( state ) ).toBe( false );
} );
} );

describe( 'isCurrentPostPublished', () => {
it( 'should return true for public posts', () => {
const state = {
Expand Down Expand Up @@ -630,6 +662,58 @@ describe( 'selectors', () => {
} );
} );

describe( 'isCurrentPostScheduled', () => {
it( 'should return true for posts with status future', () => {
const state = {
currentPost: {
status: 'future',
},
};

expect( isCurrentPostScheduled( state ) ).toBe( true );
} );

it( 'should return true for future scheduled posts', () => {
const state = {
currentPost: {
status: 'future',
date: '2100-05-30T17:21:39',
},
};

expect( isCurrentPostScheduled( state ) ).toBe( true );
} );

it( 'should return false for old scheduled posts that were already published', () => {
const state = {
currentPost: {
status: 'future',
date: '2016-05-30T17:21:39',
},
};

expect( isCurrentPostScheduled( state ) ).toBe( false );
} );

it( 'should return false for auto draft posts', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Similar as above. One of the tests might be obsolete.

Copy link
Member Author

Choose a reason for hiding this comment

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

I followed your feedback and now we test for auto-draft in scheduled posts and for the normal draft's in pending posts so no tests testing the same thing exist in each test case but we still test for but status in the app.

const state = {
currentPost: {
status: 'auto-draft',
},
};

expect( isCurrentPostScheduled( state ) ).toBe( false );
} );

it( 'should return false if status is unknown', () => {
const state = {
currentPost: {},
};

expect( isCurrentPostScheduled( state ) ).toBe( false );
} );
} );

describe( 'isEditedPostPublishable', () => {
it( 'should return true for pending posts', () => {
const state = {
Expand Down