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

Chrome: Enhance the publish button flow (and the saved state component) #945

Merged
merged 14 commits into from
Jun 7, 2017

Conversation

youknowriad
Copy link
Contributor

@youknowriad youknowriad commented May 30, 2017

closes #945

Several things in this PR:

  • We need a way to match the server's timezone in the frontend in order to compare dates properly. I'm using moment-timezone for this and setting the server timezone globally in moment
  • When publishing a post (publish button), the status applied to the button 'publish' if no date or date in the past, 'future' if date in the future but this status can be overridden when the edits (pending, private, ...)
  • When clicking the "save" link, we always save a draft unless the post is already saved, we'll keep the same status in this case.

I'd appreciate help in testing this PR because there's so many use-cases. It's easy to miss one.

Auto-saving is not implemented yet.

@youknowriad youknowriad added the General Interface Parts of the UI which don't fall neatly under other labels. label May 30, 2017
@youknowriad youknowriad self-assigned this May 30, 2017
@nylen
Copy link
Member

nylen commented May 30, 2017

@youknowriad: have you seen #920, which allows us to know whether the last save request was for a new draft (Saved!) or an existing post (Updated!)? This is not quite working correctly in master. I see that the related requestIsNewPost property was instead removed in this PR, and I am wondering how to reconcile these two PRs.

@youknowriad
Copy link
Contributor Author

I see that the related requestIsNewPost property was instead removed in this PR, and I am wondering

I didn't remove it because it was broken but because it's not needed anymore. The button doesn't need to know if the last request was for an unsaved post or not.

@nylen
Copy link
Member

nylen commented May 30, 2017

If we don't need it, let's also remove that part of the state tree and the related action properties. I wasn't very happy with how that looked anyway.

@jasmussen
Copy link
Contributor

I totally love this. I feel like the experience completely vindicates the idea of separating the two. Thanks so much for working on this. 👍

@jasmussen
Copy link
Contributor

Question: does auto-save ever kick in? If not, then how does that work, if at all? And if not, we should probably ticket this separately.

In the context of this design, I imagine the behavior being like this:

  1. Start new post. Left side area is empty
  2. Type a little. Left side shows "Save".
    3a. Wait a few seconds without typing, left side shows "Saving..." then "✔ Saved"
    3b. Or, don't wait, click Save manually, and left side shows "Saving..." then "✔ Saved"

Autosave can totally be addressed separately. Additional future enhancements, could be an autosave error message when you're trying to save but the internet is down. This error message should show up on the left, like "❗ Couldn't save [Try again]".

@youknowriad
Copy link
Contributor Author

youknowriad commented May 31, 2017

Worth noting there's a difficult to solve edge case in this PR: We can't have

  • a "pending" and "draft" post at the same time because this information is stored in the same "status" field. So when hitting "Save" on a post where we activated the "pending" toggle. This will be reset since the post is saved as "draft".

@jasmussen This PR doesn't address auto-saving yet. I was planning to do this separately if the workflow here is validated (probably needs a separate issue). I'm not sure how to solve the issue above when auto-saving (Leaving the pending toggle activated and saving the post at the same time). The solution will be to save all the user changes except the "status" but this leaves the post as "dirty" which means the "Saved !" message will never appear. I'm thinking of showing this message for like 5 seconds after the save is successful (whether it's auto-save or explicit) and don't take into consideration the "dirty" property. Thoughts on this?

Edit: We have the same issue with the "private" toggle.

@jasmussen
Copy link
Contributor

@jasmussen This PR doesn't address auto-saving yet. I was planning to do this separately if the workflow here is validated (probably needs a separate issue). I'm not sure how to solve the issue above when auto-saving (Leaving the pending toggle activated and saving the post at the same time). The solution will be to save all the user changes except the "status" but this leaves the post as "dirty" which means the "Saved !" message will never appear. I'm thinking of showing this message for like 5 seconds after the save is successful (whether it's auto-save or explicit) and don't take into consideration the "dirty" property. Thoughts on this?

Edit: We have the same issue with the "private" toggle.

@mtias @timmyc can you recall how we solved this in Calypso?

@youknowriad
Copy link
Contributor Author

@jasmussen I looked at Calypso, and here's how it's solved:

  • For private posts, toggling the "private" status publishes privately the post directly without another click on the "publish" button. This solves the issue, because the auto-save will just save the published post again without any status change.

  • For pending posts, toggling the "pending" toggle don't save the post immediately but auto-saving and saving the post manually save the post as "pending" instead of "draft".

Does this sound ok for you?

@jasmussen
Copy link
Contributor

That sounds pretty good to me, yes.

@youknowriad
Copy link
Contributor Author

@jasmussen It should be ok now. I'm using window.confirm temporary instead of a Modal (we don't have that yet). I think we should ticket the Modal and the auto-saving separately. This PR is big enough, isn't it?

@jasmussen
Copy link
Contributor

Nice, this is working and looking really well.

Visibility controls seem to have stopped working for me, though. Think it's this branch, or the webpack deduplication stuff?

@youknowriad
Copy link
Contributor Author

