Skip to content

Commit

Permalink
Revisions #4: Add revisions list and revisions diff viewer (#14927)
Browse files Browse the repository at this point in the history
* Post Editor: Add revisions list and diff viewer

* Editor Revisions List > Item: Fix leftover from when it wasn't a component on its own

* Editor Sidebar: Fix usage from a ternary to a simple `&&`

* Editor Revisions: `isRequired`-ify components propTypes

* Post Revisions: Internalize normalizers in the revisions selectors

* Post Revisions: default order by dates (more recent first) in `getPostRevisions`

* Update to external 'prop-types' module

* Refactor imports and destructuring assignments

* Post-rebase fixes

* Revisions: Reset state in own lifecycle, not Editor methods

* Picking nits.

* Address feedback (minor)

Props @bperson
  • Loading branch information
bperson authored and mcsf committed Aug 21, 2017
1 parent 008270a commit ede8666
Show file tree
Hide file tree
Showing 20 changed files with 646 additions and 85 deletions.
2 changes: 2 additions & 0 deletions assets/stylesheets/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@
@import 'post-editor/editor-categories-tags/style';
@import 'post-editor/editor-confirmation-sidebar/style';
@import 'post-editor/editor-delete-post/style';
@import 'post-editor/editor-diff-viewer/style';
@import 'post-editor/editor-discussion/style';
@import 'post-editor/editor-drawer/style';
@import 'post-editor/editor-drawer-well/style';
Expand All @@ -476,6 +477,7 @@
@import 'post-editor/editor-preview/style';
@import 'post-editor/editor-publish-date/style';
@import 'post-editor/editor-revisions/style';
@import 'post-editor/editor-revisions-list/style';
@import 'post-editor/editor-seo-accordion/style';
@import 'post-editor/editor-sharing/style';
@import 'post-editor/editor-sidebar/style';
Expand Down
2 changes: 2 additions & 0 deletions client/post-editor/edit-post-status/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class EditPostStatus extends Component {
isPostPrivate: PropTypes.bool,
confirmationSidebarStatus: PropTypes.string,
setNestedSidebar: PropTypes.func,
selectRevision: PropTypes.func,
};

