mirror of
https://github.com/wassname/talk.git
synced 2026-07-19 11:28:50 +08:00
Flag users as "Always premoderate" (#2145)
* Flag users as "Always premoderate" Status can be set using the action dropdown in the People tab and in User Details. Users flagged as "Always premoderate" will have their comments sent to the premod queue. Users can be filtered with the Always Premoderate status. Include Always Premoderate status changes in User History. Add spanish translations. * Reorder CSS as per cvle's suggestion * Address second comment
This commit is contained in:
+2
-1
@@ -105,7 +105,8 @@ function printUserAsTable(user) {
|
||||
Suspension: user.suspended
|
||||
? `Until ${user.status.suspension.until}`
|
||||
: false,
|
||||
}
|
||||
},
|
||||
{ 'Always premod comments': user.alwaysPremod }
|
||||
);
|
||||
|
||||
console.log(table.toString());
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
SHOW_ALWAYS_PREMOD_USER_DIALOG,
|
||||
HIDE_ALWAYS_PREMOD_USER_DIALOG,
|
||||
} from '../constants/alwaysPremodUserDialog.js';
|
||||
|
||||
export const showAlwaysPremodUserDialog = ({ userId, username }) => ({
|
||||
type: SHOW_ALWAYS_PREMOD_USER_DIALOG,
|
||||
userId,
|
||||
username,
|
||||
});
|
||||
|
||||
export const hideAlwaysPremodUserDialog = () => ({
|
||||
type: HIDE_ALWAYS_PREMOD_USER_DIALOG,
|
||||
});
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
SET_SEARCH_VALUE,
|
||||
SHOW_BANUSER_DIALOG,
|
||||
HIDE_BANUSER_DIALOG,
|
||||
SHOW_ALWAYS_PREMOD_USER_DIALOG,
|
||||
HIDE_ALWAYS_PREMOD_USER_DIALOG,
|
||||
SHOW_REJECT_USERNAME_DIALOG,
|
||||
HIDE_REJECT_USERNAME_DIALOG,
|
||||
SET_INDICATOR_TRACK,
|
||||
@@ -61,6 +63,15 @@ export const setSearchValue = value => ({
|
||||
export const showBanUserDialog = user => ({ type: SHOW_BANUSER_DIALOG, user });
|
||||
export const hideBanUserDialog = () => ({ type: HIDE_BANUSER_DIALOG });
|
||||
|
||||
// Always premod User Dialog
|
||||
export const showAlwaysPremodUserDialog = user => ({
|
||||
type: SHOW_ALWAYS_PREMOD_USER_DIALOG,
|
||||
user,
|
||||
});
|
||||
export const hideAlwaysPremodUserDialog = () => ({
|
||||
type: HIDE_ALWAYS_PREMOD_USER_DIALOG,
|
||||
});
|
||||
|
||||
// Reject Username Dialog
|
||||
export const showRejectUsernameDialog = user => ({
|
||||
type: SHOW_REJECT_USERNAME_DIALOG,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.dialog {
|
||||
border: none;
|
||||
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
|
||||
width: 400px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.header {
|
||||
color: black;
|
||||
font-size: 1.5em;
|
||||
font-weight: 500;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.subheader {
|
||||
color: black;
|
||||
font-size: 1.3em;
|
||||
font-weight: 500;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 6px;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Dialog } from 'coral-ui';
|
||||
import styles from './AlwaysPremodUserDialog.css';
|
||||
|
||||
import Button from 'coral-ui/components/Button';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class AlwaysPremodUserDialog extends React.Component {
|
||||
handlePerform = () => {
|
||||
this.props.onPerform();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { open, onCancel, username, info } = this.props;
|
||||
return (
|
||||
<Dialog
|
||||
className={cn(styles.dialog, 'talk-always-premod-user-dialog')}
|
||||
id="alwaysPremodUserDialog"
|
||||
open={open}
|
||||
onCancel={onCancel}
|
||||
title={t('alwayspremoddialog.always_premod_user')}
|
||||
>
|
||||
<span className={styles.close} onClick={onCancel}>
|
||||
×
|
||||
</span>
|
||||
<section>
|
||||
<h2 className={styles.header}>
|
||||
{t('alwayspremoddialog.always_premod_user')}
|
||||
</h2>
|
||||
<h3 className={styles.subheader}>
|
||||
{t('alwayspremoddialog.are_you_sure', username)}
|
||||
</h3>
|
||||
<p className={styles.description}>{info}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Button
|
||||
className={cn('talk-always-premod-user-dialog-button-cancel')}
|
||||
cStyle="white"
|
||||
onClick={onCancel}
|
||||
raised
|
||||
>
|
||||
{t('alwayspremoddialog.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className={cn('talk-always-premod-user-dialog-button-confirm')}
|
||||
cStyle="black"
|
||||
onClick={this.handlePerform}
|
||||
raised
|
||||
>
|
||||
{t('alwayspremoddialog.yes_always_premod_user')}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlwaysPremodUserDialog.propTypes = {
|
||||
open: PropTypes.bool,
|
||||
onPerform: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
username: PropTypes.string,
|
||||
info: PropTypes.string,
|
||||
};
|
||||
|
||||
export default AlwaysPremodUserDialog;
|
||||
@@ -137,6 +137,12 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.actionsMenuAlwaysPremod {
|
||||
background-color: #F0B50B;
|
||||
border-color: #F0B50B;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.actionsMenuSuspended {
|
||||
background-color: #F29336;
|
||||
border-color: #F29336;
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
isUsernameRejected,
|
||||
isUsernameChanged,
|
||||
isBanned,
|
||||
isAlwaysPremod,
|
||||
getKarma,
|
||||
} from 'coral-framework/utils/user';
|
||||
|
||||
@@ -45,6 +46,7 @@ function getUserStatusArray(user) {
|
||||
const statusMap = {
|
||||
suspended: isSuspended,
|
||||
banned: isBanned,
|
||||
alwaysPremod: isAlwaysPremod,
|
||||
usernameRejected: isUsernameRejected,
|
||||
usernameChanged: isUsernameChanged,
|
||||
};
|
||||
@@ -68,6 +70,12 @@ class UserDetail extends React.Component {
|
||||
username: this.props.root.user.username,
|
||||
});
|
||||
|
||||
showAlwaysPremodUserDialog = () =>
|
||||
this.props.showAlwaysPremodUserDialog({
|
||||
userId: this.props.root.user.id,
|
||||
username: this.props.root.user.username,
|
||||
});
|
||||
|
||||
showRejectUsernameDialog = () =>
|
||||
this.props.showRejectUsernameDialog({
|
||||
userId: this.props.root.user.id,
|
||||
@@ -107,6 +115,8 @@ class UserDetail extends React.Component {
|
||||
return t('user_detail.suspended');
|
||||
case 'banned':
|
||||
return t('user_detail.banned');
|
||||
case 'alwaysPremod':
|
||||
return t('user_detail.always_premoded');
|
||||
case 'usernameRejected':
|
||||
return (
|
||||
<span>
|
||||
@@ -153,6 +163,7 @@ class UserDetail extends React.Component {
|
||||
toggleSelectAll,
|
||||
unbanUser,
|
||||
unsuspendUser,
|
||||
removeAlwaysPremodUser,
|
||||
modal,
|
||||
acceptComment,
|
||||
rejectComment,
|
||||
@@ -169,6 +180,7 @@ class UserDetail extends React.Component {
|
||||
|
||||
const banned = isBanned(user);
|
||||
const suspended = isSuspended(user);
|
||||
const alwaysPremod = isAlwaysPremod(user);
|
||||
const usernameRejected = isUsernameRejected(user);
|
||||
const usernameChanged = isUsernameChanged(user);
|
||||
|
||||
@@ -200,6 +212,7 @@ class UserDetail extends React.Component {
|
||||
{
|
||||
[styles.actionsMenuSuspended]: suspended,
|
||||
[styles.actionsMenuBanned]: banned,
|
||||
[styles.actionsMenuAlwaysPremod]: alwaysPremod,
|
||||
},
|
||||
'talk-admin-user-detail-actions-button'
|
||||
)}
|
||||
@@ -231,6 +244,21 @@ class UserDetail extends React.Component {
|
||||
</ActionsMenuItem>
|
||||
)}
|
||||
|
||||
{alwaysPremod ? (
|
||||
<ActionsMenuItem
|
||||
onClick={() => removeAlwaysPremodUser({ id: user.id })}
|
||||
>
|
||||
{t('user_detail.remove_always_premod')}
|
||||
</ActionsMenuItem>
|
||||
) : (
|
||||
<ActionsMenuItem
|
||||
disabled={me.id === user.id}
|
||||
onClick={this.showAlwaysPremodUserDialog}
|
||||
>
|
||||
{t('user_detail.always_premod')}
|
||||
</ActionsMenuItem>
|
||||
)}
|
||||
|
||||
{usernameChanged && (
|
||||
<ActionsMenuItem onClick={this.goToReportedUsernames}>
|
||||
{t('user_detail.username_needs_approval')}
|
||||
@@ -476,8 +504,10 @@ UserDetail.propTypes = {
|
||||
showRejectUsernameDialog: PropTypes.func,
|
||||
showSuspendUserDialog: PropTypes.func,
|
||||
showBanUserDialog: PropTypes.func,
|
||||
showAlwaysPremodUserDialog: PropTypes.func,
|
||||
unbanUser: PropTypes.func.isRequired,
|
||||
unsuspendUser: PropTypes.func.isRequired,
|
||||
removeAlwaysPremodUser: PropTypes.func.isRequired,
|
||||
modal: PropTypes.bool,
|
||||
rejectUsername: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@@ -48,6 +48,10 @@ const buildActionResponse = (typename, created_at, until, status) => {
|
||||
return status
|
||||
? t('user_history.user_banned')
|
||||
: t('user_history.ban_removed');
|
||||
case 'AlwaysPremodStatusHistory':
|
||||
return status
|
||||
? t('user_history.user_always_premoded')
|
||||
: t('user_history.always_premod_removed');
|
||||
case 'SuspensionStatusHistory':
|
||||
return until
|
||||
? t('user_history.suspended', readableDuration(created_at, until))
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export const SHOW_ALWAYS_PREMOD_USER_DIALOG = 'SHOW_ALWAYS_PREMOD_USER_DIALOG';
|
||||
export const HIDE_ALWAYS_PREMOD_USER_DIALOG = 'HIDE_ALWAYS_PREMOD_USER_DIALOG';
|
||||
@@ -14,6 +14,9 @@ export const FETCH_FLAGGED_COMMENTERS_FAILURE = `${prefix}_FETCH_FLAGGED_COMMENT
|
||||
export const SHOW_BANUSER_DIALOG = `${prefix}_SHOW_BANUSER_DIALOG`;
|
||||
export const HIDE_BANUSER_DIALOG = `${prefix}_HIDE_BANUSER_DIALOG`;
|
||||
|
||||
export const SHOW_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_SHOW_ALWAYS_PREMOD_USER_DIALOG`;
|
||||
export const HIDE_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_HIDE_ALWAYS_PREMOD_USER_DIALOG`;
|
||||
|
||||
export const SHOW_REJECT_USERNAME_DIALOG = `${prefix}_SHOW_REJECT_USERNAME_DIALOG`;
|
||||
export const HIDE_REJECT_USERNAME_DIALOG = `${prefix}_HIDE_REJECT_USERNAME_DIALOG`;
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import AlwaysPremodUserDialog from '../components/AlwaysPremodUserDialog';
|
||||
import { hideAlwaysPremodUserDialog } from '../actions/alwaysPremodUserDialog';
|
||||
import { withAlwaysPremodUser } from 'coral-framework/graphql/mutations';
|
||||
import { compose } from 'react-apollo';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class AlwaysPremodUserDialogContainer extends Component {
|
||||
alwaysPremodUser = async () => {
|
||||
const { userId, alwaysPremodUser, hideAlwaysPremodUserDialog } = this.props;
|
||||
await alwaysPremodUser({ id: userId });
|
||||
hideAlwaysPremodUserDialog();
|
||||
};
|
||||
|
||||
getInfo() {
|
||||
let note = t('alwayspremoddialog.note_always_premod_user');
|
||||
return t('alwayspremoddialog.note', note);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AlwaysPremodUserDialog
|
||||
open={this.props.open}
|
||||
onPerform={this.alwaysPremodUser}
|
||||
onCancel={this.props.hideAlwaysPremodUserDialog}
|
||||
username={this.props.username}
|
||||
info={this.getInfo()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlwaysPremodUserDialogContainer.propTypes = {
|
||||
alwaysPremodUser: PropTypes.func.isRequired,
|
||||
hideAlwaysPremodUserDialog: PropTypes.func,
|
||||
open: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({
|
||||
alwaysPremodUserDialog: { open, userId, username },
|
||||
}) => ({
|
||||
open,
|
||||
userId,
|
||||
username,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
...bindActionCreators(
|
||||
{
|
||||
hideAlwaysPremodUserDialog,
|
||||
},
|
||||
dispatch
|
||||
),
|
||||
});
|
||||
|
||||
export default compose(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
),
|
||||
withAlwaysPremodUser
|
||||
)(AlwaysPremodUserDialogContainer);
|
||||
@@ -5,6 +5,7 @@ import Layout from '../components/Layout';
|
||||
import Login from '../containers/Login';
|
||||
import { FullLoading } from '../components/FullLoading';
|
||||
import BanUserDialog from './BanUserDialog';
|
||||
import AlwaysPremodUserDialog from './AlwaysPremodUserDialog';
|
||||
import SuspendUserDialog from './SuspendUserDialog';
|
||||
import RejectUsernameDialog from './RejectUsernameDialog';
|
||||
import { toggleModal as toggleShortcutModal } from '../actions/moderation';
|
||||
@@ -40,6 +41,7 @@ class LayoutContainer extends React.Component {
|
||||
toggleShortcutModal={toggleShortcutModal}
|
||||
currentUser={this.props.currentUser}
|
||||
>
|
||||
<AlwaysPremodUserDialog />
|
||||
<BanUserDialog />
|
||||
<SuspendUserDialog />
|
||||
<RejectUsernameDialog />
|
||||
|
||||
@@ -22,12 +22,14 @@ import {
|
||||
withSetCommentStatus,
|
||||
withUnbanUser,
|
||||
withUnsuspendUser,
|
||||
withRemoveAlwaysPremodUser,
|
||||
withRejectUsername,
|
||||
withPostFlag,
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
import UserDetailComment from './UserDetailComment';
|
||||
import update from 'immutability-helper';
|
||||
import { showBanUserDialog } from 'actions/banUserDialog';
|
||||
import { showAlwaysPremodUserDialog } from 'actions/alwaysPremodUserDialog';
|
||||
import { showSuspendUserDialog } from 'actions/suspendUserDialog';
|
||||
import { showRejectUsernameDialog } from 'actions/rejectUsernameDialog';
|
||||
|
||||
@@ -153,6 +155,7 @@ UserDetailContainer.propTypes = {
|
||||
selectedCommentIds: PropTypes.array,
|
||||
unbanUser: PropTypes.func.isRequired,
|
||||
unsuspendUser: PropTypes.func.isRequired,
|
||||
removeAlwaysPremodUser: PropTypes.func.isRequired,
|
||||
rejectUsername: PropTypes.func.isRequired,
|
||||
userId: PropTypes.string,
|
||||
};
|
||||
@@ -218,6 +221,17 @@ export const withUserDetailQuery = withQuery(
|
||||
created_at
|
||||
}
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
history {
|
||||
status
|
||||
assigned_by {
|
||||
id
|
||||
username
|
||||
}
|
||||
created_at
|
||||
}
|
||||
},
|
||||
username {
|
||||
status
|
||||
history {
|
||||
@@ -280,6 +294,7 @@ const mapDispatchToProps = dispatch => ({
|
||||
...bindActionCreators(
|
||||
{
|
||||
showBanUserDialog,
|
||||
showAlwaysPremodUserDialog,
|
||||
showSuspendUserDialog,
|
||||
showRejectUsernameDialog,
|
||||
changeTab,
|
||||
@@ -302,6 +317,7 @@ export default compose(
|
||||
withSetCommentStatus,
|
||||
withUnbanUser,
|
||||
withUnsuspendUser,
|
||||
withRemoveAlwaysPremodUser,
|
||||
withRejectUsername,
|
||||
withPostFlag,
|
||||
withRouter
|
||||
|
||||
@@ -10,6 +10,9 @@ const userStatusFragment = gql`
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
@@ -125,6 +128,64 @@ export default {
|
||||
});
|
||||
},
|
||||
}),
|
||||
AlwaysPremodUser: ({
|
||||
variables: {
|
||||
input: { id },
|
||||
},
|
||||
}) => ({
|
||||
update: proxy => {
|
||||
const fragmentId = `User_${id}`;
|
||||
const data = proxy.readFragment({
|
||||
fragment: userStatusFragment,
|
||||
id: fragmentId,
|
||||
});
|
||||
|
||||
const updated = update(data, {
|
||||
state: {
|
||||
status: {
|
||||
alwaysPremod: {
|
||||
status: { $set: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
proxy.writeFragment({
|
||||
fragment: userStatusFragment,
|
||||
id: fragmentId,
|
||||
data: updated,
|
||||
});
|
||||
},
|
||||
}),
|
||||
RemoveAlwaysPremodUser: ({
|
||||
variables: {
|
||||
input: { id },
|
||||
},
|
||||
}) => ({
|
||||
update: proxy => {
|
||||
const fragmentId = `User_${id}`;
|
||||
const data = proxy.readFragment({
|
||||
fragment: userStatusFragment,
|
||||
id: fragmentId,
|
||||
});
|
||||
|
||||
const updated = update(data, {
|
||||
state: {
|
||||
status: {
|
||||
alwaysPremod: {
|
||||
status: { $set: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
proxy.writeFragment({
|
||||
fragment: userStatusFragment,
|
||||
id: fragmentId,
|
||||
data: updated,
|
||||
});
|
||||
},
|
||||
}),
|
||||
BanUser: ({
|
||||
variables: {
|
||||
input: { id },
|
||||
@@ -154,6 +215,23 @@ export default {
|
||||
});
|
||||
},
|
||||
}),
|
||||
SetUserAlwaysPremodStatus: ({ variables: { status, id } }) => ({
|
||||
updateQueries: {
|
||||
TalkAdmin_Community: prev => {
|
||||
if (!status) {
|
||||
return prev;
|
||||
}
|
||||
const updated = update(prev, {
|
||||
users: {
|
||||
nodes: {
|
||||
$apply: nodes => nodes.filter(node => node.id !== id),
|
||||
},
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
},
|
||||
},
|
||||
}),
|
||||
UnbanUser: ({
|
||||
variables: {
|
||||
input: { id },
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
SHOW_ALWAYS_PREMOD_USER_DIALOG,
|
||||
HIDE_ALWAYS_PREMOD_USER_DIALOG,
|
||||
} from '../constants/alwaysPremodUserDialog';
|
||||
|
||||
const initialState = {
|
||||
open: false,
|
||||
userId: null,
|
||||
username: '',
|
||||
};
|
||||
|
||||
export default function alwaysPremodUserDialog(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case SHOW_ALWAYS_PREMOD_USER_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
open: true,
|
||||
userId: action.userId,
|
||||
username: action.username,
|
||||
};
|
||||
case HIDE_ALWAYS_PREMOD_USER_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
open: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import moderation from './moderation';
|
||||
import install from './install';
|
||||
import banUserDialog from './banUserDialog';
|
||||
import suspendUserDialog from './suspendUserDialog';
|
||||
import alwaysPremodUserDialog from './alwaysPremodUserDialog';
|
||||
import rejectUsernameDialog from './rejectUsernameDialog';
|
||||
import userDetail from './userDetail';
|
||||
import ui from './ui';
|
||||
@@ -14,6 +15,7 @@ export default {
|
||||
banUserDialog,
|
||||
configure,
|
||||
suspendUserDialog,
|
||||
alwaysPremodUserDialog,
|
||||
userDetail,
|
||||
stories,
|
||||
community,
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
SHOW_SUSPEND_USER_DIALOG,
|
||||
HIDE_SUSPEND_USER_DIALOG,
|
||||
} from '../constants/suspendUserDialog';
|
||||
import {
|
||||
SHOW_ALWAYS_PREMOD_USER_DIALOG,
|
||||
HIDE_ALWAYS_PREMOD_USER_DIALOG,
|
||||
} from '../constants/alwaysPremodUserDialog';
|
||||
|
||||
const initialState = {
|
||||
modal: false,
|
||||
@@ -23,6 +27,11 @@ export default function config(state = initialState, action) {
|
||||
...state,
|
||||
modal: true,
|
||||
};
|
||||
case SHOW_ALWAYS_PREMOD_USER_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
modal: true,
|
||||
};
|
||||
case HIDE_BAN_USER_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
@@ -33,6 +42,11 @@ export default function config(state = initialState, action) {
|
||||
...state,
|
||||
modal: false,
|
||||
};
|
||||
case HIDE_ALWAYS_PREMOD_USER_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
modal: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,12 @@ th.header:nth-child(2), th.header:nth-child(3) {
|
||||
font-size: 0.98em;
|
||||
}
|
||||
|
||||
.actionsMenuAlwaysPremod {
|
||||
background-color: #F0B50B;
|
||||
border-color: #F0B50B;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.actionsMenuSuspended {
|
||||
background-color: #F29336;
|
||||
border-color: #F29336;
|
||||
|
||||
@@ -8,7 +8,11 @@ import LoadMore from '../../../components/LoadMore';
|
||||
import PropTypes from 'prop-types';
|
||||
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
|
||||
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
|
||||
import { isSuspended, isBanned } from 'coral-framework/utils/user';
|
||||
import {
|
||||
isSuspended,
|
||||
isBanned,
|
||||
isAlwaysPremod,
|
||||
} from 'coral-framework/utils/user';
|
||||
import { RadioGroup, Radio } from 'react-mdl';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
@@ -43,6 +47,8 @@ class People extends React.Component {
|
||||
return 'Banned';
|
||||
} else if (isSuspended(user)) {
|
||||
return 'Suspended';
|
||||
} else if (isAlwaysPremod(user)) {
|
||||
return 'Always premoderated';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
@@ -55,6 +61,10 @@ class People extends React.Component {
|
||||
this.props.unbanUser(input);
|
||||
};
|
||||
|
||||
removeAlwaysPremodUser = input => {
|
||||
this.props.removeAlwaysPremodUser(input);
|
||||
};
|
||||
|
||||
showBanUserDialog = input => {
|
||||
this.props.showBanUserDialog(input);
|
||||
};
|
||||
@@ -63,6 +73,10 @@ class People extends React.Component {
|
||||
this.props.showSuspendUserDialog(input);
|
||||
};
|
||||
|
||||
showAlwaysPremodUserDialog = input => {
|
||||
this.props.showAlwaysPremodUserDialog(input);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
onFilterChange,
|
||||
@@ -109,6 +123,7 @@ class People extends React.Component {
|
||||
<Radio value="active">{t('community.active')}</Radio>
|
||||
<Radio value="suspended">{t('community.suspended')}</Radio>
|
||||
<Radio value="banned">{t('community.banned')}</Radio>
|
||||
<Radio value="alwaysPremod">{t('community.always_premod')}</Radio>
|
||||
</RadioGroup>
|
||||
<div className={styles.filterDetail}>
|
||||
{t('community.filter_role')}
|
||||
@@ -188,6 +203,9 @@ class People extends React.Component {
|
||||
user
|
||||
),
|
||||
[styles.actionsMenuBanned]: isBanned(user),
|
||||
[styles.actionsMenuAlwaysPremod]: isAlwaysPremod(
|
||||
user
|
||||
),
|
||||
},
|
||||
'talk-admin-user-detail-actions-button'
|
||||
)}
|
||||
@@ -232,6 +250,27 @@ class People extends React.Component {
|
||||
{t('modqueue.ban_user_actions')}
|
||||
</ActionsMenuItem>
|
||||
)}
|
||||
|
||||
{isAlwaysPremod(user) ? (
|
||||
<ActionsMenuItem
|
||||
onClick={() =>
|
||||
this.removeAlwaysPremodUser({ id: user.id })
|
||||
}
|
||||
>
|
||||
Remove Always Premoderate
|
||||
</ActionsMenuItem>
|
||||
) : (
|
||||
<ActionsMenuItem
|
||||
onClick={() =>
|
||||
this.showAlwaysPremodUserDialog({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('modqueue.always_premod_user_actions')}
|
||||
</ActionsMenuItem>
|
||||
)}
|
||||
</ActionsMenu>
|
||||
</td>
|
||||
<td className="mdl-data-table__cell--non-numeric">
|
||||
@@ -294,8 +333,10 @@ People.propTypes = {
|
||||
viewUserDetail: PropTypes.func.isRequired,
|
||||
unbanUser: PropTypes.func.isRequired,
|
||||
unsuspendUser: PropTypes.func.isRequired,
|
||||
removeAlwaysPremodUser: PropTypes.func.isRequired,
|
||||
showSuspendUserDialog: PropTypes.func,
|
||||
showBanUserDialog: PropTypes.func,
|
||||
showAlwaysPremodUserDialog: PropTypes.func,
|
||||
loadMore: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@ import {
|
||||
withUnbanUser,
|
||||
withUnsuspendUser,
|
||||
withSetUserRole,
|
||||
withRemoveAlwaysPremodUser,
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
import { showBanUserDialog } from 'actions/banUserDialog';
|
||||
import { showSuspendUserDialog } from 'actions/suspendUserDialog';
|
||||
import { showAlwaysPremodUserDialog } from 'actions/alwaysPremodUserDialog';
|
||||
import { viewUserDetail } from '../../../actions/userDetail';
|
||||
import { appendNewNodes } from 'plugin-api/beta/client/utils';
|
||||
import update from 'immutability-helper';
|
||||
@@ -30,6 +32,7 @@ class PeopleContainer extends React.PureComponent {
|
||||
active: { suspended: false, banned: false },
|
||||
suspended: { suspended: true },
|
||||
banned: { banned: true },
|
||||
alwaysPremod: { alwaysPremod: true },
|
||||
};
|
||||
|
||||
onFilterChange = filter => e =>
|
||||
@@ -40,7 +43,6 @@ class PeopleContainer extends React.PureComponent {
|
||||
|
||||
getFilterState = () => {
|
||||
const { role, status } = this.state;
|
||||
|
||||
return {
|
||||
status: this.statusToQuery[status] || null,
|
||||
role: role || null,
|
||||
@@ -106,8 +108,10 @@ class PeopleContainer extends React.PureComponent {
|
||||
setUserRole={this.props.setUserRole}
|
||||
showSuspendUserDialog={this.props.showSuspendUserDialog}
|
||||
showBanUserDialog={this.props.showBanUserDialog}
|
||||
showAlwaysPremodUserDialog={this.props.showAlwaysPremodUserDialog}
|
||||
unbanUser={this.props.unbanUser}
|
||||
unsuspendUser={this.props.unsuspendUser}
|
||||
removeAlwaysPremodUser={this.props.removeAlwaysPremodUser}
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
users={this.props.root.users}
|
||||
@@ -122,9 +126,11 @@ PeopleContainer.propTypes = {
|
||||
setUserRole: PropTypes.func.isRequired,
|
||||
unbanUser: PropTypes.func.isRequired,
|
||||
unsuspendUser: PropTypes.func.isRequired,
|
||||
removeAlwaysPremodUser: PropTypes.func.isRequired,
|
||||
viewUserDetail: PropTypes.func.isRequired,
|
||||
showSuspendUserDialog: PropTypes.func,
|
||||
showBanUserDialog: PropTypes.func,
|
||||
showAlwaysPremodUserDialog: PropTypes.func,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
};
|
||||
@@ -153,6 +159,9 @@ const LOAD_MORE_QUERY = gql`
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
@@ -187,6 +196,9 @@ const FILTER_QUERY = gql`
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
@@ -203,6 +215,7 @@ const mapDispatchToProps = dispatch =>
|
||||
viewUserDetail,
|
||||
showSuspendUserDialog,
|
||||
showBanUserDialog,
|
||||
showAlwaysPremodUserDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
@@ -215,6 +228,7 @@ export default compose(
|
||||
withSetUserRole,
|
||||
withUnsuspendUser,
|
||||
withUnbanUser,
|
||||
withRemoveAlwaysPremodUser,
|
||||
withQuery(
|
||||
gql`
|
||||
query TalkAdmin_Community_People {
|
||||
@@ -236,6 +250,9 @@ export default compose(
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
|
||||
@@ -175,6 +175,9 @@ const USER_BANNED_SUBSCRIPTION = gql`
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
@@ -196,6 +199,9 @@ const USER_SUSPENDED_SUBSCRIPTION = gql`
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
@@ -217,6 +223,9 @@ const USERNAME_REJECTED_SUBSCRIPTION = gql`
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
@@ -253,6 +262,9 @@ const EMBED_QUERY = gql`
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
|
||||
@@ -398,6 +398,9 @@ const fragments = {
|
||||
banned {
|
||||
status
|
||||
}
|
||||
alwaysPremod {
|
||||
status
|
||||
}
|
||||
suspension {
|
||||
until
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createDefaultResponseFragments } from '../utils';
|
||||
// fragments defined here are automatically registered.
|
||||
export default {
|
||||
...createDefaultResponseFragments(
|
||||
'AlwaysPremodUserResponse',
|
||||
'BanUsersResponse',
|
||||
'ChangeUsernameResponse',
|
||||
'CloseAssetResponse',
|
||||
@@ -14,6 +15,7 @@ export default {
|
||||
'IgnoreUserResponse',
|
||||
'ModifyTagResponse',
|
||||
'PostFlagResponse',
|
||||
'RemoveAlwaysPremodUserResponse',
|
||||
'SetCommentStatusResponse',
|
||||
'SetUsernameResponse',
|
||||
'SetUsernameStatusResponse',
|
||||
|
||||
@@ -412,6 +412,48 @@ export const withSetUsername = withMutation(
|
||||
}
|
||||
);
|
||||
|
||||
export const withAlwaysPremodUser = withMutation(
|
||||
gql`
|
||||
mutation AlwaysPremodUser($input: AlwaysPremodUserInput!) {
|
||||
alwaysPremodUser(input: $input) {
|
||||
...AlwaysPremodUserResponse
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({ mutate }) => ({
|
||||
alwaysPremodUser: input => {
|
||||
return mutate({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export const withRemoveAlwaysPremodUser = withMutation(
|
||||
gql`
|
||||
mutation RemoveAlwaysPremodUser($input: RemoveAlwaysPremodUserInput!) {
|
||||
removeAlwaysPremodUser(input: $input) {
|
||||
...RemoveAlwaysPremodUserResponse
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({ mutate }) => ({
|
||||
removeAlwaysPremodUser: input => {
|
||||
return mutate({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
export const withBanUser = withMutation(
|
||||
gql`
|
||||
mutation BanUser($input: BanUserInput!) {
|
||||
|
||||
@@ -35,6 +35,15 @@ export const isBanned = user => {
|
||||
return get(user, 'state.status.banned.status');
|
||||
};
|
||||
|
||||
/**
|
||||
* isAlwaysPremod
|
||||
* retrieves boolean based on the user premod status
|
||||
*/
|
||||
|
||||
export const isAlwaysPremod = user => {
|
||||
return get(user, 'state.status.alwaysPremod.status');
|
||||
};
|
||||
|
||||
/**
|
||||
* isUsernameRejected
|
||||
* retrieves boolean based on the username status
|
||||
|
||||
@@ -11,7 +11,7 @@ const mergeState = (query, state) => {
|
||||
}
|
||||
|
||||
if (status) {
|
||||
const { username, banned, suspended } = status;
|
||||
const { username, banned, suspended, alwaysPremod } = status;
|
||||
|
||||
if (typeof username !== 'undefined' && username && username.length > 0) {
|
||||
query.merge({
|
||||
@@ -27,6 +27,12 @@ const mergeState = (query, state) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof alwaysPremod !== 'undefined' && alwaysPremod !== null) {
|
||||
query.merge({
|
||||
'status.alwaysPremod.status': alwaysPremod,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof suspended !== 'undefined' && suspended !== null) {
|
||||
if (suspended) {
|
||||
query.merge({
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
SET_USERNAME,
|
||||
SET_USER_USERNAME_STATUS,
|
||||
SET_USER_BAN_STATUS,
|
||||
SET_USER_ALWAYS_PREMOD_STATUS,
|
||||
SET_USER_SUSPENSION_STATUS,
|
||||
UPDATE_USER_ROLES,
|
||||
DELETE_OTHER_USER,
|
||||
@@ -32,6 +33,13 @@ const setUserBanStatus = async (ctx, id, status = false, message = null) => {
|
||||
}
|
||||
};
|
||||
|
||||
const setUserAlwaysPremodStatus = async (ctx, id, status = false) => {
|
||||
const user = await Users.setAlwaysPremodStatus(id, status, ctx.user.id);
|
||||
if (user.alwaysPremod) {
|
||||
ctx.pubsub.publish('userAlwaysPremod', user);
|
||||
}
|
||||
};
|
||||
|
||||
const setUserSuspensionStatus = async (
|
||||
ctx,
|
||||
id,
|
||||
@@ -220,6 +228,7 @@ module.exports = ctx => {
|
||||
setRole: () => Promise.reject(new ErrNotAuthorized()),
|
||||
setUserBanStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||
setUserSuspensionStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||
setUserAlwaysPremodStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||
setUserUsernameStatus: () => Promise.reject(new ErrNotAuthorized()),
|
||||
setUsername: () => Promise.reject(new ErrNotAuthorized()),
|
||||
stopIgnoringUser: () => Promise.reject(new ErrNotAuthorized()),
|
||||
@@ -256,6 +265,11 @@ module.exports = ctx => {
|
||||
setUserBanStatus(ctx, id, status, message);
|
||||
}
|
||||
|
||||
if (ctx.user.can(SET_USER_ALWAYS_PREMOD_STATUS)) {
|
||||
mutators.User.setUserAlwaysPremodStatus = (id, status) =>
|
||||
setUserAlwaysPremodStatus(ctx, id, status);
|
||||
}
|
||||
|
||||
if (ctx.user.can(SET_USER_SUSPENSION_STATUS)) {
|
||||
mutators.User.setUserSuspensionStatus = (id, until, message) =>
|
||||
setUserSuspensionStatus(ctx, id, until, message);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
const { decorateUserField } = require('./util');
|
||||
|
||||
const AlwaysPremodStatusHistory = {};
|
||||
|
||||
decorateUserField(AlwaysPremodStatusHistory, 'assigned_by');
|
||||
|
||||
module.exports = AlwaysPremodStatusHistory;
|
||||
@@ -3,6 +3,7 @@ const debug = require('debug')('talk:graph:resolvers');
|
||||
|
||||
const Action = require('./action');
|
||||
const ActionSummary = require('./action_summary');
|
||||
const AlwaysPremodStatusHistory = require('./always_premod_status_history');
|
||||
const Asset = require('./asset');
|
||||
const AssetActionSummary = require('./asset_action_summary');
|
||||
const BannedStatusHistory = require('./banned_status_history');
|
||||
@@ -37,6 +38,7 @@ const plugins = require('../../services/plugins');
|
||||
let resolvers = {
|
||||
Action,
|
||||
ActionSummary,
|
||||
AlwaysPremodStatusHistory,
|
||||
Asset,
|
||||
AssetActionSummary,
|
||||
BannedStatusHistory,
|
||||
|
||||
@@ -74,6 +74,16 @@ const RootMutation = {
|
||||
unbanUser: async (obj, { input: { id } }, { mutators: { User } }) => {
|
||||
await User.setUserBanStatus(id, false);
|
||||
},
|
||||
alwaysPremodUser: async (obj, { input: { id } }, { mutators: { User } }) => {
|
||||
await User.setUserAlwaysPremodStatus(id, true);
|
||||
},
|
||||
removeAlwaysPremodUser: async (
|
||||
obj,
|
||||
{ input: { id } },
|
||||
{ mutators: { User } }
|
||||
) => {
|
||||
await User.setUserAlwaysPremodStatus(id, false);
|
||||
},
|
||||
ignoreUser: async (_, { id }, { mutators: { User } }) => {
|
||||
await User.ignoreUser({ id });
|
||||
},
|
||||
|
||||
@@ -142,6 +142,10 @@ input UserStatusInput {
|
||||
# suspended will restrict the returned users to only those that are, or are not
|
||||
# suspended. If not provided, no filtering will be performed.
|
||||
suspended: Boolean
|
||||
|
||||
# alwaysPremod will restrict the returned users to only those that are, or are not
|
||||
# marked as always premoderate. If not provided, no filtering will be performed.
|
||||
alwaysPremod: Boolean
|
||||
}
|
||||
|
||||
type UsernameStatusHistory {
|
||||
@@ -166,6 +170,17 @@ type BannedStatus {
|
||||
history: [BannedStatusHistory!]
|
||||
}
|
||||
|
||||
type AlwaysPremodStatusHistory {
|
||||
status: Boolean!
|
||||
assigned_by: User
|
||||
created_at: Date!
|
||||
}
|
||||
|
||||
type AlwaysPremodStatus {
|
||||
status: Boolean!
|
||||
history: [AlwaysPremodStatusHistory!]
|
||||
}
|
||||
|
||||
type SuspensionStatusHistory {
|
||||
until: Date
|
||||
assigned_by: User
|
||||
@@ -184,6 +199,9 @@ type UserStatus {
|
||||
# banned is the bool that determines if the user is banned or not.
|
||||
banned: BannedStatus!
|
||||
|
||||
# alwaysPremod is the bool that determines if the user comments are always pushed to the premod queue or not
|
||||
alwaysPremod: AlwaysPremodStatus!
|
||||
|
||||
# suspension is the date that the user is suspended until.
|
||||
suspension: SuspensionStatus!
|
||||
}
|
||||
@@ -1446,6 +1464,22 @@ type UnbanUserResponse implements Response {
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
input AlwaysPremodUserInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
input RemoveAlwaysPremodUserInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
type AlwaysPremodUserResponse implements Response {
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
type RemoveAlwaysPremodUserResponse implements Response {
|
||||
errors: [UserError!]
|
||||
}
|
||||
|
||||
input SuspendUserInput {
|
||||
id: ID!
|
||||
message: String!
|
||||
@@ -1545,6 +1579,14 @@ type RootMutation {
|
||||
# Mutation is restricted.
|
||||
unsuspendUser(input: UnsuspendUserInput!): UnsuspendUserResponse
|
||||
|
||||
# Sets the always premod status on a given user. Requires the `MODERATOR` role.
|
||||
# Mutation is restricted.
|
||||
alwaysPremodUser(input: AlwaysPremodUserInput!): AlwaysPremodUserResponse
|
||||
|
||||
# Sets the always premod status on a given user. Requires the `MODERATOR` role.
|
||||
# Mutation is restricted.
|
||||
removeAlwaysPremodUser(input: RemoveAlwaysPremodUserInput!): RemoveAlwaysPremodUserResponse
|
||||
|
||||
# Sets the ban status on a given user. Requires the `MODERATOR` role.
|
||||
# Mutation is restricted.
|
||||
banUser(input: BanUserInput!): BanUsersResponse
|
||||
|
||||
@@ -3,6 +3,13 @@ en:
|
||||
sort_comments: 'Sort Comments'
|
||||
view_options: 'View Options'
|
||||
already_flagged_username: 'You have already flagged this username.'
|
||||
alwayspremoddialog:
|
||||
are_you_sure: 'Are you sure you would like to always premoderate {0}?'
|
||||
always_premod_user: 'Always premoderate user?'
|
||||
cancel: Cancel
|
||||
note: 'Note: {0}'
|
||||
note_always_premod_user: 'Always premoderating this user will place all of their comments in the Pre-Moderate queue.'
|
||||
yes_always_premod_user: 'Yes, always premoderate user'
|
||||
bandialog:
|
||||
are_you_sure: 'Are you sure you would like to ban {0}?'
|
||||
ban_user: 'Ban User?'
|
||||
@@ -64,6 +71,7 @@ en:
|
||||
admin: Administrator
|
||||
ads_marketing: 'This looks like an ad/marketing'
|
||||
all: 'All'
|
||||
always_premod: 'Always premoderated'
|
||||
are_you_sure: 'Are you sure you would like to ban {0}?'
|
||||
ban_user: 'Ban User?'
|
||||
banned: Banned
|
||||
@@ -382,6 +390,7 @@ en:
|
||||
actions: Actions
|
||||
all: all
|
||||
all_streams: 'All Streams'
|
||||
always_premod_user_actions: 'Always Premod User'
|
||||
approve: Approve
|
||||
approved: Approved
|
||||
ban_user: Ban
|
||||
@@ -528,6 +537,8 @@ en:
|
||||
username_flags: 'flags for this username'
|
||||
user_detail:
|
||||
all: All
|
||||
always_premod: 'Always Premod User'
|
||||
always_premoded: 'Always premoderated'
|
||||
ban: 'Ban User'
|
||||
banned: Banned
|
||||
email: Email
|
||||
@@ -539,6 +550,7 @@ en:
|
||||
reject_rate: 'Reject Rate'
|
||||
reject_username: 'Reject Username'
|
||||
rejected: Rejected
|
||||
remove_always_premod: 'Remove Always Premoderate'
|
||||
remove_ban: 'Remove Ban'
|
||||
remove_suspension: 'Remove Suspension'
|
||||
suspend: 'Suspend User'
|
||||
@@ -552,12 +564,14 @@ en:
|
||||
username_rejected: 'Username rejected'
|
||||
user_history:
|
||||
action: Action
|
||||
always_premod_removed: 'Always premoderate removed'
|
||||
ban_removed: 'Ban removed'
|
||||
date: Date
|
||||
suspended: 'Suspended, {0}'
|
||||
suspension_removed: 'Suspension removed'
|
||||
system: System
|
||||
taken_by: 'Taken By'
|
||||
user_always_premoded: 'User always premoderated'
|
||||
user_banned: 'User banned'
|
||||
username_status: 'Username {0}'
|
||||
user_impersonating: 'This user is impersonating'
|
||||
|
||||
@@ -3,6 +3,13 @@ es:
|
||||
sort_comments: 'Ordenar comentarios'
|
||||
view_options: 'Ver opciones'
|
||||
already_flagged_username: 'Usted ya reportó a este usuario.'
|
||||
alwayspremoddialog:
|
||||
are_you_sure: '¿Estás seguro que quieres pre-moderar a {0}?'
|
||||
always_premod_user: '¿Pre-moderar al Usuario?'
|
||||
cancel: Cancelar
|
||||
note: 'Nota: {0}'
|
||||
note_always_premod_user: 'Pre-moderar a un usuario pondrá todos sus comentarios en la fila de Pre-Moderación.'
|
||||
yes_always_premod_user: 'Sí, pre-moderar al usuario.'
|
||||
bandialog:
|
||||
are_you_sure: '¿Estás seguro que quieres suspender a {0}?'
|
||||
ban_user: '¿Quieres suspender al Usuario?'
|
||||
@@ -62,6 +69,7 @@ es:
|
||||
admin: Administrator
|
||||
ads_marketing: 'Esto parece ser un publicidad/acción de comercialización'
|
||||
all: 'Todos'
|
||||
always_premod: 'Pre-moderado'
|
||||
are_you_sure: '¿Estás seguro que quieres suspender a {0}?'
|
||||
ban_user: '¿Quieres suspender al Usuario?'
|
||||
banned: Suspendido
|
||||
@@ -355,6 +363,7 @@ es:
|
||||
actions: Acciones
|
||||
all: todos
|
||||
all_streams: 'Todos los Hilos'
|
||||
always_premod_user_actions: 'Siempre Pre-moderar Usuario'
|
||||
approve: Aprobar
|
||||
approved: Aprobado
|
||||
ban_user: Bloquear
|
||||
@@ -486,12 +495,15 @@ es:
|
||||
username_flags: 'reportes para este nombre de usuario'
|
||||
user_detail:
|
||||
all: Todos
|
||||
always_premod: 'Siempre pre-moderar usuario'
|
||||
always_premoded: 'Siempre pre-moderado'
|
||||
ban: 'Bloquear usuario'
|
||||
banned: Baneado
|
||||
email: 'Correo electrónico'
|
||||
member_since: 'Miembro desde'
|
||||
reject_rate: 'Promedio de rechazo'
|
||||
rejected: Rechazado
|
||||
remove_always_premod: 'Cancelar siempre pre-moderar'
|
||||
remove_ban: 'Cancelar bloqueo'
|
||||
remove_suspension: 'Cancelar suspensión'
|
||||
suspend: 'Suspender usuario'
|
||||
@@ -503,12 +515,14 @@ es:
|
||||
username_rejected: 'Usuario rechazado'
|
||||
user_history:
|
||||
action: Acción
|
||||
always_premod_removed: 'Siempre pre-moderar cancelado'
|
||||
ban_removed: 'Bloqueo cancelado'
|
||||
date: Fecha
|
||||
suspended: 'Suspendido, {0}'
|
||||
suspension_removed: 'Suspensión cancelada'
|
||||
system: Sistema
|
||||
taken_by: 'Está en uso'
|
||||
user_always_premoded: 'Usuario siempre pre-moderado'
|
||||
user_banned: 'Usuario bloqueado'
|
||||
username_status: 'Nombre de usuario {0}'
|
||||
user_impersonating: 'Este usuario suplanta a alguien'
|
||||
|
||||
@@ -180,6 +180,30 @@ const User = new Schema(
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// alwaysPremod stores the current user premod status as well as the history of
|
||||
// changes.
|
||||
alwaysPremod: {
|
||||
// Status stores the current user premod status.
|
||||
status: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
index: true,
|
||||
},
|
||||
history: [
|
||||
{
|
||||
// Status stores the historical premod status.
|
||||
status: Boolean,
|
||||
|
||||
// assigned_by stores the user id of the user who assigned this status.
|
||||
assigned_by: { type: String, default: null },
|
||||
|
||||
// created_at stores the date when this status was assigned.
|
||||
created_at: { type: Date, default: Date.now },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// IgnoresUsers is an array of user id's that the current user is ignoring.
|
||||
@@ -359,6 +383,27 @@ User.virtual('banned')
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* alwaysPremod returns true when the user is currently in always premod, and sets the alwaysPremod
|
||||
* status locally.
|
||||
*/
|
||||
User.virtual('alwaysPremod')
|
||||
.get(function() {
|
||||
return this.status.alwaysPremod.status;
|
||||
})
|
||||
.set(function(status) {
|
||||
this.status.alwaysPremod.status = status;
|
||||
|
||||
if (!this.status.alwaysPremod.history) {
|
||||
this.status.alwaysPremod.history = [];
|
||||
}
|
||||
|
||||
this.status.alwaysPremod.history.push({
|
||||
status,
|
||||
created_at: new Date(),
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* suspended returns true when the user is currently suspended, and sets the
|
||||
* suspension status locally.
|
||||
|
||||
@@ -7,6 +7,7 @@ module.exports = {
|
||||
EDIT_COMMENT: 'EDIT_COMMENT',
|
||||
SET_USER_USERNAME_STATUS: 'SET_USER_USERNAME_STATUS',
|
||||
SET_USER_BAN_STATUS: 'SET_USER_BAN_STATUS',
|
||||
SET_USER_ALWAYS_PREMOD_STATUS: 'SET_USER_ALWAYS_PREMOD_STATUS',
|
||||
SET_USER_SUSPENSION_STATUS: 'SET_USER_SUSPENSION_STATUS',
|
||||
SET_COMMENT_STATUS: 'SET_COMMENT_STATUS',
|
||||
ADD_COMMENT_TAG: 'ADD_COMMENT_TAG',
|
||||
|
||||
@@ -47,6 +47,7 @@ module.exports = (user, perm) => {
|
||||
case types.SET_USER_USERNAME_STATUS:
|
||||
case types.SET_USER_BAN_STATUS:
|
||||
case types.SET_USER_SUSPENSION_STATUS:
|
||||
case types.SET_USER_ALWAYS_PREMOD_STATUS:
|
||||
case types.UPDATE_ASSET_SETTINGS:
|
||||
case types.UPDATE_ASSET_STATUS:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
|
||||
@@ -9,9 +9,9 @@ module.exports = (
|
||||
},
|
||||
}
|
||||
) => {
|
||||
// If the settings say that we're in premod mode, then the comment is in
|
||||
// premod status.
|
||||
if (moderation === 'PRE') {
|
||||
// If the settings say that we're in premod mode, or the user is flagged as
|
||||
// always premod, then the comment is in premod status.
|
||||
if (moderation === 'PRE' || ctx.user.status.alwaysPremod.status === true) {
|
||||
return {
|
||||
status: 'PREMOD',
|
||||
};
|
||||
|
||||
@@ -207,6 +207,50 @@ class Users {
|
||||
return user;
|
||||
}
|
||||
|
||||
static async setAlwaysPremodStatus(id, status, assignedBy = null) {
|
||||
let user = await User.findOneAndUpdate(
|
||||
{
|
||||
id,
|
||||
'status.alwaysPremod.status': {
|
||||
$ne: status,
|
||||
},
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
'status.alwaysPremod.status': status,
|
||||
},
|
||||
$push: {
|
||||
'status.alwaysPremod.history': {
|
||||
status,
|
||||
assigned_by: assignedBy,
|
||||
created_at: Date.now(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
new: true,
|
||||
runValidators: true,
|
||||
}
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
user = await User.findOne({ id });
|
||||
if (!user) {
|
||||
throw new ErrNotFound();
|
||||
}
|
||||
|
||||
if (user.status.alwaysPremod.status === status) {
|
||||
return user;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'always premod status change edit failed for an unknown reason'
|
||||
);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
static async setBanStatus(id, status, assignedBy = null, message) {
|
||||
let user = await User.findOneAndUpdate(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user