Merge pull request #618 from coralproject/user-comment-history

show comments on the UserDetail drawer
This commit is contained in:
Kim Gardner
2017-06-02 16:10:22 -04:00
committed by GitHub
14 changed files with 178 additions and 20 deletions
@@ -33,3 +33,12 @@ export const setSortOrder = (order) => ({
order
});
export const changeUserDetailStatuses = (tab) => {
let statuses;
if (tab === 'all') {
statuses = ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'];
} else if (tab === 'rejected') {
statuses = ['REJECTED'];
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
};
@@ -17,11 +17,11 @@ const ActionButton = ({type = '', active, ...props}) => {
return (
<Button
className={`${typeName} ${styles.actionButton} ${active ? styles[`${typeName}__active`] : ''}`}
className={`${typeName} ${styles.actionButton} ${props.minimal ? styles.minimal : ''} ${active ? styles[`${typeName}__active`] : ''}`}
cStyle={typeName}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
>{t(`modqueue.${text}`)}</Button>
>{props.minimal ? '' : t(`modqueue.${text}`)}</Button>
);
};
@@ -189,6 +189,12 @@
width: 140px;
}
.minimal {
width: 45px;
min-width: 0;
float: left;
}
.approve__active {
box-shadow: none;
color: white;
@@ -8,3 +8,4 @@ export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER';
export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES';
@@ -8,6 +8,8 @@ const initialState = fromJS({
commentId: null,
commentStatus: null,
userDetailId: null,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
banDialog: false,
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
@@ -65,6 +67,10 @@ export default function moderation (state = initialState, action) {
return state.set('userDetailId', action.userId);
case actions.HIDE_USER_DETAIL:
return state.set('userDetailId', null);
case actions.CHANGE_USER_DETAIL_STATUSES:
return state
.set('userDetailActiveTab', action.tab)
.set('userDetailStatuses', action.statuses);
case actions.SET_SORT_ORDER:
return state.set('sortOrder', action.order);
default :
@@ -23,6 +23,7 @@ const Comment = ({
viewUserDetail,
suspectWords,
bannedWords,
minimal,
...props
}) => {
const links = linkify.getMatches(comment.body);
@@ -55,9 +56,13 @@ const Comment = ({
<div className={styles.container}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span className={styles.username} onClick={() => viewUserDetail(comment.user.id)}>
{comment.user.name}
</span>
{
!minimal && (
<span className={styles.username} onClick={() => viewUserDetail(comment.user.id)}>
{comment.user.name}
</span>
)
}
<span className={styles.created}>
{timeago(comment.created_at || Date.now() - props.index * 60 * 1000)}
</span>
@@ -91,7 +96,7 @@ const Comment = ({
</div>
<div className={styles.moderateArticle}>
Story: {comment.asset.title}
{!props.currentAsset &&
{!props.currentAsset && !minimal &&
<Link to={`/admin/moderate/${comment.asset.id}`}>{t('modqueue.moderate')}</Link>}
</div>
<div className={styles.itemBody}>
@@ -128,6 +133,7 @@ const Comment = ({
(action === 'APPROVE' && comment.status === 'ACCEPTED');
return (
<ActionButton
minimal={minimal}
key={i}
type={action}
user={comment.user}
@@ -173,6 +179,7 @@ const Comment = ({
};
Comment.propTypes = {
minimal: PropTypes.bool,
viewUserDetail: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
@@ -187,10 +187,15 @@ export default class Moderation extends Component {
{moderation.userDetailId && (
<UserDetail
id={moderation.userDetailId}
hideUserDetail={hideUserDetail} />
hideUserDetail={hideUserDetail}
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment} />
)}
</div>
);
}
}
@@ -36,6 +36,23 @@
background-color: transparent;
font-size: 16px;
position: absolute;
width: 100%;
width: 90%;
outline: none;
}
.commentStatuses {
padding: 0;
list-style: none;
li {
display: inline-block;
margin: 0 10px;
cursor: pointer;
padding: 0 10px;
}
}
.active {
font-weight: bold;
border-bottom: 3px solid #F36451;
}
@@ -2,12 +2,20 @@ import React, {PropTypes} from 'react';
import {Button, Drawer} from 'coral-ui';
import styles from './UserDetail.css';
import Slot from 'coral-framework/components/Slot';
import Comment from './Comment';
import {actionsMap} from '../helpers/moderationQueueActionsMap';
export default class UserDetail extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
bannedWords: PropTypes.array.isRequired,
suspectWords: PropTypes.array.isRequired,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
}
copyPermalink = () => {
@@ -20,9 +28,33 @@ export default class UserDetail extends React.Component {
}
}
changeStatus = (tab) => {
if (tab === 'all') {
this.props.changeStatus('all');
} else if (tab === 'rejected') {
this.props.changeStatus('rejected');
}
}
render () {
const {root: {user, totalComments, rejectedComments}, hideUserDetail} = this.props;
const {
root: {
user,
totalComments,
rejectedComments,
comments: {nodes}
},
moderation: {userDetailActiveTab: tab},
bannedWords,
suspectWords,
showBanUserDialog,
showSuspendUserDialog,
acceptComment,
rejectComment,
hideUserDetail
} = this.props;
const localProfile = user.profiles.find((p) => p.provider === 'local');
let profile;
if (localProfile) {
profile = localProfile.id;
@@ -62,8 +94,34 @@ export default class UserDetail extends React.Component {
<p>{`${(rejectedPercent).toFixed(1)}%`}</p>
</div>
</div>
<ul className={styles.commentStatuses}>
<li className={tab === 'all' ? styles.active : ''} onClick={this.changeStatus.bind(this, 'all')}>All</li>
<li className={tab === 'rejected' ? styles.active : ''} onClick={this.changeStatus.bind(this, 'rejected')}>Rejected</li>
</ul>
<div>
{
nodes.map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
return <Comment
key={i}
index={i}
comment={comment}
selected={false}
suspectWords={suspectWords}
bannedWords={bannedWords}
viewUserDetail={() => {}}
actions={actionsMap[status]}
showBanUserDialog={showBanUserDialog}
showSuspendUserDialog={showSuspendUserDialog}
acceptComment={acceptComment}
rejectComment={rejectComment}
currentAsset={null}
currentUserId={this.props.id}
minimal={true} />;
})
}
</div>
</Drawer>
);
}
}
@@ -247,6 +247,7 @@ span {
}
.created {
padding: 5px;
color: #262626;
font-size: 14px;
margin-left: 15px;
@@ -433,7 +434,6 @@ span {
.username {
color: blue;
text-decoration: underline;
padding: 5px;
cursor: pointer;
&:hover {
@@ -1,8 +1,25 @@
import React, {PropTypes} from 'react';
import {compose, gql} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import UserDetail from '../components/UserDetail';
import withQuery from 'coral-framework/hocs/withQuery';
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
import {getDefinitionName} from 'coral-framework/utils';
import {changeUserDetailStatuses} from 'coral-admin/src/actions/moderation';
import Comment from './Comment';
const commentConnectionFragment = gql`
fragment CoralAdmin_Moderation_CommentConnection on CommentConnection {
nodes {
...${getDefinitionName(Comment.fragments.comment)}
}
hasNextPage
startCursor
endCursor
}
${Comment.fragments.comment}
`;
const pluginFragments = getSlotsFragments([
'userProfile',
@@ -19,12 +36,12 @@ class UserDetailContainer extends React.Component {
return null;
}
return <UserDetail {...this.props}/>;
return <UserDetail changeStatus={this.props.changeUserDetailStatuses} {...this.props}/>;
}
}
export const withUserDetailQuery = withQuery(gql`
query CoralAdmin_UserDetail($author_id: ID!) {
query CoralAdmin_UserDetail($author_id: ID!, $statuses: [COMMENT_STATUS!]) {
user(id: $author_id) {
id
username
@@ -37,18 +54,35 @@ export const withUserDetailQuery = withQuery(gql`
}
totalComments: commentCount(query: {author_id: $author_id})
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
comments: comments(query: {
author_id: $author_id,
statuses: $statuses
}) {
...CoralAdmin_Moderation_CommentConnection
}
${pluginFragments.spreads('root')}
}
${Comment.fragments.comment}
${pluginFragments.definitions('user')}
${pluginFragments.definitions('root')}
${commentConnectionFragment}
`, {
options: ({id}) => {
options: ({id, moderation: {userDetailStatuses: statuses}}) => {
return {
variables: {author_id: id}
variables: {author_id: id, statuses}
};
}
});
const mapStateToProps = (state) => ({
moderation: state.moderation.toJS()
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({changeUserDetailStatuses}, dispatch)
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withUserDetailQuery,
)(UserDetailContainer);
+13 -3
View File
@@ -1,15 +1,25 @@
.drawer {
max-width: 700px;
min-width: 400px;
padding: 20px;
min-width: 550px;
position: fixed;
top: 0;
right: 0;
right: -17px;
bottom: 0;
background-color: white;
transition: transform 500ms ease-in-out;
box-shadow: -3px 0px 4px 0px rgba(0,0,0,0.15);
z-index: 10000;
.content {
position: absolute;
padding: 20px;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: scroll;
overflow-x: hidden;
}
}
.closeButton {
+3 -1
View File
@@ -6,7 +6,9 @@ const Drawer = ({children, handleClickOutside}) => {
return (
<div className={styles.drawer}>
<div className={styles.closeButton} onClick={handleClickOutside}>×</div>
{children}
<div className={styles.content}>
{children}
</div>
</div>
);
};
+3
View File
@@ -148,6 +148,9 @@ enum ACTION_TYPE {
# CommentsQuery allows the ability to query comments by a specific methods.
input CommentsQuery {
# Author of the comments
author_id: ID
# Current status of a comment. Requires the `ADMIN` role.
statuses: [COMMENT_STATUS!]