constructor( props ) {
Expand Down Expand Up @@ -182,6 +183,7 @@ export class EditPostStatus extends Component {
revisions={ this.props.post && this.props.post.revisions }
adminUrl={ adminUrl }
setNestedSidebar={ this.props.setNestedSidebar }
selectRevision={ this.props.selectRevision }
/>
</div>
);
Expand Down
49 changes: 49 additions & 0 deletions client/post-editor/editor-diff-viewer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* External dependencies
*/
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { get, map } from 'lodash';

/**
* Internal dependencies
*/
import { getPostRevision, getPostRevisionChanges } from 'state/selectors';

const EditorDiffViewer = ( { contentChanges, revision } ) => (
<div className="editor-diff-viewer">
<h1 className="editor-diff-viewer__title">
{ get( revision, 'title' ) }
</h1>
<div className="editor-diff-viewer__content">
{ map( contentChanges, ( change, changeIndex ) => {
const changeClassNames = classNames( {
'editor-diff-viewer__additions': change.added,
'editor-diff-viewer__deletions': change.removed,
} );
return (
<span className={ changeClassNames } key={ changeIndex }>
{ change.value }
</span>
);
} ) }
</div>
</div>
);

EditorDiffViewer.propTypes = {
contentChanges: PropTypes.array.isRequired,
postId: PropTypes.number,
revision: PropTypes.object,
selectedRevisionId: PropTypes.number,
siteId: PropTypes.number,
};

export default connect(
( state, { siteId, postId, selectedRevisionId } ) => ( {
contentChanges: getPostRevisionChanges( state, siteId, postId, selectedRevisionId ),
revision: getPostRevision( state, siteId, postId, selectedRevisionId, 'editing' ),
} )
)( EditorDiffViewer );
31 changes: 31 additions & 0 deletions client/post-editor/editor-diff-viewer/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.editor-diff-viewer__title {
padding-left: 10px; /* Intentionally non-standard to align editor body margin (10px) */
padding-right: 10px;
font-family: $serif;
font-size: 28px;
color: $gray-dark;
font-weight: 600;

@include breakpoint( '>480px' ) {
font-size: 32px;
}
}

.editor-diff-viewer {
margin: 0 auto;
max-width: 700px;
width: 100%;
}

.editor-diff-viewer__content {
padding: 11px;
}

.editor-diff-viewer__additions {
background: #e9f9fe;
}

.editor-diff-viewer__deletions {
color: #c83240;
text-decoration: line-through;
}
2 changes: 2 additions & 0 deletions client/post-editor/editor-drawer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const EditorDrawer = React.createClass( {
isPostPrivate: PropTypes.bool,
confirmationSidebarStatus: PropTypes.string,
setNestedSidebar: PropTypes.func,
selectRevision: PropTypes.func,
},

onExcerptChange: function( event ) {
Expand Down Expand Up @@ -335,6 +336,7 @@ const EditorDrawer = React.createClass( {
isPostPrivate={ this.props.isPostPrivate }
confirmationSidebarStatus={ this.props.confirmationSidebarStatus }
setNestedSidebar={ this.props.setNestedSidebar }
selectRevision={ this.props.selectRevision }
/>
</Accordion>
);
Expand Down
32 changes: 32 additions & 0 deletions client/post-editor/editor-revisions-list/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* External dependencies
*/
import React from 'react';
import PropTypes from 'prop-types';
import { localize } from 'i18n-calypso';

/**
* Internal dependencies
*/
import Button from 'components/button';

const EditorRevisionsListHeader = ( { loadRevision, selectedRevisionId, translate } ) => (
<div className="editor-revisions-list__header">
<Button
compact
className="editor-revisions-list__load-revision"
disabled={ selectedRevisionId === null }
onClick={ loadRevision }
>
{ translate( 'Load revision in the editor' ) }
</Button>
</div>
);

EditorRevisionsListHeader.propTypes = {
loadRevision: PropTypes.func,
selectedRevisionId: PropTypes.number,
translate: PropTypes.func.isRequired,
};

export default localize( EditorRevisionsListHeader );
101 changes: 101 additions & 0 deletions client/post-editor/editor-revisions-list/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* External dependencies
*/
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { connect } from 'react-redux';
import { map } from 'lodash';

/**
* Internal dependencies
*/
import EditorRevisionsListHeader from './header';
import EditorRevisionsListItem from './item';
import QueryPostRevisions from 'components/data/query-post-revisions';
import { getPostRevision, getPostRevisions } from 'state/selectors';
import { getSelectedSiteId } from 'state/ui/selectors';
import { getEditorPostId } from 'state/ui/editor/selectors';
import { isWithinBreakpoint } from 'lib/viewport';

class EditorRevisionsList extends PureComponent {
loadRevision = () => {
this.props.loadRevision( this.props.selectedRevision );
}

trySelectingRevision() {
if (
this.props.selectedRevisionId === null &&
this.props.revisions.length > 0 &&
isWithinBreakpoint( '>660px' )
) {
this.props.selectRevision( this.props.revisions[ 0 ].id );
}
}

componentWillMount() {
this.trySelectingRevision();
}

componentDidMount() {
// Make sure that scroll position in the editor is not preserved.
window.scrollTo( 0, 0 );
}

componentDidUpdate() {
this.trySelectingRevision();
}

render() {
return (
<div>
<QueryPostRevisions postId={ this.props.postId } siteId={ this.props.siteId } />
<EditorRevisionsListHeader
loadRevision={ this.loadRevision }
selectedRevisionId={ this.props.selectedRevisionId }
/>
<ul className="editor-revisions-list__list">
{ map( this.props.revisions, revision => {
const itemClasses = classNames(
'editor-revisions-list__revision',
{ 'is-selected': revision.id === this.props.selectedRevisionId }
);
return (
<li className={ itemClasses } key={ revision.id }>
<EditorRevisionsListItem
revision={ revision }
selectRevision={ this.props.selectRevision }
/>
</li>
);
} ) }
</ul>
</div>
);
}
}

EditorRevisionsList.propTypes = {
loadRevision: PropTypes.func.isRequired,
postId: PropTypes.number,
revisions: PropTypes.array.isRequired,
selectedRevision: PropTypes.object,
selectedRevisionId: PropTypes.number,
selectRevision: PropTypes.func.isRequired,
siteId: PropTypes.number,
};

export default connect(
( state, { selectedRevisionId } ) => {
const siteId = getSelectedSiteId( state );
const postId = getEditorPostId( state );
return {
postId,
revisions: getPostRevisions( state, siteId, postId, 'display' ),
selectedRevision: getPostRevision(
state, siteId, postId, selectedRevisionId, 'editing'
),
siteId,
};
},
)( EditorRevisionsList );
84 changes: 84 additions & 0 deletions client/post-editor/editor-revisions-list/item.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* External dependencies
*/
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { localize } from 'i18n-calypso';
import { isObject } from 'lodash';

/**
* Internal dependencies
*/
import PostTime from 'reader/post-time';

class EditorRevisionsListItem extends PureComponent {
selectRevision = () => {
this.props.selectRevision( this.props.revision.id );
}

render() {
return (
<button
className="editor-revisions-list__button"
onClick={ this.selectRevision }
type="button"
>
<span className="editor-revisions-list__date">
<PostTime date={ this.props.revision.date } />
</span>
&nbsp;
<span className="editor-revisions-list__author">
{ isObject( this.props.revision.author ) && (
this.props.translate( 'by %(author)s', {
args: { author: this.props.revision.author.display_name },
} )
) }
</span>

<div className="editor-revisions-list__changes">
{ this.props.revision.changes.added > 0 && (
<span className="editor-revisions-list__additions">
{ this.props.translate(
'%(changes)d word added',
'%(changes)d words added',
{
args: { changes: this.props.revision.changes.added },
count: this.props.revision.changes.added,
}
) }
</span>
) }

{ this.props.revision.changes.added > 0 && this.props.revision.changes.removed > 0 && ', ' }

{ this.props.revision.changes.removed > 0 && (
<span className="editor-revisions-list__deletions">
{ this.props.translate(
'%(changes)d word removed',
'%(changes)d words removed',
{
args: { changes: this.props.revision.changes.removed },
count: this.props.revision.changes.removed,
}
) }
</span>
) }

{ this.props.revision.changes.added === 0 && this.props.revision.changes.removed === 0 && (
<span className="editor-revisions-list__minor-changes">
{ this.props.translate( 'minor changes' ) }
</span>
) }
</div>
</button>
);
}
}

EditorRevisionsListItem.propTypes = {
revision: PropTypes.object.isRequired,
selectRevision: PropTypes.func.isRequired,
translate: PropTypes.func.isRequired,
};

export default localize( EditorRevisionsListItem );
Loading

0 comments on commit ede8666

Please sign in to comment.