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

Fix a few issues with saving post titles, dirty handling, etc. #848

Merged
merged 11 commits into from
May 24, 2017
27 changes: 19 additions & 8 deletions editor/header/saved-state/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,28 @@ import Dashicon from 'components/dashicon';
* Internal dependencies
*/
import './style.scss';
import { isEditedPostDirty } from '../../selectors';
import { isEditedPostNew, isEditedPostDirty } from '../../selectors';

function SavedState( { isDirty } ) {
function SavedState( { isNew, isDirty } ) {
const classes = classNames( 'editor-saved-state', {
'is-new': isNew,
'is-dirty': isDirty,
} );
const icon = isDirty
? 'warning'
: 'saved';
const text = isDirty
? wp.i18n.__( 'Unsaved changes' )
: wp.i18n.__( 'Saved' );

let icon, text;
if ( isNew && isDirty ) {
icon = 'warning';
text = wp.i18n.__( 'New post with changes' );
} else if ( isNew ) {
icon = 'edit';
text = wp.i18n.__( 'New post' );
} else if ( isDirty ) {
icon = 'warning';
text = wp.i18n.__( 'Unsaved changes' );
} else {
icon = 'saved';
text = wp.i18n.__( 'Saved' );
}

return (
<div className={ classes }>
Expand All @@ -36,6 +46,7 @@ function SavedState( { isDirty } ) {

export default connect(
( state ) => ( {
isNew: isEditedPostNew( state ),
isDirty: isEditedPostDirty( state ),
} )
)( SavedState );
23 changes: 4 additions & 19 deletions editor/header/tools/publish-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ function PublishButton( {
isRequesting,
isError,
requestIsNewPost,
onUpdate,
onSaveDraft,
onSave,
} ) {
const buttonEnabled = ! isRequesting;
let buttonText, saveCallback;

let buttonText;
if ( isRequesting ) {
buttonText = requestIsNewPost
? wp.i18n.__( 'Saving…' )
Expand All @@ -56,12 +55,6 @@ function PublishButton( {
buttonText = wp.i18n.__( 'Save draft' );
}

if ( post && post.id ) {
saveCallback = onUpdate;
} else {
saveCallback = onSaveDraft;
}

const buttonDisabledHint = process.env.NODE_ENV === 'production'
? wp.i18n.__( 'The Save button is disabled during early alpha releases.' )
: null;
Expand All @@ -70,7 +63,7 @@ function PublishButton( {
<Button
isPrimary
isLarge
onClick={ () => saveCallback( post, edits, blocks ) }
onClick={ () => onSave( post, edits, blocks ) }
disabled={ ! buttonEnabled || process.env.NODE_ENV === 'production' }
title={ buttonDisabledHint }
>
Expand All @@ -91,19 +84,11 @@ export default connect(
requestIsNewPost: isSavingNewPost( state ),
} ),
( dispatch ) => ( {
onUpdate( post, edits, blocks ) {
onSave( post, edits, blocks ) {
savePost( dispatch, post.id, {
content: wp.blocks.serialize( blocks ),
...edits,
} );
},

onSaveDraft( post, edits, blocks ) {
savePost( dispatch, null /* is a new post */, {
content: wp.blocks.serialize( blocks ),
status: 'draft', // TODO change this after status controls
...edits,
} );
},
} )
)( PublishButton );
15 changes: 15 additions & 0 deletions editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { Provider as ReduxProvider } from 'react-redux';
import { Provider as SlotFillProvider } from 'react-slot-fill';
import { omit } from 'lodash';

/**
* Internal dependencies
Expand All @@ -25,6 +26,20 @@ export function createEditorInstance( id, post ) {
blocks: wp.blocks.parse( post.content.raw ),
} );

if ( ! post.id ) {
// Each property that is set in `post-content.js` (other than `content`
// because it is serialized when a save is requested) needs to be
// registered as an edit now. Otherwise the initial values of these
// properties will not be properly saved with the post.
store.dispatch( {
type: 'SETUP_NEW_POST',
edits: {
title: post.title.raw,
...omit( post, 'title', 'content' ),
},
} );
}

wp.element.render(
<ReduxProvider store={ store }>
<SlotFillProvider>
Expand Down
4 changes: 4 additions & 0 deletions editor/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export function hasEditorRedo( state ) {
return state.editor.history.future.length > 0;
}

export function isEditedPostNew( state ) {
return ! state.currentPost.id;
}

export function isEditedPostDirty( state ) {
return state.editor.dirty;
}
Expand Down
2 changes: 2 additions & 0 deletions editor/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const editor = combineUndoableReducers( {
edits( state = {}, action ) {
switch ( action.type ) {
case 'EDIT_POST':
case 'SETUP_NEW_POST':
return {
...state,
...action.edits,
Expand All @@ -48,6 +49,7 @@ export const editor = combineUndoableReducers( {
case 'MOVE_BLOCK_UP':
case 'REPLACE_BLOCKS':
case 'REMOVE_BLOCK':
case 'EDIT_POST':
return true;
}

Expand Down
21 changes: 21 additions & 0 deletions editor/test/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
isEditorSidebarOpened,
hasEditorUndo,
hasEditorRedo,
isEditedPostNew,
isEditedPostDirty,
getCurrentPost,
getPostEdits,
Expand Down Expand Up @@ -123,6 +124,26 @@ describe( 'selectors', () => {
} );
} );

describe( 'isEditedPostNew', () => {
it( 'should return true when the post is new', () => {
const state = {
currentPost: {},
};

expect( isEditedPostNew( state ) ).to.be.true();
} );

it( 'should return false when the post has an ID', () => {
const state = {
currentPost: {
id: 1,
},
};

expect( isEditedPostNew( state ) ).to.be.false();
} );
} );

describe( 'isEditedPostDirty', () => {
it( 'should return true when the post is dirty', () => {
const state = {
Expand Down
79 changes: 79 additions & 0 deletions editor/test/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,85 @@ describe( 'state', () => {
tags: [ 2 ],
} );
} );

it( 'should save initial post state', () => {
const state = editor( undefined, {
type: 'SETUP_NEW_POST',
edits: {
status: 'draft',
title: 'post title',
},
} );

expect( state.edits ).to.eql( {
status: 'draft',
title: 'post title',
} );
} );
} );

describe( 'dirty()', () => {
it( 'should be true when the post is edited', () => {
const state = editor( undefined, {
type: 'EDIT_POST',
edits: {},
} );

expect( state.dirty ).to.be.true();
} );

it( 'should change to false when the post is reset', () => {
const original = editor( undefined, {
type: 'EDIT_POST',
edits: {},
} );

const state = editor( original, {
type: 'RESET_BLOCKS',
post: {},
blocks: [],
} );

expect( state.dirty ).to.be.false();
} );

it( 'should not change from true when an unrelated action occurs', () => {
const original = editor( undefined, {
type: 'EDIT_POST',
edits: {},
} );

const state = editor( original, {
type: 'BRISKET_READY',
} );

expect( state.dirty ).to.be.true();
} );

it( 'should not change from false when an unrelated action occurs', () => {
const original = editor( undefined, {
type: 'RESET_BLOCKS',
post: {},
blocks: [],
} );

expect( original.dirty ).to.be.false();

const state = editor( original, {
type: 'BRISKET_READY',
} );

expect( state.dirty ).to.be.false();
} );

it( 'should be false when the post is initialized', () => {
const state = editor( undefined, {
type: 'SETUP_NEW_POST',
edits: {},
} );

expect( state.dirty ).to.be.false();
} );
} );
} );

Expand Down
4 changes: 4 additions & 0 deletions post-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ window._wpGutenbergPost = {
title: {
raw: 'Welcome to the Gutenberg Editor',
},
// TODO `status` and any other initial attributes other than content and
// title need to move somewhere else when this file goes away. See:
// https://github.com/WordPress/gutenberg/pull/848#issuecomment-302836177
status: 'draft',
content: {
raw: [
'<!-- wp:core/text -->',
Expand Down