Skip to content

Commit

Permalink
Comments: add single comment view (#19635)
Browse files Browse the repository at this point in the history
* Comments: sidebar menu item is selected on both list and single comment view
* Comments: updates link on the author date to the single comment view
* Comments: adds moderation data component
  • Loading branch information
rodrigoi authored Nov 14, 2017
1 parent b091c76 commit a1c399a
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 12 deletions.
1 change: 1 addition & 0 deletions assets/stylesheets/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@
@import 'my-sites/ads/style';
@import 'my-sites/all-sites/style';
@import 'my-sites/all-sites-icon/style';
@import 'my-sites/comment/style';
@import 'my-sites/comments/style';
@import 'my-sites/comments/comment/style';
@import 'my-sites/current-site/style';
Expand Down
110 changes: 110 additions & 0 deletions client/components/data/moderate-comment/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/** @format */
/**
* External dependencies
*
*/
import { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { get } from 'lodash';

/**
* Internal dependencies
*/
import {
bumpStat,
composeAnalytics,
recordTracksEvent,
withAnalytics,
} from 'state/analytics/actions';
import { changeCommentStatus, deleteComment } from 'state/comments/actions';
import { getSiteComment } from 'state/selectors';

class ModerateComment extends Component {
static propTypes = {
siteId: PropTypes.number,
postId: PropTypes.number,
commentId: PropTypes.number.isRequired,
newStatus: PropTypes.string,
currentStatus: PropTypes.string,
updateCommentStatus: PropTypes.func.isRequired,
destroyComment: PropTypes.func.isRequired,
};

componentDidMount() {
this.moderate( this.props );
}

componentWillReceiveProps( nextProps ) {
if (
this.props.siteId === nextProps.siteId &&
this.props.postId === nextProps.postId &&
this.props.commentId === nextProps.commentId &&
this.props.newStatus === nextProps.newStatus
) {
return;
}

this.moderate( nextProps );
}

moderate( {
siteId,
postId,
commentId,
newStatus,
currentStatus,
updateCommentStatus,
destroyComment,
} ) {
if ( ! siteId || ! postId || ! commentId || ! newStatus || newStatus === currentStatus ) {
return;
}

if ( 'delete' === newStatus ) {
destroyComment();
return;
}

updateCommentStatus();
}

render() {
return null;
}
}

const mapStateToProps = ( state, { siteId, commentId } ) => {
const comment = getSiteComment( state, siteId, commentId );

return {
currentStatus: get( comment, 'status' ),
};
};

const mapDispatchToProps = ( dispatch, { siteId, postId, commentId, newStatus } ) => ( {
updateCommentStatus: () =>
dispatch(
withAnalytics(
composeAnalytics(
recordTracksEvent( 'calypso_comment_management_change_status_from_email', {
status: newStatus,
} ),
bumpStat( 'calypso_comment_management', 'comment_status_changed_to_' + newStatus )
),
changeCommentStatus( siteId, postId, commentId, newStatus )
)
),
destroyComment: () =>
dispatch(
withAnalytics(
composeAnalytics(
recordTracksEvent( 'calypso_comment_management_delete' ),
bumpStat( 'calypso_comment_management', 'comment_deleted' )
),
deleteComment( siteId, postId, commentId )
)
),
} );

export default connect( mapStateToProps, mapDispatchToProps )( ModerateComment );
46 changes: 46 additions & 0 deletions client/my-sites/comment/comment-permalink.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/** @format */
/**
* External dependencies
*/
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { localize } from 'i18n-calypso';
import { get, isUndefined } from 'lodash';

/**
* Internal dependencies
*/
import Card from 'components/card';
import SectionHeader from 'components/section-header';
import ExternalLink from 'components/external-link';
import { getSiteComment } from 'state/selectors';

const CommentPermalink = ( { isLoading, permaLink, translate } ) =>
! isLoading && (
<Card className="comment__comment-permalink">
<SectionHeader label={ translate( 'Comment Permalink' ) } />
<ExternalLink icon={ true } href={ permaLink }>
{ permaLink }
</ExternalLink>
</Card>
);

CommentPermalink.propTypes = {
siteId: PropTypes.number,
commentId: PropTypes.number.isRequired,
isLoading: PropTypes.bool.isRequired,
permaLink: PropTypes.string,
translate: PropTypes.func.isRequired,
};

const mapStateToProps = ( state, { siteId, commentId } ) => {
const comment = getSiteComment( state, siteId, commentId );

return {
isLoading: isUndefined( comment ),
permaLink: get( comment, 'URL', '' ),
};
};

export default connect( mapStateToProps )( localize( CommentPermalink ) );
82 changes: 82 additions & 0 deletions client/my-sites/comment/main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/** @format */
/**
* External dependencies
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { localize } from 'i18n-calypso';
import { get } from 'lodash';

/**
* Internal dependencies
*/
import Main from 'components/main';
import EmptyContent from 'components/empty-content';
import DocumentHead from 'components/data/document-head';
import ModerateComment from 'components/data/moderate-comment';
import Comment from 'my-sites/comments/comment';
import CommentPermalink from 'my-sites/comment/comment-permalink';
import CommentListHeader from 'my-sites/comments/comment-list/comment-list-header';
import PageViewTracker from 'lib/analytics/page-view-tracker';
import { preventWidows } from 'lib/formatting';
import { getSiteComment, canCurrentUser } from 'state/selectors';
import getSiteId from 'state/selectors/get-site-id';

export class CommentView extends Component {
static propTypes = {
siteId: PropTypes.number,
postId: PropTypes.number,
commentId: PropTypes.number.isRequired,
action: PropTypes.string,
canModerateComments: PropTypes.bool.isRequired,
translate: PropTypes.func.isRequired,
};

render() {
const { siteId, postId, commentId, action, canModerateComments, translate } = this.props;

return (
// eslint-disable-next-line wpcalypso/jsx-classname-namespace
<Main className="comments" wideLayout>
<PageViewTracker path="/comment/:site" title="Comments" />
<DocumentHead title={ translate( 'Comment' ) } />
{ canModerateComments && (
<ModerateComment { ...{ siteId, postId, commentId, newStatus: action } } />
) }
<CommentListHeader { ...{ postId } } />
{ ! canModerateComments && (
<EmptyContent
title={ preventWidows(
translate( "Oops! You don't have permission to manage comments." )
) }
line={ preventWidows(
translate( "If you think you should, contact this site's administrator." )
) }
illustration="/calypso/images/illustrations/illustration-500.svg"
/>
) }
{ canModerateComments && <Comment commentId={ commentId } refreshCommentData={ true } /> }
{ canModerateComments && <CommentPermalink { ...{ siteId, commentId } } /> }
</Main>
);
}
}

const mapStateToProps = ( state, ownProps ) => {
const { commentId, siteFragment } = ownProps;

const siteId = getSiteId( state, siteFragment );
const comment = getSiteComment( state, siteId, commentId );
const postId = get( comment, 'post.ID' );

const canModerateComments = canCurrentUser( state, siteId, 'moderate_comments' ) !== false;

return {
siteId,
postId,
canModerateComments,
};
};

export default connect( mapStateToProps )( localize( CommentView ) );
14 changes: 14 additions & 0 deletions client/my-sites/comment/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.comment__comment-permalink {
padding: 0;
margin-top: 16px;

.section-header {
margin: 0;
}

.external-link {
display: block;
padding: 24px;
word-wrap: break-word;
}
}
20 changes: 15 additions & 5 deletions client/my-sites/comments/comment/comment-author.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import { get } from 'lodash';
/**
* Internal dependencies
*/
import { isEnabled } from 'config';
import Emojify from 'components/emojify';
import ExternalLink from 'components/external-link';
import Gravatar from 'components/gravatar';
import CommentPostLink from 'my-sites/comments/comment/comment-post-link';
import { decodeEntities } from 'lib/formatting';
import { urlToDomainAndPath } from 'lib/url';
import { getSiteComment } from 'state/selectors';
import { getSelectedSiteId } from 'state/ui/selectors';
import { getSelectedSiteId, getSelectedSiteSlug } from 'state/ui/selectors';

export class CommentAuthor extends Component {
static propTypes = {
Expand Down Expand Up @@ -86,9 +87,15 @@ export class CommentAuthor extends Component {

<div className="comment__author-info-element">
<span className="comment__date">
<ExternalLink href={ commentUrl } title={ formattedDate }>
{ relativeDate }
</ExternalLink>
{ isEnabled( 'comments/management/comment-view' ) ? (
<a href={ commentUrl } title={ formattedDate }>
{ relativeDate }
</a>
) : (
<ExternalLink href={ commentUrl } title={ formattedDate }>
{ relativeDate }
</ExternalLink>
) }
</span>
{ authorUrl && (
<span className="comment__author-url">
Expand All @@ -107,6 +114,7 @@ export class CommentAuthor extends Component {

const mapStateToProps = ( state, { commentId } ) => {
const siteId = getSelectedSiteId( state );
const siteSlug = getSelectedSiteSlug( state );
const comment = getSiteComment( state, siteId, commentId );
const authorAvatarUrl = get( comment, 'author.avatar_URL' );
const authorDisplayName = decodeEntities( get( comment, 'author.name' ) );
Expand All @@ -119,7 +127,9 @@ const mapStateToProps = ( state, { commentId } ) => {
commentContent: get( comment, 'content' ),
commentDate: get( comment, 'date' ),
commentType: get( comment, 'type', 'comment' ),
commentUrl: get( comment, 'URL' ),
commentUrl: isEnabled( 'comments/management/comment-view' )
? `/comment/${ siteSlug }/${ commentId }`
: get( comment, 'URL' ),
gravatarUser,
};
};
Expand Down
Loading

0 comments on commit a1c399a

Please sign in to comment.