From 98303bb14c81e327e50d3a5e6b5ee8ecbbe6a010 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Fri, 7 Jul 2017 16:38:29 -0700 Subject: [PATCH 01/10] Update document.title when post title changes --- editor/actions.js | 7 +++++++ editor/effects.js | 19 +++++++++++++++++++ editor/post-title/index.js | 3 ++- editor/selectors.js | 11 +++++++++++ lib/register.php | 2 +- 5 files changed, 40 insertions(+), 2 deletions(-) diff --git a/editor/actions.js b/editor/actions.js index b607097f2c2bf..5a00e46723989 100644 --- a/editor/actions.js +++ b/editor/actions.js @@ -82,6 +82,13 @@ export function hideInsertionPoint() { }; } +export function editPostTitle( postTitle ) { + return { + type: 'EDIT_POST_TITLE', + postTitle, + }; +} + export function editPost( edits ) { return { type: 'EDIT_POST', diff --git a/editor/effects.js b/editor/effects.js index f75379c6c6b25..d81577b269ecb 100644 --- a/editor/effects.js +++ b/editor/effects.js @@ -20,9 +20,28 @@ import { getCurrentPostType, getBlocks, getPostEdits, + isCleanNewPost, } from './selectors'; +let originalDocumentTitle; + +function populateDocumentTitle( title, isCleanNew = false ) { + if ( ! title.trim() ) { + title = isCleanNew ? __( 'New post' ) : __( '(Untitled)' ); + } + if ( ! originalDocumentTitle ) { + originalDocumentTitle = document.title; + } + document.title = title + ' | ' + originalDocumentTitle; +} + export default { + RESET_POST( action, store ) { + populateDocumentTitle( action.post.title ? action.post.title.raw : '', isCleanNewPost( store.getState() ) ); + }, + EDIT_POST_TITLE( action, store ) { + populateDocumentTitle( action.postTitle, isCleanNewPost( store.getState() ) ); + }, REQUEST_POST_UPDATE( action, store ) { const { dispatch, getState } = store; const state = getState(); diff --git a/editor/post-title/index.js b/editor/post-title/index.js index fa22b7434e9b5..d5f275c5ef7f2 100644 --- a/editor/post-title/index.js +++ b/editor/post-title/index.js @@ -18,7 +18,7 @@ import { ENTER } from 'utils/keycodes'; */ import './style.scss'; import { getEditedPostTitle } from '../selectors'; -import { editPost, clearSelectedBlock } from '../actions'; +import { editPost, editPostTitle, clearSelectedBlock } from '../actions'; import PostPermalink from '../post-permalink'; /** @@ -119,6 +119,7 @@ export default connect( return { onUpdate( title ) { dispatch( editPost( { title } ) ); + dispatch( editPostTitle( title ) ); }, clearSelectedBlock() { dispatch( clearSelectedBlock() ); diff --git a/editor/selectors.js b/editor/selectors.js index 3d2116d311c1d..347d36cda3471 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -88,6 +88,17 @@ export function isEditedPostDirty( state ) { return state.editor.dirty; } +/** + * Returns true if there are unsaved values for the current edit session and if + * the currently edited post is yet to be saved. + * + * @param {Object} state Global application state + * @return {Boolean} Whether new post and unsaved values exist + */ +export function isCleanNewPost( state ) { + return ! isEditedPostDirty( state ) && isEditedPostNew( state ); +} + /** * Returns the post currently being edited in its last known saved state, not * including unsaved edits. Returns an object containing relevant default post diff --git a/lib/register.php b/lib/register.php index 8824601aaf361..36fceb5b46e4d 100644 --- a/lib/register.php +++ b/lib/register.php @@ -44,7 +44,7 @@ function gutenberg_menu() { add_submenu_page( 'gutenberg', - __( 'New Post', 'gutenberg' ), + __( 'Gutenberg ', 'gutenberg' ), __( 'New Post', 'gutenberg' ), 'edit_posts', 'gutenberg', From b406fd211c11dd935bfc4697c6b4c582606352d1 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 8 Jul 2017 19:28:43 -0700 Subject: [PATCH 02/10] Eliminate EDIT_POST_TITLE action in favor of reusing EDIT_POST --- editor/actions.js | 7 ------- editor/effects.js | 6 ++++-- editor/post-title/index.js | 3 +-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/editor/actions.js b/editor/actions.js index 5a00e46723989..b607097f2c2bf 100644 --- a/editor/actions.js +++ b/editor/actions.js @@ -82,13 +82,6 @@ export function hideInsertionPoint() { }; } -export function editPostTitle( postTitle ) { - return { - type: 'EDIT_POST_TITLE', - postTitle, - }; -} - export function editPost( edits ) { return { type: 'EDIT_POST', diff --git a/editor/effects.js b/editor/effects.js index d81577b269ecb..05daacf7857f9 100644 --- a/editor/effects.js +++ b/editor/effects.js @@ -39,8 +39,10 @@ export default { RESET_POST( action, store ) { populateDocumentTitle( action.post.title ? action.post.title.raw : '', isCleanNewPost( store.getState() ) ); }, - EDIT_POST_TITLE( action, store ) { - populateDocumentTitle( action.postTitle, isCleanNewPost( store.getState() ) ); + EDIT_POST( action, store ) { + if ( undefined !== action.edits.title ) { + populateDocumentTitle( action.edits.title, isCleanNewPost( store.getState() ) ); + } }, REQUEST_POST_UPDATE( action, store ) { const { dispatch, getState } = store; diff --git a/editor/post-title/index.js b/editor/post-title/index.js index d5f275c5ef7f2..fa22b7434e9b5 100644 --- a/editor/post-title/index.js +++ b/editor/post-title/index.js @@ -18,7 +18,7 @@ import { ENTER } from 'utils/keycodes'; */ import './style.scss'; import { getEditedPostTitle } from '../selectors'; -import { editPost, editPostTitle, clearSelectedBlock } from '../actions'; +import { editPost, clearSelectedBlock } from '../actions'; import PostPermalink from '../post-permalink'; /** @@ -119,7 +119,6 @@ export default connect( return { onUpdate( title ) { dispatch( editPost( { title } ) ); - dispatch( editPostTitle( title ) ); }, clearSelectedBlock() { dispatch( clearSelectedBlock() ); From 0a186392c9d2c788c1a6f274ed2ce82bfde9ddb6 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sat, 8 Jul 2017 19:43:42 -0700 Subject: [PATCH 03/10] Add tests for isCleanNewPost selector; fix description --- editor/selectors.js | 4 ++-- editor/test/selectors.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/editor/selectors.js b/editor/selectors.js index 347d36cda3471..a7ea00136e4c5 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -89,8 +89,8 @@ export function isEditedPostDirty( state ) { } /** - * Returns true if there are unsaved values for the current edit session and if - * the currently edited post is yet to be saved. + * Returns true if there are no unsaved values for the current edit session and if + * the currently edited post is new (and has never been saved before). * * @param {Object} state Global application state * @return {Boolean} Whether new post and unsaved values exist diff --git a/editor/test/selectors.js b/editor/test/selectors.js index bb0fae346bee9..6577a36562f2e 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -13,6 +13,7 @@ import { hasEditorRedo, isEditedPostNew, isEditedPostDirty, + isCleanNewPost, getCurrentPost, getCurrentPostId, getCurrentPostType, @@ -174,12 +175,48 @@ describe( 'selectors', () => { editor: { dirty: false, }, + currentPost: {}, }; expect( isEditedPostDirty( state ) ).toBe( false ); } ); } ); + describe( 'isCleanNewPost', () => { + it( 'should return true when the post is not dirty and has not been saved before', () => { + const state = { + editor: { + dirty: false, + }, + currentPost: {}, + }; + + expect( isCleanNewPost( state ) ).to.be.true(); + } ); + + it( 'should return false when the post is not dirty but the post has been saved', () => { + const state = { + editor: { + dirty: false, + }, + currentPost: { id: 1 }, + }; + + expect( isCleanNewPost( state ) ).to.be.false(); + } ); + + it( 'should return false when the post is dirty but the post has not been saved', () => { + const state = { + editor: { + dirty: true, + currentPost: {}, + }, + }; + + expect( isCleanNewPost( state ) ).to.be.false(); + } ); + } ); + describe( 'getCurrentPost', () => { it( 'should return the current post', () => { const state = { From c428f3a333238789a53af04fa9a172537c057a41 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 10 Jul 2017 16:32:01 -0700 Subject: [PATCH 04/10] Add getDocumentTitle selector --- editor/effects.js | 18 +++++----- editor/selectors.js | 21 ++++++++++- editor/test/selectors.js | 77 ++++++++++++++++++++++++++++++++++++++++ lib/register.php | 2 +- 4 files changed, 107 insertions(+), 11 deletions(-) diff --git a/editor/effects.js b/editor/effects.js index 05daacf7857f9..1dc289f79d570 100644 --- a/editor/effects.js +++ b/editor/effects.js @@ -20,15 +20,11 @@ import { getCurrentPostType, getBlocks, getPostEdits, - isCleanNewPost, + getDocumentTitle, } from './selectors'; -let originalDocumentTitle; - -function populateDocumentTitle( title, isCleanNew = false ) { - if ( ! title.trim() ) { - title = isCleanNew ? __( 'New post' ) : __( '(Untitled)' ); - } +let originalDocumentTitle; // @todo Obtain document.title template from PHP export? +function populateDocumentTitle( title ) { if ( ! originalDocumentTitle ) { originalDocumentTitle = document.title; } @@ -37,11 +33,15 @@ function populateDocumentTitle( title, isCleanNew = false ) { export default { RESET_POST( action, store ) { - populateDocumentTitle( action.post.title ? action.post.title.raw : '', isCleanNewPost( store.getState() ) ); + setTimeout( () => { // Next-tick to ensure action has been applied to state in store . + populateDocumentTitle( getDocumentTitle( store.getState() ) ); + } ); }, EDIT_POST( action, store ) { if ( undefined !== action.edits.title ) { - populateDocumentTitle( action.edits.title, isCleanNewPost( store.getState() ) ); + setTimeout( () => { + populateDocumentTitle( getDocumentTitle( store.getState() ) ); + } ); } }, REQUEST_POST_UPDATE( action, store ) { diff --git a/editor/selectors.js b/editor/selectors.js index a7ea00136e4c5..c7fa75c210ff7 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -15,6 +15,11 @@ import { getBlockType } from 'blocks'; */ import { addQueryArgs } from './utils/url'; +/** + * WordPress dependencies + */ +import { __ } from 'i18n'; + /** * Returns the current editing mode. * @@ -237,11 +242,25 @@ export function isEditedPostBeingScheduled( state ) { * @return {String} Raw post title */ export function getEditedPostTitle( state ) { - return state.editor.edits.title === undefined + return get( state.editor, 'edits.title' ) === undefined ? get( state.currentPost, 'title.raw' ) : state.editor.edits.title; } +/** + * Gets the document title to be used. + * + * @param {Object} state Global application state + * @return {string} Document title + */ +export function getDocumentTitle( state ) { + let title = getEditedPostTitle( state ); + if ( '' === title.trim() ) { + title = isCleanNewPost( state ) ? __( 'New post' ) : __( '(Untitled)' ); + } + return title; +} + /** * Returns the raw excerpt of the post being edited, preferring the unsaved * value if different than the saved post. diff --git a/editor/test/selectors.js b/editor/test/selectors.js index 6577a36562f2e..714c39c8037e8 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -19,6 +19,7 @@ import { getCurrentPostType, getPostEdits, getEditedPostTitle, + getDocumentTitle, getEditedPostExcerpt, getEditedPostVisibility, isEditedPostPublished, @@ -54,6 +55,11 @@ import { getNotices, } from '../selectors'; +/** + * WordPress dependencies + */ +import { __ } from 'i18n'; + describe( 'selectors', () => { describe( 'getEditorMode', () => { it( 'should return the selected editor mode', () => { @@ -297,6 +303,77 @@ describe( 'selectors', () => { } ); } ); + describe( 'getDocumentTitle', () => { + it( 'should return current title unedited existing post', () => { + const state = { + currentPost: { + id: 123, + title: { raw: 'The Title' }, + }, + editor: { + dirty: false, + }, + }; + + expect( getDocumentTitle( state ) ).to.equal( 'The Title' ); + } ); + + it( 'should return current title for edited existing post', () => { + const state = { + currentPost: { + id: 123, + title: { raw: 'The Title' }, + }, + editor: { + dirty: true, + edits: { title: 'Modified Title' }, + }, + }; + + expect( getDocumentTitle( state ) ).to.equal( 'Modified Title' ); + } ); + + it( 'should return new post title when new post is clean', () => { + const state = { + currentPost: { + title: { raw: '' }, + }, + editor: { + dirty: false, + }, + }; + + expect( getDocumentTitle( state ) ).to.equal( __( 'New post' ) ); + } ); + + it( 'should return untitled title when new post is dirty', () => { + const state = { + currentPost: { + title: { raw: '' }, + }, + editor: { + dirty: true, + }, + }; + + expect( getDocumentTitle( state ) ).to.equal( __( '(Untitled)' ) ); + } ); + + it( 'should return untitled title', () => { + const state = { + currentPost: { + id: 123, + title: { raw: '' }, + }, + editor: { + dirty: true, + }, + }; + + expect( getDocumentTitle( state ) ).to.equal( __( '(Untitled)' ) ); + } ); + } ); + describe( 'getEditedPostExcerpt', () => { it( 'should return the post saved excerpt if the excerpt is not edited', () => { const state = { diff --git a/lib/register.php b/lib/register.php index 36fceb5b46e4d..cf16006d64b22 100644 --- a/lib/register.php +++ b/lib/register.php @@ -44,7 +44,7 @@ function gutenberg_menu() { add_submenu_page( 'gutenberg', - __( 'Gutenberg ', 'gutenberg' ), + __( 'Gutenberg', 'gutenberg' ), __( 'New Post', 'gutenberg' ), 'edit_posts', 'gutenberg', From e0c47b8ad29a66d5e12f30a765870e6496f15fdc Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 10 Jul 2017 19:51:46 -0700 Subject: [PATCH 05/10] Update tests to use Jest API --- editor/test/selectors.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/editor/test/selectors.js b/editor/test/selectors.js index 714c39c8037e8..8df897eb01f6f 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -197,7 +197,7 @@ describe( 'selectors', () => { currentPost: {}, }; - expect( isCleanNewPost( state ) ).to.be.true(); + expect( isCleanNewPost( state ) ).toBe( true ); } ); it( 'should return false when the post is not dirty but the post has been saved', () => { @@ -208,7 +208,7 @@ describe( 'selectors', () => { currentPost: { id: 1 }, }; - expect( isCleanNewPost( state ) ).to.be.false(); + expect( isCleanNewPost( state ) ).toBe( false ); } ); it( 'should return false when the post is dirty but the post has not been saved', () => { @@ -219,7 +219,7 @@ describe( 'selectors', () => { }, }; - expect( isCleanNewPost( state ) ).to.be.false(); + expect( isCleanNewPost( state ) ).toBe( false ); } ); } ); @@ -315,7 +315,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).to.equal( 'The Title' ); + expect( getDocumentTitle( state ) ).toBe( 'The Title' ); } ); it( 'should return current title for edited existing post', () => { @@ -330,7 +330,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).to.equal( 'Modified Title' ); + expect( getDocumentTitle( state ) ).toBe( 'Modified Title' ); } ); it( 'should return new post title when new post is clean', () => { @@ -343,7 +343,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).to.equal( __( 'New post' ) ); + expect( getDocumentTitle( state ) ).toBe( __( 'New post' ) ); } ); it( 'should return untitled title when new post is dirty', () => { @@ -356,7 +356,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).to.equal( __( '(Untitled)' ) ); + expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) ); } ); it( 'should return untitled title', () => { @@ -370,7 +370,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).to.equal( __( '(Untitled)' ) ); + expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) ); } ); } ); From 37873da0dfa89eab08d8ee915771617f2ba90194 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 18 Jul 2017 23:01:04 -0700 Subject: [PATCH 06/10] Add DocumentTitle component --- editor/document-title/index.js | 23 +++++++++++++++++++++++ editor/effects.js | 21 --------------------- editor/layout/index.js | 4 +++- editor/selectors.js | 10 ++++++++-- editor/test/selectors.js | 10 +++++----- 5 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 editor/document-title/index.js diff --git a/editor/document-title/index.js b/editor/document-title/index.js new file mode 100644 index 0000000000000..80a878fdf39dc --- /dev/null +++ b/editor/document-title/index.js @@ -0,0 +1,23 @@ +/** + * External dependencies + */ +import { connect } from 'react-redux'; +import { Component } from 'react'; + +/** + * Internal dependencies + */ +import { getDocumentTitle } from '../selectors'; + +class DocumentTitle extends Component { + render() { + document.title = this.props.title; + return null; + } +} + +export default connect( + ( state ) => ( { + title: getDocumentTitle( state ), + } ) +)( DocumentTitle ); diff --git a/editor/effects.js b/editor/effects.js index 1dc289f79d570..f75379c6c6b25 100644 --- a/editor/effects.js +++ b/editor/effects.js @@ -20,30 +20,9 @@ import { getCurrentPostType, getBlocks, getPostEdits, - getDocumentTitle, } from './selectors'; -let originalDocumentTitle; // @todo Obtain document.title template from PHP export? -function populateDocumentTitle( title ) { - if ( ! originalDocumentTitle ) { - originalDocumentTitle = document.title; - } - document.title = title + ' | ' + originalDocumentTitle; -} - export default { - RESET_POST( action, store ) { - setTimeout( () => { // Next-tick to ensure action has been applied to state in store . - populateDocumentTitle( getDocumentTitle( store.getState() ) ); - } ); - }, - EDIT_POST( action, store ) { - if ( undefined !== action.edits.title ) { - setTimeout( () => { - populateDocumentTitle( getDocumentTitle( store.getState() ) ); - } ); - } - }, REQUEST_POST_UPDATE( action, store ) { const { dispatch, getState } = store; const state = getState(); diff --git a/editor/layout/index.js b/editor/layout/index.js index e7fcf77cc6e20..9055f185875d0 100644 --- a/editor/layout/index.js +++ b/editor/layout/index.js @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import classnames from 'classnames'; /** - * WordPress Dependencie + * WordPress dependencies */ import { NoticeList } from 'components'; @@ -18,6 +18,7 @@ import Sidebar from '../sidebar'; import TextEditor from '../modes/text-editor'; import VisualEditor from '../modes/visual-editor'; import UnsavedChangesWarning from '../unsaved-changes-warning'; +import DocumentTitle from '../document-title'; import { removeNotice } from '../actions'; import { getEditorMode, @@ -32,6 +33,7 @@ function Layout( { mode, isSidebarOpened, notices, ...props } ) { return (
+
diff --git a/editor/selectors.js b/editor/selectors.js index c7fa75c210ff7..5e491b2afc121 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -247,6 +247,8 @@ export function getEditedPostTitle( state ) { : state.editor.edits.title; } +let originalDocumentTitle = null; + /** * Gets the document title to be used. * @@ -255,10 +257,14 @@ export function getEditedPostTitle( state ) { */ export function getDocumentTitle( state ) { let title = getEditedPostTitle( state ); - if ( '' === title.trim() ) { + if ( null === originalDocumentTitle ) { + originalDocumentTitle = document.title || __( 'Gutenberg' ); + } + + if ( ! title || '' === title.trim() ) { title = isCleanNewPost( state ) ? __( 'New post' ) : __( '(Untitled)' ); } - return title; + return title + ' | ' + originalDocumentTitle; } /** diff --git a/editor/test/selectors.js b/editor/test/selectors.js index 8df897eb01f6f..76c84bf59c4e3 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -315,7 +315,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( 'The Title' ); + expect( getDocumentTitle( state ) ).toBe( 'The Title | ' + __( 'Gutenberg' ) ); } ); it( 'should return current title for edited existing post', () => { @@ -330,7 +330,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( 'Modified Title' ); + expect( getDocumentTitle( state ) ).toBe( 'Modified Title | ' + __( 'Gutenberg' ) ); } ); it( 'should return new post title when new post is clean', () => { @@ -343,7 +343,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( __( 'New post' ) ); + expect( getDocumentTitle( state ) ).toBe( __( 'New post' ) + ' | ' + __( 'Gutenberg' ) ); } ); it( 'should return untitled title when new post is dirty', () => { @@ -356,7 +356,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) ); + expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) + ' | ' + __( 'Gutenberg' ) ); } ); it( 'should return untitled title', () => { @@ -370,7 +370,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) ); + expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) + ' | ' + __( 'Gutenberg' ) ); } ); } ); From b142f8bb79e922ee60aceae4fd791ea8fc8be350 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 18 Jul 2017 23:26:20 -0700 Subject: [PATCH 07/10] Store original document title on DocumentTitle; restore when unmounted --- editor/document-title/index.js | 10 +++++++++- editor/selectors.js | 7 +------ editor/test/selectors.js | 10 +++++----- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/editor/document-title/index.js b/editor/document-title/index.js index 80a878fdf39dc..1d0039a0dc4f8 100644 --- a/editor/document-title/index.js +++ b/editor/document-title/index.js @@ -10,10 +10,18 @@ import { Component } from 'react'; import { getDocumentTitle } from '../selectors'; class DocumentTitle extends Component { + componentWillMount() { + this.originalDocumentTitle = document.title; + } + render() { - document.title = this.props.title; + document.title = this.props.title + ' | ' + this.originalDocumentTitle; return null; } + + componentWillUnmount() { + document.title = this.originalDocumentTitle; + } } export default connect( diff --git a/editor/selectors.js b/editor/selectors.js index 5e491b2afc121..c12046be60404 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -247,8 +247,6 @@ export function getEditedPostTitle( state ) { : state.editor.edits.title; } -let originalDocumentTitle = null; - /** * Gets the document title to be used. * @@ -257,14 +255,11 @@ let originalDocumentTitle = null; */ export function getDocumentTitle( state ) { let title = getEditedPostTitle( state ); - if ( null === originalDocumentTitle ) { - originalDocumentTitle = document.title || __( 'Gutenberg' ); - } if ( ! title || '' === title.trim() ) { title = isCleanNewPost( state ) ? __( 'New post' ) : __( '(Untitled)' ); } - return title + ' | ' + originalDocumentTitle; + return title; } /** diff --git a/editor/test/selectors.js b/editor/test/selectors.js index 76c84bf59c4e3..8df897eb01f6f 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -315,7 +315,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( 'The Title | ' + __( 'Gutenberg' ) ); + expect( getDocumentTitle( state ) ).toBe( 'The Title' ); } ); it( 'should return current title for edited existing post', () => { @@ -330,7 +330,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( 'Modified Title | ' + __( 'Gutenberg' ) ); + expect( getDocumentTitle( state ) ).toBe( 'Modified Title' ); } ); it( 'should return new post title when new post is clean', () => { @@ -343,7 +343,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( __( 'New post' ) + ' | ' + __( 'Gutenberg' ) ); + expect( getDocumentTitle( state ) ).toBe( __( 'New post' ) ); } ); it( 'should return untitled title when new post is dirty', () => { @@ -356,7 +356,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) + ' | ' + __( 'Gutenberg' ) ); + expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) ); } ); it( 'should return untitled title', () => { @@ -370,7 +370,7 @@ describe( 'selectors', () => { }, }; - expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) + ' | ' + __( 'Gutenberg' ) ); + expect( getDocumentTitle( state ) ).toBe( __( '(Untitled)' ) ); } ); } ); From f4145f30f217ce58ab5e0fa5443dfd97f0faa768 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 19 Jul 2017 00:22:42 -0700 Subject: [PATCH 08/10] Remove side effects from render function --- editor/document-title/index.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/editor/document-title/index.js b/editor/document-title/index.js index 1d0039a0dc4f8..de0043dd7f1cd 100644 --- a/editor/document-title/index.js +++ b/editor/document-title/index.js @@ -10,12 +10,27 @@ import { Component } from 'react'; import { getDocumentTitle } from '../selectors'; class DocumentTitle extends Component { - componentWillMount() { + + constructor( props ) { + super( props ); this.originalDocumentTitle = document.title; } + setDocumentTitle( title ) { + document.title = title + ' | ' + this.originalDocumentTitle; + } + + componentDidMount() { + this.setDocumentTitle( this.props.title ); + } + + componentWillReceiveProps( nextProps ) { + if ( nextProps.title !== this.props.title ) { + this.setDocumentTitle( nextProps.title ); + } + } + render() { - document.title = this.props.title + ' | ' + this.originalDocumentTitle; return null; } From 7462db6cf0d87efea36bd084910301cba029cb4a Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 19 Jul 2017 00:59:30 -0700 Subject: [PATCH 09/10] Ensure currentPost reducer wraps edits in {raw} objects according to schema in UPDATE_POST action --- editor/selectors.js | 8 +++++--- editor/state.js | 13 +++++++++++-- editor/test/state.js | 10 +++++----- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/editor/selectors.js b/editor/selectors.js index c12046be60404..a9f873bbbe3bd 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -242,9 +242,11 @@ export function isEditedPostBeingScheduled( state ) { * @return {String} Raw post title */ export function getEditedPostTitle( state ) { - return get( state.editor, 'edits.title' ) === undefined - ? get( state.currentPost, 'title.raw' ) - : state.editor.edits.title; + const editedTitle = get( state.editor, 'edits.title' ); + if ( editedTitle !== undefined ) { + return editedTitle; + } + return get( state.currentPost, 'title.raw' ); } /** diff --git a/editor/state.js b/editor/state.js index 9d77c9f58c9c6..cd60e518df182 100644 --- a/editor/state.js +++ b/editor/state.js @@ -4,7 +4,7 @@ import optimist from 'redux-optimist'; import { combineReducers, applyMiddleware, createStore } from 'redux'; import refx from 'refx'; -import { reduce, keyBy, first, last, omit, without, flowRight } from 'lodash'; +import { reduce, keyBy, first, last, omit, without, flowRight, forOwn } from 'lodash'; /** * WordPress dependencies @@ -18,6 +18,7 @@ import { combineUndoableReducers } from './utils/undoable-reducer'; import effects from './effects'; const isMobile = window.innerWidth < 782; +const renderedPostProps = new Set( [ 'guid', 'title', 'excerpt', 'content' ] ); /** * Undoable reducer returning the editor post state, including blocks parsed @@ -263,7 +264,15 @@ export function currentPost( state = {}, action ) { return action.post; case 'UPDATE_POST': - return { ...state, ...action.edits }; + const post = { ...state }; + forOwn( action.edits, ( value, key ) => { + if ( renderedPostProps.has( key ) ) { + post[ key ] = { raw: value }; + } else { + post[ key ] = value; + } + } ); + return post; } return state; diff --git a/editor/test/state.js b/editor/test/state.js index 996b6f47157b6..9f29d3abefc20 100644 --- a/editor/test/state.js +++ b/editor/test/state.js @@ -603,22 +603,22 @@ describe( 'state', () => { describe( 'currentPost()', () => { it( 'should reset a post object', () => { - const original = deepFreeze( { title: 'unmodified' } ); + const original = deepFreeze( { title: { raw: 'unmodified' } } ); const state = currentPost( original, { type: 'RESET_POST', post: { - title: 'new post', + title: { raw: 'new post' }, }, } ); expect( state ).toEqual( { - title: 'new post', + title: { raw: 'new post' }, } ); } ); it( 'should update the post object with UPDATE_POST', () => { - const original = deepFreeze( { title: 'unmodified', status: 'publish' } ); + const original = deepFreeze( { title: { raw: 'unmodified' }, status: 'publish' } ); const state = currentPost( original, { type: 'UPDATE_POST', @@ -628,7 +628,7 @@ describe( 'state', () => { } ); expect( state ).toEqual( { - title: 'updated post object from server', + title: { raw: 'updated post object from server' }, status: 'publish', } ); } ); From 2309add89df5a50f69f1dd76018196687ee72115 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 20 Jul 2017 15:53:02 -0700 Subject: [PATCH 10/10] Normalize component method ordering and selector usage --- editor/document-title/index.js | 8 ++++---- editor/selectors.js | 10 +++++++--- editor/test/selectors.js | 4 ++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/editor/document-title/index.js b/editor/document-title/index.js index de0043dd7f1cd..f3f45e86bf3b4 100644 --- a/editor/document-title/index.js +++ b/editor/document-title/index.js @@ -30,13 +30,13 @@ class DocumentTitle extends Component { } } - render() { - return null; - } - componentWillUnmount() { document.title = this.originalDocumentTitle; } + + render() { + return null; + } } export default connect( diff --git a/editor/selectors.js b/editor/selectors.js index a9f873bbbe3bd..923e0d9cec14a 100644 --- a/editor/selectors.js +++ b/editor/selectors.js @@ -242,11 +242,15 @@ export function isEditedPostBeingScheduled( state ) { * @return {String} Raw post title */ export function getEditedPostTitle( state ) { - const editedTitle = get( state.editor, 'edits.title' ); + const editedTitle = getPostEdits( state ).title; if ( editedTitle !== undefined ) { return editedTitle; } - return get( state.currentPost, 'title.raw' ); + const currentPost = getCurrentPost( state ); + if ( currentPost.title && currentPost.title.raw ) { + return currentPost.title.raw; + } + return ''; } /** @@ -258,7 +262,7 @@ export function getEditedPostTitle( state ) { export function getDocumentTitle( state ) { let title = getEditedPostTitle( state ); - if ( ! title || '' === title.trim() ) { + if ( ! title.trim() ) { title = isCleanNewPost( state ) ? __( 'New post' ) : __( '(Untitled)' ); } return title; diff --git a/editor/test/selectors.js b/editor/test/selectors.js index 8df897eb01f6f..f6baaa07c46ac 100644 --- a/editor/test/selectors.js +++ b/editor/test/selectors.js @@ -312,6 +312,7 @@ describe( 'selectors', () => { }, editor: { dirty: false, + edits: {}, }, }; @@ -340,6 +341,7 @@ describe( 'selectors', () => { }, editor: { dirty: false, + edits: {}, }, }; @@ -353,6 +355,7 @@ describe( 'selectors', () => { }, editor: { dirty: true, + edits: {}, }, }; @@ -367,6 +370,7 @@ describe( 'selectors', () => { }, editor: { dirty: true, + edits: {}, }, };