Visibility controls seem to have stopped working for me, though. Think it's this branch or the webpack deduplication stuff?

Good catch, it's fixed now by 1ab9d1e but I can't understand why the onChange handler is not enough. Really weird.

@aduth
Copy link
Member

aduth commented Jun 1, 2017

  1. Navigate to Gutenberg
  2. Click Save

Expected: "Publish" button labeled "Publish"
Actual: "Publish" button labeled "Schedule"


@keyframes move_background {
from {
background-position: 0 0;
Copy link
Member

Choose a reason for hiding this comment

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

Tabbing inconsistency.

if ( isSaving ) {
return (
<div className={ className }>
Saving
Copy link
Member

Choose a reason for hiding this comment

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

Should localize

if ( ! isNew && ! isDirty ) {
return (
<div className={ className }>
<Dashicon icon={ 'saved' } />
Copy link
Member

Choose a reason for hiding this comment

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

Need for curly braces? <Dashicon icon="saved" />

text = wp.i18n.__( 'Saved' );
if ( isSaving ) {
return (
<div className={ className }>
Copy link
Member

Choose a reason for hiding this comment

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

Do we want this to be a block node, or is inline span better?

( dispatch ) => ( {
onSave( post, edits, blocks ) {
dispatch( savePost( post.id, {
content: wp.blocks.serialize( blocks ),
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if block serialization should occur as part of a middleware before save proceeds into the network request handler, instead of the responsibility of the save button. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

maybe. To be honest, sometimes I think we should avoid passing any edits, or content, or blocks here and use getState instead inside the effect handler. because it feels like I'm adding extra props to components when the component doesn't really need those props.

Copy link
Member

Choose a reason for hiding this comment

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

because it feels like I'm adding extra props to components when the component doesn't really need those props.

Yeah, that's more-or-less how I feel too. It doesn't seem to follow that the save button ought to be concerned with how to create the payload for the request. I think either an effect handler or a separate standalone middleware could serve in its place. I mentioned middleware only because I'm curious to see how far we can take effects to not rely on the store argument, per some of my own goals outlined in the refx 2.0 changelog. I'd be fine with either approach, honestly, and the effect infrastructure being in place is a convenient lure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's probably better to leave this for a separate PR. The change doesn't concern this PR only.

dirty: isEditedPostDirty( state ),
isSaving: isSavingPost( state ),
isPublished: isEditedPostAlreadyPublished( state ),
isBeingScheduled: isEditedPostBeingScheduled( state, 'date' ),
Copy link
Member

Choose a reason for hiding this comment

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

There's no second argument accepted for isEditedPostBeingScheduled

right: 0;
bottom: 0;
content: '';
background-image: repeating-linear-gradient( -45deg, $blue-wordpress-700, $blue-wordpress-700 11px, $blue-wordpress 10px, $blue-wordpress 20px );
Copy link
Member

Choose a reason for hiding this comment

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

Tabbing inconsistency. We need stylesheet linting 😄

@@ -12,6 +19,10 @@ import './assets/stylesheets/main.scss';
import Layout from './layout';
import { createReduxStore } from './state';

// Configure moment globally
moment.locale( settings.l10n.locale );
Copy link
Member

Choose a reason for hiding this comment

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

Should this occur in date/index.js setupLocale ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The question is: if we do this in data/index.js, wp-data won't be library independent anymore. If you look at the code, you'll notice it's already done there, temporary but reverted to the original locale.

Copy link
Member

Choose a reason for hiding this comment

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

Is it independent this way though, if we're assigning defaults to the global instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's independent in wp-date but an app like Gutenberg can make it global explicitly. That's my thinking. But I don't mind moving this to wp-date but we'd have to acknowledge that wp-date is moment dependent (externally).

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we necessarily need to solve this here. Proposed for more discussion on the related Trac ticket: https://core.trac.wordpress.org/ticket/39222#comment:35

editor/state.js Outdated
@@ -34,6 +34,8 @@ export const editor = combineUndoableReducers( {
...state,
...action.edits,
};
case 'REQUEST_POST_UPDATE':
Copy link
Member

Choose a reason for hiding this comment

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

Do you think it be more semantic for edits to clear themselves by a standalone action CLEAR_POST_EDITS, which the effect handler(s) for REQUEST_POST_UPDATE would be responsible for returning/dispatching? Still unsure just how much we want to tie editor state to network activity, especially now that we have patterns for relatively simple effect chains.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand this concern. And this is part of defining what is our Data Approach, how do we handle async actions etc... We should document this and be consistent.

Copy link
Member

Choose a reason for hiding this comment

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

I'll comment on #902

right: 0;
bottom: 0;
content: '';
background-image: repeating-linear-gradient( -45deg, $blue-wordpress-700, $blue-wordpress-700 11px, $blue-wordpress 10px, $blue-wordpress 20px );
Copy link
Member

Choose a reason for hiding this comment

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

Still not quite tabbed enough 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🙃

return (
<span className={ className }>
<Dashicon icon="saved" />
{ wp.i18n.__( 'Saved' ) }
Copy link
Member

Choose a reason for hiding this comment

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

Now that we're importing __, should we be consistent with how we're calling it throughout the file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🙃

position: relative;

// These styles overrides the disabled state with the button primary styles
background: none !important;
Copy link
Member

Choose a reason for hiding this comment

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

Can we override by exceeding specificity of those default styles, instead of through !important ? Why are we breaking consistency with default disabled button styling ? Should we be applying these overrides to Button more generally if they're warranted ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately we can't. These are the styles of WP and they are marked as important :(

@youknowriad
Copy link
Contributor Author

youknowriad commented Jun 2, 2017

@aduth I was able to fix the timezone issue, by creating a custom WP timezone for these custom timezones. 5f0fa08

@youknowriad youknowriad force-pushed the update/publish-button branch 2 times, most recently from ffe519d to 691ec08 Compare June 2, 2017 13:22
aduth
aduth previously requested changes Jun 2, 2017
Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

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

Since we don't include isNew in REQUEST_POST_UPDATE_SUCCESS, the effect handler for navigating to apply ?post_id is no longer triggered.

@youknowriad youknowriad dismissed aduth’s stale review June 6, 2017 09:26

The behavior is now similar to Calypso where we hide the pending toggle on published posts. Makes things simpler.

@jasmussen
Copy link
Contributor

Excited about this branch! I'd like to do some responsive work. Should I do that in this branch, or wait until it's merged into master? I'm reluctant to do it in master currently, awaiting this one being merged in.

@jasmussen
Copy link
Contributor

Actually, scratch that last comment. This branch already solves the responsive issues that are in master!

@youknowriad
Copy link
Contributor Author

Yeah, let's try to merge this today. I think I've addressed all the concerns. I'll wait for another look

Copy link
Member

@aduth aduth left a comment

Choose a reason for hiding this comment

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

I left some comments but I think this is in a good state to merge if we want to iterate in subsequent pull requests.


dispatch( { type: 'CLEAR_POST_EDITS' } );
Copy link
Member

Choose a reason for hiding this comment

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

Question: Should we wait until after the save has completed to clear edits? What if the save fails?

Copy link
Member

Choose a reason for hiding this comment

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

In fact, I notice something odd about this behavior. When changing visibility for a published post and Update-ing the post, the visibility reverts back to its original value briefly. I suspect it's because of this dispatch here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hard problem to solve, we should probably diff the edits before and after save to avoid loosing the unsaved edits (if we move this call in the onSuccess)

} ) {
const buttonEnabled = ! isRequesting;

const buttonEnabled = ! isSaving &&
Copy link
Member

Choose a reason for hiding this comment

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

Thinking some of this more complex logic warrants testing, or moving to selector e.g. isPostPublishable.

* @param {Object} state Global application state
* @return {Boolearn} Whether the post has been bublished
*/
export function isEditedPostAlreadyPublished( state ) {
Copy link
Member

Choose a reason for hiding this comment

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

Question: Would this make more or less sense without "Already". I think it helps highlight that it tests whether the post was scheduled at a date before the current time, but maybe too much so to the extent that it might not be intuitive to use for a simple publish test.

showHint={ false }
/>
</div>
{ ! isPublished &&
Copy link
Member

Choose a reason for hiding this comment

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

What would it take to be able to show this for published posts as well? I'd suppose we need to revert back to the post's original status when toggling off, not a static value. Maybe an action that clears the current edited value for a property (we might want that for what we have anyways).

I'm not sure it makes much sense to revert from Published to Pending Review, but it is possible in the current editor.

Maybe if we at least have flow for reverting to draft it becomes a little more reasonable, since there's a workflow for changing from Published to Pending Review.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, enabling it is not difficult to achieve, but yeah, it doesn't make much sense. I'm more in favour of having a "revert to draft" button here. cc @jasmussen

@youknowriad
Copy link
Contributor Author

Thanks for the review @aduth I've addressed the selector notes. I'm going to merge this.
Here's a summary of the points to be addressed separately:

  • "Revert to draft" Button
  • Enhance the save effect to retrieve the edits and blocks from the store (as opposed to being passed as an argument)
  • Enhance the save effect to reset Edits on Save Success (and handle the edits made while saving)

Should we create separate issues for each of these points?

@youknowriad youknowriad merged commit 73bd4ab into master Jun 7, 2017
@youknowriad youknowriad deleted the update/publish-button branch June 7, 2017 16:47
@jasmussen
Copy link
Contributor

🎉

@aduth
Copy link
Member

aduth commented Jun 7, 2017

"Revert to draft" Button

@jasmussen Do we have this in any of the other mockups?

@youknowriad Yeah, we should create separate issues. I'm hoping to take a look at a "save" middleware (re: second point) soon.

@jasmussen
Copy link
Contributor

Do we have this in any of the other mockups?

No specific mockups, no, but when we implemented this design we did for core, on WordPress.com first, we also designed that button there. Here's how it looks:

screen shot 2017-06-07 at 19 10 58

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
General Interface Parts of the UI which don't fall neatly under other labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants