Skip to content

Commit

Permalink
Post Editor: Add revisions list and diff viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
bperson committed Jun 21, 2017
1 parent 899b0e1 commit 5122f83
Show file tree
Hide file tree
Showing 10 changed files with 535 additions and 63 deletions.
2 changes: 2 additions & 0 deletions assets/stylesheets/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,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 @@ -459,6 +460,7 @@
@import 'post-editor/editor-post-type-unsupported/style';
@import 'post-editor/editor-preview/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
52 changes: 52 additions & 0 deletions client/post-editor/editor-diff-viewer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* External dependencies
*/
import classNames from 'classnames';
import { get, map } from 'lodash';
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';

/**
* Internal dependencies
*/
import getPostRevision from 'state/selectors/get-post-revision';
import getPostRevisionChanges from 'state/selectors/get-post-revision-changes';
import { normalizeForEditing } from 'state/selectors/utils/revisions';

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,
postId: PropTypes.number,
revision: PropTypes.object,
selectedRevisionId: PropTypes.number,
siteId: PropTypes.number,
};

export default connect(
( state, ownProps ) => ( {
contentChanges: getPostRevisionChanges( state, ownProps.siteId, ownProps.postId, ownProps.selectedRevisionId ),
revision: normalizeForEditing(
getPostRevision( state, ownProps.siteId, ownProps.postId, ownProps.selectedRevisionId )
),
} )
)( 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;
}
31 changes: 31 additions & 0 deletions client/post-editor/editor-revisions-list/header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* External dependencies
*/
import { localize } from 'i18n-calypso';
import React, { PropTypes } from 'react';

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

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

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

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

/**
* Internal dependencies
*/
import EditorRevisionsListHeader from './header';
import EditorRevisionsListItem from './item';
import QueryPostRevisions from 'components/data/query-post-revisions';
import getPostRevision from 'state/selectors/get-post-revision';
import getPostRevisions from 'state/selectors/get-post-revisions';
import {
normalizeForDisplay,
normalizeForEditing
} from 'state/selectors/utils/revisions';
import { getSelectedSiteId } from 'state/ui/selectors';
import { getEditorPostId } from 'state/ui/editor/selectors';
import viewport 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 &&
viewport.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,
postId: PropTypes.number,
revisions: PropTypes.array,
selectedRevision: PropTypes.object,
selectedRevisionId: PropTypes.number,
selectRevision: PropTypes.func,
siteId: PropTypes.number,
};

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

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

class EditorRevisionsListItem extends PureComponent {
selectRevision = event => {
const revisionId = parseInt( event.currentTarget.dataset.revisionId, 10 );
this.props.selectRevision( revisionId );
}

render() {
return (
<button
className="editor-revisions-list__button"
data-revision-id={ this.props.revision.id }
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,
selectRevision: PropTypes.func,
translate: PropTypes.func,
};

export default localize( EditorRevisionsListItem );
Loading

0 comments on commit 5122f83

Please sign in to comment.