Merge branch 'next' into user-status-refactor

This commit is contained in:
Wyatt Johnson
2017-11-17 09:43:59 -07:00
23 changed files with 120 additions and 14 deletions
@@ -59,6 +59,9 @@ export default withFragments({
editing {
edited
}
status_history {
type
}
hasParent
${getSlotFragmentSpreads(slots, 'comment')}
...${getDefinitionName(CommentLabels.fragments.comment)}
@@ -111,6 +111,17 @@ class ModerationContainer extends Component {
return this.handleCommentChange(prev, comment, notifyText);
},
},
{
document: COMMENT_RESET_SUBSCRIPTION,
variables,
updateQuery: (prev, {subscriptionData: {data: {commentReset: comment}}}) => {
const user = comment.status_history[comment.status_history.length - 1].assigned_by;
const notifyText = this.props.auth.user.id === user.id
? ''
: t('modqueue.notify_reset', user.username, prepareNotificationText(comment.body));
return this.handleCommentChange(prev, comment, notifyText);
},
},
{
document: COMMENT_EDITED_SUBSCRIPTION,
variables,
@@ -299,6 +310,23 @@ const COMMENT_REJECTED_SUBSCRIPTION = gql`
${Comment.fragments.comment}
`;
const COMMENT_RESET_SUBSCRIPTION = gql`
subscription CommentReset($asset_id: ID){
commentReset(asset_id: $asset_id){
...${getDefinitionName(Comment.fragments.comment)}
status_history {
type
created_at
assigned_by {
id
username
}
}
}
}
${Comment.fragments.comment}
`;
const LOAD_MORE_QUERY = gql`
query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Cursor, $sortOrder: SORT_ORDER, $asset_id: ID, $tags:[String!], $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sortOrder: $sortOrder, action_type: $action_type, tags: $tags}) {
@@ -158,6 +158,7 @@
}
.content {
word-wrap: break-word;
}
.footer {
@@ -182,6 +182,7 @@ export default class Comment extends React.Component {
editableUntil: PropTypes.string,
})
}).isRequired,
setCommentStatus: PropTypes.func.isRequired,
// edit a comment, passed (id, asset_id, { body })
editComment: PropTypes.func,
@@ -343,7 +344,12 @@ export default class Comment extends React.Component {
} = this.props;
if (!highlighted && this.commentIsRejected(comment)) {
return <CommentTombstone action='reject' />;
return <CommentTombstone action='reject' onUndo={() => {
this.props.setCommentStatus({
commentId: comment.id,
status: comment.status_history[comment.status_history.length - 2].type,
});
}}/>;
}
if (this.commentIsIgnored(comment)) {
@@ -3,4 +3,10 @@
text-align: center;
padding: 1em;
color: #3E4F71;
}
.undo {
cursor: pointer;
text-decoration: underline;
margin-left: 5px;
}
@@ -24,6 +24,9 @@ class CommentTombstone extends React.Component {
<hr aria-hidden={true} />
<p className={styles.commentTombstone}>
{this.getCopy()}
{this.props.action === 'reject' &&
<span className={styles.undo} onClick={this.props.onUndo}>{t('comment.undo_reject')}</span>
}
</p>
</div>
);
@@ -32,6 +35,7 @@ class CommentTombstone extends React.Component {
CommentTombstone.propTypes = {
action: PropTypes.string,
onUndo: PropTypes.func,
};
export default CommentTombstone;
@@ -3,6 +3,7 @@ import React from 'react';
import Comment from '../components/Comment';
import {withFragments} from 'coral-framework/hocs';
import {getSlotFragmentSpreads} from 'coral-framework/utils';
import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {THREADING_LEVEL} from '../constants/stream';
import hoistStatics from 'recompose/hoistStatics';
import {nest} from '../graphql/utils';
@@ -75,6 +76,9 @@ const singleCommentFragment = gql`
id
username
}
status_history {
type
}
action_summaries {
__typename
count
@@ -130,6 +134,7 @@ const withCommentFragments = withFragments({
const enhance = compose(
withAnimateEnter,
withCommentFragments,
withSetCommentStatus,
);
export default enhance(Comment);
@@ -102,6 +102,9 @@ export default {
}
}
}
status_history {
type
}
action_summaries {
count
current_user {
@@ -182,6 +185,7 @@ export default {
editableUntil: new Date().toISOString(),
edited: false,
},
status_history: [],
id: `pending-${uuid()}`,
}
}
+14 -2
View File
@@ -3,6 +3,7 @@ import cn from 'classnames';
import styles from './Slot.css';
import {connect} from 'react-redux';
import omit from 'lodash/omit';
import kebabCase from 'lodash/kebabCase';
import PropTypes from 'prop-types';
import isEqual from 'lodash/isEqual';
import {getShallowChanges} from 'coral-framework/utils';
@@ -58,6 +59,7 @@ class Slot extends React.Component {
childFactory,
defaultComponent: DefaultComponent,
queryData,
fill,
} = this.props;
const {plugins} = this.context;
let children = this.getChildren();
@@ -72,7 +74,7 @@ class Slot extends React.Component {
}
return (
<Component className={cn({[styles.inline]: inline, [styles.debug]: pluginConfig.debug}, className)}>
<Component className={cn({[styles.inline]: inline, [styles.debug]: pluginConfig.debug}, className, `talk-slot-${kebabCase(fill)}`)}>
{children}
</Component>
);
@@ -85,12 +87,22 @@ Slot.defaultProps = {
Slot.propTypes = {
fill: PropTypes.string.isRequired,
inline: PropTypes.bool,
className: PropTypes.string,
reduxState: PropTypes.object,
defaultComponent: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
]),
/**
* You may specify the component to use as the root wrapper.
* Defaults to 'div'.
*/
component: PropTypes.any,
component: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
]),
// props coming from graphql must be passed through this property.
queryData: PropTypes.object,
@@ -138,6 +138,9 @@ export const withSetCommentStatus = withMutation(
const fragment = gql`
fragment Talk_SetCommentStatus on Comment {
status
status_history {
type
}
}`;
const fragmentId = `Comment_${commentId}`;
@@ -145,6 +148,8 @@ export const withSetCommentStatus = withMutation(
const data = proxy.readFragment({fragment, id: fragmentId});
data.status = status;
data.status_history = data.status_history ? data.status_history : [];
data.status_history.push({__typename: 'CommentStatusHistory', type: status});
proxy.writeFragment({fragment, id: fragmentId, data});
}
+14 -1
View File
@@ -13,6 +13,18 @@
border-bottom: solid 1px #EBEBEB;
}
.main {
min-width: 70%;
}
.sidebar {
min-width: 30%;
}
.commentBody {
word-wrap: break-word;
}
.assetURL {
text-decoration: none;
font-weight: bold;
@@ -44,7 +56,8 @@
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
min-width: 136px;
min-width: 140px;
padding: 0px 10px;
}
li {
+1 -1
View File
@@ -19,7 +19,7 @@ class Comment extends React.Component {
return (
<div className={styles.myComment}>
<div>
<div className={styles.main}>
<Slot
fill="commentContent"
defaultComponent={CommentContent}
+2 -4
View File
@@ -52,13 +52,11 @@ const RootMutation = {
setCommentStatus: async (_, {id, status}, {mutators: {Comment}, pubsub}) => {
const comment = await Comment.setStatus({id, status});
if (status === 'ACCEPTED') {
// Publish the comment status change via the subscription.
pubsub.publish('commentAccepted', comment);
} else if (status === 'REJECTED') {
// Publish the comment status change via the subscription.
pubsub.publish('commentRejected', comment);
} else if (status === 'NONE') {
pubsub.publish('commentReset', comment);
}
},
addTag: async (_, {tag}, {mutators: {Tag}}) => {
+3
View File
@@ -11,6 +11,9 @@ const Subscription = {
commentRejected(comment) {
return comment;
},
commentReset(comment) {
return comment;
},
commentFlagged(comment) {
return comment;
},
+8 -1
View File
@@ -2,6 +2,7 @@ const {
SUBSCRIBE_COMMENT_ACCEPTED,
SUBSCRIBE_COMMENT_REJECTED,
SUBSCRIBE_COMMENT_FLAGGED,
SUBSCRIBE_COMMENT_RESET,
SUBSCRIBE_ALL_COMMENT_EDITED,
SUBSCRIBE_ALL_COMMENT_ADDED,
SUBSCRIBE_ALL_USER_SUSPENDED,
@@ -59,12 +60,18 @@ const setupFunctions = {
}
return !args.asset_id || comment.asset_id === args.asset_id;
},
commentRejected: (options, args) => (comment, context) => {
commentRejected: (options, args, comment, context) => {
if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_REJECTED)) {
return false;
}
return !args.asset_id || comment.asset_id === args.asset_id;
},
commentReset: (options, args, comment, context) => {
if (!context.user || !context.user.can(SUBSCRIBE_COMMENT_RESET)) {
return false;
}
return !args.asset_id || comment.asset_id === args.asset_id;
},
userSuspended: (options, args, user, context) => {
if (
!context.user
+4
View File
@@ -1499,6 +1499,10 @@ type Subscription {
# Requires the `ADMIN` or `MODERATOR` role.
commentRejected(asset_id: ID): Comment
# Get an update whenever the status of a comment has been reset.
# Requires the `ADMIN` or `MODERATOR` role.
commentReset(asset_id: ID): Comment
# Get an update whenever a user has been suspended.
# `user_id` must match id of current user except for
# users with the `ADMIN` or `MODERATOR` role.
+2
View File
@@ -17,6 +17,7 @@ en:
characters_remaining: "characters remaining"
comment:
anon: "Anonymous"
undo_reject: "Undo"
ban_user: "Ban User"
comment: "Post a comment"
edited: Edited
@@ -281,6 +282,7 @@ en:
notify_accepted: '{0} accepted comment "{1}"'
notify_rejected: '{0} rejected comment "{1}"'
notify_flagged: '{0} flagged comment "{1}"'
notify_reset: '{0} reset status of comment "{1}"'
approve: "Approve"
approved: "Approved"
ban_user: "Ban"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "talk",
"version": "3.7.1",
"version": "3.8.0",
"description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net",
"main": "app.js",
"private": true,
+1
View File
@@ -2,6 +2,7 @@ module.exports = {
SUBSCRIBE_COMMENT_ACCEPTED: 'SUBSCRIBE_COMMENT_ACCEPTED',
SUBSCRIBE_COMMENT_REJECTED: 'SUBSCRIBE_COMMENT_REJECTED',
SUBSCRIBE_COMMENT_FLAGGED: 'SUBSCRIBE_COMMENT_FLAGGED',
SUBSCRIBE_COMMENT_RESET: 'SUBSCRIBE_COMMENT_RESET',
SUBSCRIBE_ALL_COMMENT_ADDED: 'SUBSCRIBE_ALL_COMMENT_ADDED',
SUBSCRIBE_ALL_COMMENT_EDITED: 'SUBSCRIBE_ALL_COMMENT_EDITED',
SUBSCRIBE_ALL_USER_SUSPENDED: 'SUBSCRIBE_ALL_USER_SUSPENDED',
+2
View File
@@ -6,6 +6,8 @@ module.exports = (user, perm) => {
case types.SUBSCRIBE_COMMENT_FLAGGED:
case types.SUBSCRIBE_COMMENT_ACCEPTED:
case types.SUBSCRIBE_COMMENT_REJECTED:
case types.SUBSCRIBE_COMMENT_RESET:
return check(user, ['ADMIN', 'MODERATOR']);
case types.SUBSCRIBE_ALL_COMMENT_EDITED:
case types.SUBSCRIBE_ALL_COMMENT_ADDED:
case types.SUBSCRIBE_ALL_USER_SUSPENDED:
@@ -44,6 +44,7 @@
margin: 0;
quotes: '\201c' '\201d';
margin-bottom: 10px;
word-wrap: break-word;
}
.quote:before {
@@ -33,9 +33,9 @@ const BanUserDialog = ({showBanDialog, closeBanDialog, banUser}) => (
);
BanUserDialog.propTypes = {
showBanDialog: PropTypes.func.isRequired,
showBanDialog: PropTypes.bool.isRequired,
closeBanDialog: PropTypes.func.isRequired,
banUser: PropTypes.func.isRequired,
};
export default BanUserDialog;
export default BanUserDialog;
+2 -1
View File
@@ -515,5 +515,6 @@ module.exports = {
HandleAuthPopupCallback,
HandleGenerateCredentials,
HandleLogout,
CheckBlacklisted
CheckBlacklisted,
CheckRecaptcha,
};