Merge branch 'master' into issue-676-set-domain-whitelist

This commit is contained in:
Kiwi
2017-06-24 00:22:34 +07:00
committed by GitHub
45 changed files with 469 additions and 568 deletions
@@ -0,0 +1,7 @@
import {SHOW_BAN_USER_DIALOG, HIDE_BAN_USER_DIALOG} from '../constants/banUserDialog';
export const showBanUserDialog = ({userId, username, commentId, commentStatus}) =>
({type: SHOW_BAN_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideBanUserDialog = () => ({type: HIDE_BAN_USER_DIALOG});
+5 -5
View File
@@ -10,8 +10,8 @@ import {
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/request';
@@ -69,6 +69,6 @@ export const setCommenterStatus = (id, status) => (dispatch) => {
export const showBanUserDialog = (user) => ({type: SHOW_BANUSER_DIALOG, user});
export const hideBanUserDialog = () => ({type: HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (user) => ({type: SHOW_SUSPENDUSER_DIALOG, user});
export const hideSuspendUserDialog = () => ({type: HIDE_SUSPENDUSER_DIALOG});
// Reject Username Dialog
export const showRejectUsernameDialog = (user) => ({type: SHOW_REJECT_USERNAME_DIALOG, user});
export const hideRejectUsernameDialog = () => ({type: HIDE_REJECT_USERNAME_DIALOG});
@@ -3,16 +3,6 @@ import * as actions from 'constants/moderation';
export const toggleModal = (open) => ({type: actions.TOGGLE_MODAL, open});
export const singleView = () => ({type: actions.SINGLE_VIEW});
// Ban User Dialog
export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote});
export const hideBanUserDialog = () => ({type: actions.HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (userId, username, commentId, commentStatus) =>
({type: actions.SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideSuspendUserDialog = () => ({type: actions.HIDE_SUSPEND_USER_DIALOG});
// hide shortcuts note
export const hideShortcutsNote = () => {
try {
@@ -0,0 +1,7 @@
import {SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG} from '../constants/suspendUserDialog.js';
export const showSuspendUserDialog = ({userId, username, commentId, commentStatus}) =>
({type: SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideSuspendUserDialog = () => ({type: HIDE_SUSPEND_USER_DIALOG});
@@ -0,0 +1,44 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
const BanUserDialog = ({open, onCancel, onPerform, username, info}) => (
<Dialog
className={styles.dialog}
id="banUserDialog"
open={open}
onCancel={onCancel}
title={t('bandialog.ban_user')}>
<span className={styles.close} onClick={onCancel}>×</span>
<div>
<div className={styles.header}>
<h2>{t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('bandialog.are_you_sure', username)}</h3>
<i>{info}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={onCancel} raised>
{t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onPerform} raised>
{t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
open: PropTypes.bool,
onPerform: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
username: PropTypes.string,
info: PropTypes.string,
};
export default BanUserDialog;
@@ -8,8 +8,8 @@ import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
const menuOptionsMap = {
'reject': {status: 'REJECTED', icon: 'close', key: 'r'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 't'},
'reject': {status: 'REJECTED', icon: 'close', key: 'f'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 'd'},
'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
'ban': {status: 'BANNED', icon: 'not interested'}
};
@@ -49,7 +49,6 @@ class SuspendUserDialog extends React.Component {
handlePerform = () => {
this.props.onPerform({
id: this.props.userId,
message: this.state.message,
// Add 1 minute more to help `timeago.js` to display the correct duration.
@@ -153,11 +152,10 @@ class SuspendUserDialog extends React.Component {
SuspendUserDialog.propTypes = {
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onPerform: PropTypes.func.isRequired,
username: PropTypes.string,
userId: PropTypes.string,
onCancel: PropTypes.func.isRequired,
organizationName: PropTypes.string,
username: PropTypes.string,
};
export default SuspendUserDialog;
@@ -0,0 +1,2 @@
export const SHOW_BAN_USER_DIALOG = 'SHOW_BAN_USER_DIALOG';
export const HIDE_BAN_USER_DIALOG = 'HIDE_BAN_USER_DIALOG';
@@ -13,5 +13,5 @@ export const FETCH_FLAGGED_COMMENTERS_FAILURE = 'FETCH_FLAGGED_COMMENTERS_FAILUR
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const SHOW_SUSPENDUSER_DIALOG = 'SHOW_SUSPENDUSER_DIALOG';
export const HIDE_SUSPENDUSER_DIALOG = 'HIDE_SUSPENDUSER_DIALOG';
export const SHOW_REJECT_USERNAME_DIALOG = 'SHOW_REJECT_USERNAME_DIALOG';
export const HIDE_REJECT_USERNAME_DIALOG = 'HIDE_REJECT_USERNAME_DIALOG';
@@ -1,10 +1,6 @@
export const TOGGLE_MODAL = 'TOGGLE_MODAL';
export const SINGLE_VIEW = 'SINGLE_VIEW';
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const HIDE_SHORTCUTS_NOTE = 'HIDE_SHORTCUTS_NOTE';
export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
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';
@@ -0,0 +1,2 @@
export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
@@ -0,0 +1,63 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import BanUserDialog from '../components/BanUserDialog';
import {hideBanUserDialog} from '../actions/banUserDialog';
import {withSetUserStatus, withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {compose} from 'react-apollo';
import t from 'coral-framework/services/i18n';
class BanUserDialogContainer extends Component {
banUser = async () => {
const {userId, commentId, commentStatus, setUserStatus, setCommentStatus, hideBanUserDialog} = this.props;
await setUserStatus({userId, status: 'BANNED'});
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({commentId, status: 'REJECTED'});
}
}
getInfo() {
let note = t('bandialog.note_ban_user');
if (this.props.commentStatus && this.props.commentStatus !== 'REJECTED') {
note = t('bandialog.note_reject_comment');
}
return t('bandialog.note', note);
}
render() {
return (
<BanUserDialog
open={this.props.open}
onPerform={this.banUser}
onCancel={this.props.hideBanUserDialog}
username={this.props.username}
info={this.getInfo()}
/>
);
}
}
const mapStateToProps = ({banUserDialog: {open, userId, username, commentId, commentStatus}}) => ({
open,
userId,
username,
commentId,
commentStatus,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideBanUserDialog,
}, dispatch),
});
export default compose(
withSetUserStatus,
withSetCommentStatus,
connect(
mapStateToProps,
mapDispatchToProps,
),
)(BanUserDialogContainer);
+7 -1
View File
@@ -5,6 +5,8 @@ import {fetchConfig} from '../actions/config';
import AdminLogin from '../components/AdminLogin';
import {logout} from 'coral-framework/actions/auth';
import {FullLoading} from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import {toggleModal as toggleShortcutModal} from '../actions/moderation';
import {checkLogin, handleLogin, requestPasswordReset} from '../actions/auth';
import {can} from 'coral-framework/services/perms';
@@ -52,7 +54,11 @@ class LayoutContainer extends Component {
handleLogout={handleLogout}
toggleShortcutModal={toggleShortcutModal}
{...this.props}
/>
>
<BanUserDialog />
<SuspendUserDialog />
{this.props.children}
</Layout>
);
} else if (loggedIn) {
return (
@@ -0,0 +1,83 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import SuspendUserDialog from '../components/SuspendUserDialog';
import {hideSuspendUserDialog} from '../actions/suspendUserDialog';
import {withSetCommentStatus, withSuspendUser} from 'coral-framework/graphql/mutations';
import {compose, gql} from 'react-apollo';
import * as notification from 'coral-admin/src/services/notification';
import t, {timeago} from 'coral-framework/services/i18n';
import withQuery from 'coral-framework/hocs/withQuery';
import get from 'lodash/get';
class SuspendUserDialogContainer extends Component {
suspendUser = async ({message, until}) => {
const {userId, username, commentStatus, commentId, hideSuspendUserDialog, setCommentStatus, suspendUser} = this.props;
hideSuspendUserDialog();
try {
const result = await suspendUser({id: userId, message, until});
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
t('suspenduser.notify_suspend_until', username, timeago(until)),
);
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
return setCommentStatus({commentId, status: 'REJECTED'})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
}
}
catch(err) {
notification.showMutationErrors(err);
}
};
render() {
return (
<SuspendUserDialog
open={this.props.open}
onPerform={this.suspendUser}
onCancel={this.props.hideSuspendUserDialog}
organizationName={get(this.props, 'root.settings.organizationName')}
username={this.props.username}
/>
);
}
}
const withOrganizationName = withQuery(gql`
query CoralAdmin_SuspendUserDialog {
settings {
organizationName
}
}
`);
const mapStateToProps = ({suspendUserDialog: {open, userId, username, commentId, commentStatus}}) => ({
open,
userId,
username,
commentId,
commentStatus,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideSuspendUserDialog,
}, dispatch),
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps,
),
withSuspendUser,
withSetCommentStatus,
withOrganizationName,
)(SuspendUserDialogContainer);
@@ -0,0 +1,30 @@
import {SHOW_BAN_USER_DIALOG, HIDE_BAN_USER_DIALOG} from '../constants/banUserDialog';
const initialState = {
open: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
};
export default function banUserDialog(state = initialState, action) {
switch (action.type) {
case SHOW_BAN_USER_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
};
case HIDE_BAN_USER_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
+7 -7
View File
@@ -9,8 +9,8 @@ import {
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
const initialState = Map({
@@ -24,7 +24,7 @@ const initialState = Map({
pagePeople: 0,
user: Map({}),
banDialog: false,
suspendDialog: false
rejectUsernameDialog: false
});
export default function community (state = initialState, action) {
@@ -79,14 +79,14 @@ export default function community (state = initialState, action) {
user: Map(action.user),
banDialog: true
});
case HIDE_SUSPENDUSER_DIALOG:
case HIDE_REJECT_USERNAME_DIALOG:
return state
.set('suspendDialog', false);
case SHOW_SUSPENDUSER_DIALOG:
.set('rejectUsernameDialog', false);
case SHOW_REJECT_USERNAME_DIALOG:
return state
.merge({
user: Map(action.user),
suspendDialog: true
rejectUsernameDialog: true
});
default :
return state;
+4
View File
@@ -5,9 +5,13 @@ import community from './community';
import moderation from './moderation';
import install from './install';
import config from './config';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
export default {
auth,
banUserDialog,
suspendUserDialog,
assets,
settings,
community,
+1 -39
View File
@@ -1,61 +1,23 @@
import {fromJS, Map, Set} from 'immutable';
import {fromJS, Set} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = fromJS({
singleView: false,
modalOpen: false,
user: {},
commentId: null,
commentStatus: null,
userDetailId: null,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
userDetailSelectedIds: new Set(),
banDialog: false,
storySearchVisible: false,
storySearchString: '',
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
suspendUserDialog: {
show: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
},
});
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.MODERATION_CLEAR_STATE:
return initialState;
case actions.HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false)
.set('commentStatus', null);
case actions.SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
commentId: action.commentId,
commentStatus: action.commentStatus,
showRejectedNote: action.showRejectedNote,
banDialog: true
});
case actions.SHOW_SUSPEND_USER_DIALOG:
return state
.mergeDeep({
suspendUserDialog: {
show: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
}
});
case actions.HIDE_SUSPEND_USER_DIALOG:
return state
.setIn(['suspendUserDialog', 'show'], false);
case actions.SET_ACTIVE_TAB:
return state
.set('activeTab', action.activeTab);
@@ -0,0 +1,30 @@
import {SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG} from '../constants/suspendUserDialog';
const initialState = {
open: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
};
export default function suspendUserDialog(state = initialState, action) {
switch (action.type) {
case SHOW_SUSPEND_USER_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
};
case HIDE_SUSPEND_USER_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
@@ -1,23 +1,18 @@
import React from 'react';
import styles from './Community.css';
import BanUserButton from './BanUserButton';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton icon='not interested' user={user} onClick={() => props.showBanUserDialog(user)} />;
}
return (
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={() => {
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showSuspendUserDialog({user: user});
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showRejectUsernameDialog({user: user});
}}
>{t(`modqueue.${menuActionsMap[type].text}`)}</Button>
);
@@ -1,11 +0,0 @@
.banButton {
-webkit-transform: scale(.8);
transform: scale(.8);
margin: 0;
i {
vertical-align: middle;
margin-right: 5px;
font-size: 14px;
}
}
@@ -1,24 +0,0 @@
import React, {PropTypes} from 'react';
import {Button, Icon} from 'coral-ui';
import styles from './BanUserButton.css';
import t from 'coral-framework/services/i18n';
const BanUserButton = ({user, ...props}) => (
<div className={styles.ban}>
<Button cStyle='ban'
className={`ban ${styles.banButton}`}
disabled={user.status === 'BANNED' ? 'disabled' : ''}
onClick={props.onClick}
raised>
<Icon name='not_interested' />
{t('comment.ban_user')}
</Button>
</div>
);
BanUserButton.propTypes = {
onClick: PropTypes.func.isRequired
};
export default BanUserButton;
@@ -1,164 +0,0 @@
.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: 500px;
top: 50%;
transform: translateY(-50%);
height: 184px;
padding: 20px;
h2 {
color: black;
font-size: 1.76em;
font-weight: 500;
margin: 0;
}
h3 {
color: black;
font-size: 1.4em;
font-weight: 500;
margin: 0;
}
}
.textField {
margin-top: 15px;
}
.textField label {
font-size: 1.08em;
font-weight: bold;
margin-bottom: 5px;
}
.textField input {
width: 100%;
display: block;
border: none;
outline: none;
border: 1px solid rgba(0,0,0,.12);
padding: 10px 6px;
box-sizing: border-box;
border-radius: 2px;
margin: 5px auto;
}
.footer {
margin: 20px auto 10px;
text-align: center;
}
.footer span {
display: block;
margin-bottom: 5px;
}
.footer a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
}
.socialConnections {
margin-bottom: 20px;
}
.signInButton {
margin-top: 10px;
}
.close {
font-size: 20px;
line-height: 14px;
top: 10px;
right: 10px;
position: absolute;
display: block;
font-weight: bold;
color: #363636;
cursor: pointer;
}
.close:hover {
color: #6b6b6b;
}
input.error{
border: solid 2px #f44336;
}
.errorMsg, .hint {
color: grey;
font-weight: 600;
padding: 3px 0 16px;
}
.alert {
padding: 10px;
margin-bottom: 20px;
border-radius: 2px;
}
.alert--success {
border: solid 1px #1ec00e;
background: #cbf1b8;
color: #006900;
}
.alert--error {
background: #FFEBEE;
color: #B71C1C;
}
.userBox a {
color: #2c69b6;
cursor: pointer;
margin: 0px;
}
.attention {
display: inline-block;
width: 15px;
height: 15px;
background: #B71C1C;
color: #FFEBEE;
font-weight: bolder;
padding: 4px;
vertical-align: middle;
border-radius: 20px;
box-sizing: border-box;
font-size: 9px;
line-height: 7px;
text-align: center;
margin-right: 5px;
}
.action {
margin-top: 15px;
}
.passwordRequestSuccess {
border: 1px solid green;
background-color: lightgreen;
padding: 10px;
}
.passwordRequestFailure {
border: 1px solid orange;
background-color: 1px solid coral;
padding: 10px;
}
.cancel {
margin-right: 10px;
width: 47%;
}
.ban {
width: 47%;
}
.buttons {
margin: 20px 0;
}
@@ -1,51 +0,0 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('community.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{t('community.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('community.are_you_sure', user.username)}</h3>
<i>{t('community.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{t('community.cancel')}
</Button>
<Button
cStyle="black" className={styles.ban}
onClick={() => {
handleBanUser({userId: user.id}).then(() => {
handleClose();
});
}}
raised>
{t('community.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
@@ -1,10 +1,9 @@
import React, {Component} from 'react';
import CommunityMenu from './CommunityMenu';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
import RejectUsernameDialog from './RejectUsernameDialog';
export default class Community extends Component {
@@ -80,19 +79,15 @@ export default class Community extends Component {
<FlaggedAccounts
commenters={users}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
showRejectUsernameDialog={props.showRejectUsernameDialog}
approveUser={props.approveUser}
rejectUsername={props.rejectUsername}
showSuspendUserDialog={props.showSuspendUserDialog}
currentUser={this.props.currentUser}
/>
<BanUserDialog
open={community.banDialog}
user={community.user}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
/>
<SuspendUserDialog
open={community.suspendDialog}
handleClose={props.hideSuspendUserDialog}
<RejectUsernameDialog
open={community.rejectUsernameDialog}
handleClose={props.hideRejectUsernameDialog}
user={community.user}
rejectUsername={props.rejectUsername}
/>
@@ -9,8 +9,6 @@ const FlaggedAccounts = ({...props}) => {
const {commenters} = props;
const hasResults = commenters && !!commenters.length;
// if (commenter.status === 'PENDING' && commenter.actions.length > 0) {
return (
<div className={styles.container}>
<div className={styles.mainFlaggedContent}>
@@ -24,8 +22,9 @@ const FlaggedAccounts = ({...props}) => {
modActionButtons={['APPROVE', 'REJECT']}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
showRejectUsernameDialog={props.showRejectUsernameDialog}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
currentUser={props.currentUser}
/>;
})
: <EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
@@ -1,30 +1,30 @@
import React, {Component, PropTypes} from 'react';
import {Dialog, Button} from 'coral-ui';
import styles from './SuspendUserDialog.css';
import styles from './RejectUsernameDialog.css';
import t from 'coral-framework/services/i18n';
const stages = [
{
title: 'suspenduser.title_reject',
description: 'suspenduser.description_reject',
title: 'reject_username.title_reject',
description: 'reject_username.description_reject',
options: {
'j': 'suspenduser.no_cancel',
'k': 'suspenduser.yes_suspend'
'j': 'reject_username.no_cancel',
'k': 'reject_username.yes_suspend'
}
},
{
title: 'suspenduser.title_notify',
description: 'suspenduser.description_notify',
title: 'reject_username.title_notify',
description: 'reject_username.description_notify',
options: {
'j': 'bandialog.cancel',
'k': 'suspenduser.send'
'k': 'reject_username.send'
}
}
];
class SuspendUserDialog extends Component {
class RejectUsernameDialog extends Component {
state = {email: '', stage: 0}
@@ -35,7 +35,7 @@ class SuspendUserDialog extends Component {
}
componentDidMount() {
this.setState({email: t('suspenduser.email_message_reject'), about: t('suspenduser.username')});
this.setState({email: t('reject_username.email_message_reject'), about: t('reject_username.username')});
}
/*
@@ -72,22 +72,22 @@ class SuspendUserDialog extends Component {
return <Dialog
className={styles.suspendDialog}
id="suspendUserDialog"
id="rejectUsernameDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('suspenduser.suspend_user')}>
title={t('reject_username.suspend_user')}>
<div className={styles.title}>
{t(stages[stage].title, t('suspenduser.username'))}
{t(stages[stage].title, t('reject_username.username'))}
</div>
<div className={styles.container}>
<div className={styles.description}>
{t(stages[stage].description, t('suspenduser.username'))}
{t(stages[stage].description, t('reject_username.username'))}
</div>
{
stage === 1 &&
<div className={styles.writeContainer}>
<div className={styles.emailMessage}>{t('suspenduser.write_message')}</div>
<div className={styles.emailMessage}>{t('reject_username.write_message')}</div>
<div className={styles.emailContainer}>
<textarea
rows={5}
@@ -100,7 +100,7 @@ class SuspendUserDialog extends Component {
<div className={styles.modalButtons}>
{Object.keys(stages[stage].options).map((key, i) => (
<Button key={i} onClick={this.onActionClick(stage, i)}>
{t(stages[stage].options[key], t('suspenduser.username'))}
{t(stages[stage].options[key], t('reject_username.username'))}
</Button>
))}
</div>
@@ -109,4 +109,4 @@ class SuspendUserDialog extends Component {
}
}
export default SuspendUserDialog;
export default RejectUsernameDialog;
@@ -2,6 +2,8 @@ import React from 'react';
import styles from './Community.css';
import ActionButton from './ActionButton';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import t from 'coral-framework/services/i18n';
@@ -18,6 +20,16 @@ const User = (props) => {
const {user, modActionButtons} = props;
let userStatus = user.status;
const showSuspenUserDialog = () => props.showSuspendUserDialog({
userId: user.id,
username: user.username,
});
const showBanUserDialog = () => props.showBanUserDialog({
userId: user.id,
username: user.username,
});
// Do not display unless the user status is 'pending' or 'banned'.
// This means that they have already been reviewed and approved.
return (userStatus === 'PENDING' || userStatus === 'BANNED') &&
@@ -28,12 +40,19 @@ const User = (props) => {
<span>
{user.username}
</span>
<ActionButton
className={styles.banButton}
type='BAN'
user={user}
showBanUserDialog={props.showBanUserDialog}
/>
{props.currentUser.id !== user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={showSuspenUserDialog}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={showBanUserDialog}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
}
</div>
</div>
@@ -78,11 +97,10 @@ const User = (props) => {
<div className={`actions ${styles.actions}`}>
{modActionButtons.map((action, i) =>
<ActionButton key={i}
type={action.toUpperCase()}
user={user}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
showSuspendUserDialog={props.showSuspendUserDialog}
type={action.toUpperCase()}
user={user}
approveUser={props.approveUser}
showRejectUsernameDialog={props.showRejectUsernameDialog}
/>
)}
</div>
@@ -7,14 +7,15 @@ import {Spinner} from 'coral-ui';
import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations';
import {showBanUserDialog} from 'actions/banUserDialog';
import {showSuspendUserDialog} from 'actions/suspendUserDialog';
import {
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
showRejectUsernameDialog,
hideRejectUsernameDialog
} from '../../../actions/community';
import Community from '../components/Community';
@@ -85,16 +86,17 @@ export const withCommunityQuery = withQuery(gql`
});
const mapStateToProps = (state) => ({
community: state.community.toJS()
community: state.community.toJS(),
currentUser: state.auth.toJS().user,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchAccounts,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
showRejectUsernameDialog,
hideRejectUsernameDialog,
updateSorting,
newPage,
}, dispatch);
@@ -165,7 +165,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
checked={settings.autoCloseStream} />
</div>
<div className={styles.content}>
{t('configure.close_after')}
<div className={styles.settingsHeader}>{t('configure.comment_count_header')}</div>
<br />
<Textfield
type='number'
@@ -1,52 +0,0 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
const onBanClick = (userId, commentId, commentStatus, handleBanUser, rejectComment, handleClose) => (e) => {
e.preventDefault();
handleBanUser({userId})
.then(handleClose)
.then(() => commentStatus === 'REJECTED' ? null : rejectComment({commentId}));
};
const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId, commentStatus, showRejectedNote}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('bandialog.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('bandialog.are_you_sure', user.username)}</h3>
<i>{showRejectedNote && t('bandialog.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onBanClick(user.id, commentId, commentStatus, handleBanUser, rejectComment, handleClose)} raised>
{t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
commentId: PropTypes.string,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
@@ -65,6 +65,20 @@ class Comment extends React.Component {
selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp';
}
const showSuspenUserDialog = () => props.showSuspendUserDialog({
userId: comment.user.id,
username: comment.user.username,
commentId: comment.id,
commentStatus: comment.status,
});
const showBanUserDialog = () => props.showBanUserDialog({
userId: comment.user.id,
username: comment.user.username,
commentId: comment.id,
commentStatus: comment.status,
});
return (
<li
tabIndex={props.index}
@@ -102,11 +116,11 @@ class Comment extends React.Component {
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showSuspendUserDialog(comment.user.id, comment.user.username, comment.id, comment.status)}>
onClick={showSuspenUserDialog}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}>
onClick={showBanUserDialog}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
@@ -217,6 +231,7 @@ class Comment extends React.Component {
? <FlagBox
actions={flagActions}
actionSummaries={flagActionSummaries}
viewUserDetail={() => viewUserDetail(comment.user.id)}
/>
: null}
</li>
@@ -67,3 +67,17 @@
color: black;
}
}
.username {
color: #393B44;
text-decoration: none;
cursor: pointer;
font-weight: 600;
padding: 2px 5px;
border-radius: 2px;
margin-left: -5px;
transition: background-color 200ms ease;
&:hover {
background-color: #E0E0E0;
}
}
@@ -33,7 +33,7 @@ class FlagBox extends Component {
}
render() {
const {actionSummaries, actions} = this.props;
const {actionSummaries, actions, viewUserDetail} = this.props;
const {showDetail} = this.state;
return (
@@ -59,9 +59,14 @@ class FlagBox extends Component {
<li key={i}>
{this.reasonMap(summary.reason)} (<strong>{summary.count}</strong>)
<ul>
{
actionList.map((action, j) => <li key={`${i}_${j}`} className={styles.subDetail}><span>{action.user.username}</span> {action.message}</li>)
}
{actionList.map((action, j) =>
<li key={`${i}_${j}`} className={styles.subDetail}>
<a className={styles.username} onClick={viewUserDetail}>
{action.user.username}
</a>
{action.message}
</li>
)}
</ul>
</li>
);
@@ -2,8 +2,6 @@ import React, {Component} from 'react';
import key from 'keymaster';
import styles from './styles.css';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import ModerationQueue from './ModerationQueue';
import ModerationMenu from './ModerationMenu';
import ModerationHeader from './ModerationHeader';
@@ -29,8 +27,8 @@ export default class Moderation extends Component {
key('esc', () => toggleModal(false));
key('j', this.select(true));
key('k', this.select(false));
key('r', this.moderate(false));
key('t', this.moderate(true));
key('f', this.moderate(false));
key('d', this.moderate(true));
}
onClose = () => {
@@ -88,8 +86,8 @@ export default class Moderation extends Component {
key.unbind('esc');
key.unbind('j');
key.unbind('k');
key.unbind('r');
key.unbind('t');
key.unbind('f');
key.unbind('d');
}
componentDidUpdate(_, prevState) {
@@ -169,24 +167,6 @@ export default class Moderation extends Component {
viewUserDetail={viewUserDetail}
hideUserDetail={hideUserDetail}
/>
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
commentId={moderation.commentId}
commentStatus={moderation.commentStatus}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
showRejectedNote={moderation.showRejectedNote}
rejectComment={props.rejectComment}
/>
<SuspendUserDialog
open={moderation.suspendUserDialog.show}
username={moderation.suspendUserDialog.username}
userId={moderation.suspendUserDialog.userId}
organizationName={root.settings.organizationName}
onCancel={props.hideSuspendUserDialog}
onPerform={this.props.suspendUser}
/>
<ModerationKeysModal
hideShortcutsNote={props.hideShortcutsNote}
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
@@ -4,23 +4,20 @@ import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import {getDefinitionName} from 'coral-framework/utils';
import * as notification from 'coral-admin/src/services/notification';
import t, {timeago} from 'coral-framework/services/i18n';
import t from 'coral-framework/services/i18n';
import update from 'immutability-helper';
import truncate from 'lodash/truncate';
import NotFoundAsset from '../components/NotFoundAsset';
import {withSetUserStatus, withSuspendUser, withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {handleCommentChange} from '../../../graphql/utils';
import {fetchSettings} from 'actions/settings';
import {showBanUserDialog} from 'actions/banUserDialog';
import {showSuspendUserDialog} from 'actions/suspendUserDialog';
import {
toggleModal,
singleView,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
hideShortcutsNote,
toggleStorySearch,
viewUserDetail,
@@ -138,37 +135,6 @@ class ModerationContainer extends Component {
}
}
suspendUser = async (args) => {
this.props.hideSuspendUserDialog();
try {
const result = await this.props.suspendUser(args);
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
t('suspenduser.notify_suspend_until',
this.props.moderation.suspendUserDialog.username,
timeago(args.until)),
);
const {commentStatus, commentId} = this.props.moderation.suspendUserDialog;
if (commentStatus !== 'REJECTED') {
return this.props.rejectComment({commentId})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
}
}
catch(err) {
notification.showMutationErrors(err);
}
};
banUser = ({userId}) => {
return this.props.setUserStatus({userId, status: 'BANNED'});
}
acceptComment = ({commentId}) => {
return this.props.setCommentStatus({commentId, status: 'ACCEPTED'});
}
@@ -245,10 +211,8 @@ class ModerationContainer extends Component {
return <Moderation
{...this.props}
loadMore={this.loadMore}
banUser={this.banUser}
acceptComment={this.acceptComment}
rejectComment={this.rejectComment}
suspendUser={this.suspendUser}
activeTab={this.activeTab}
/>;
}
@@ -458,11 +422,9 @@ const mapDispatchToProps = (dispatch) => ({
singleView,
fetchSettings,
showBanUserDialog,
hideBanUserDialog,
hideShortcutsNote,
toggleStorySearch,
showSuspendUserDialog,
hideSuspendUserDialog,
viewUserDetail,
hideUserDetail,
setSortOrder,
@@ -474,8 +436,6 @@ const mapDispatchToProps = (dispatch) => ({
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withSetCommentStatus,
withSetUserStatus,
withSuspendUser,
withQueueCountPolling,
withModQueueQuery,
)(ModerationContainer);
@@ -5,9 +5,9 @@ export const actionsMap = {
};
export const menuActionsMap = {
'REJECT': {status: 'REJECTED', text: 'reject', icon: 'close', key: 'r'},
'REJECT': {status: 'REJECTED', text: 'reject', icon: 'close', key: 'f'},
'REJECTED': {status: 'REJECTED', text: 'rejected', icon: 'close'},
'APPROVE': {status: 'ACCEPTED', text: 'approve', icon: 'done', key: 't'},
'APPROVE': {status: 'ACCEPTED', text: 'approve', icon: 'done', key: 'd'},
'FLAGGED': {status: 'FLAGGED', text: 'flag', icon: 'flag', filter: 'Untouched'},
'BAN': {status: 'BANNED', text: 'ban_user', icon: 'not interested'},
'': {icon: 'done'}
+1 -1
View File
@@ -30,7 +30,7 @@ class ResponseError {
* Exports a HOC with the same signature as `graphql`, that will
* apply mutation options registered in the graphRegistry.
*/
export default (document, config) => (WrappedComponent) => {
export default (document, config = {}) => (WrappedComponent) => {
config = {
...config,
options: config.options || {},
+1 -1
View File
@@ -14,7 +14,7 @@ const withSkipOnErrors = (reducer) => (prev, action, ...rest) => {
* Exports a HOC with the same signature as `graphql`, that will
* apply query options registered in the graphRegistry.
*/
export default (document, config) => (WrappedComponent) => {
export default (document, config = {}) => (WrappedComponent) => {
config = {
...config,
options: config.options || {},
+1 -1
View File
@@ -981,7 +981,7 @@ type RootMutation {
# Suspends a user. Requires the `ADMIN` role.
suspendUser(input: SuspendUserInput!): SuspendUserResponse
# Suspends a user. Requires the `ADMIN` role.
# Reject a username. Requires the `ADMIN` role.
rejectUsername(input: RejectUsernameInput!): RejectUsernameResponse
# Sets Comment status. Requires the `ADMIN` role.
+26 -28
View File
@@ -4,7 +4,9 @@ en:
ban_user: "Ban User?"
banned_user: "Banned User"
cancel: "Cancel"
note: "Note: Banning this user will also place this comment in the Rejected queue."
note: "Note: {0}"
note_reject_comment: "Banning this user will also place this comment in the Rejected queue."
note_ban_user: "Banning this user will not let them edit comment or remove anything."
yes_ban_user: "Yes, Ban User"
bio_offensive: "This bio is offensive"
cancel: "Cancel"
@@ -54,7 +56,6 @@ en:
newsroom_role: "Newsroom Role"
no_flagged_accounts: "The Flagged Usernames queue is currently empty."
no_results: "No users found with that user name or email address. They're hiding!"
note: "Note: Banning this user will not let them edit comment or remove anything."
offensive: "Offensive"
other: Other
people: People
@@ -330,35 +331,32 @@ en:
status: "Stream Status"
stream_status: "Stream Status"
suspenduser:
bio: bio
cancel: "Cancel"
days: "{0} days"
description_0: "Would you like to temporarily suspend this user because of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}."
description_1: "Suspending this user will temporarily disable their account and hide all of their comments on the site."
description_notify: "Suspending this user will temporarily disable their account and hide all of their comments on the site."
description_reject: "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}."
description_suspend: "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete."
email: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment like or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns."
email_subject: "Your account has been suspended"
email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns."
email_message_suspend: "Dear {0},\n\nIn accordance with {1}s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}."
error_email_message_empty: "You must specify an E-Mail message."
hours: "{0} hours"
no_cancel: "No cancel"
notify_suspend_until: "User {0} has been temporarily suspended. This suspension will automatically end {1}."
one_hour: "1 hour"
send: Send
select_duration: "Select suspension duration"
suspend_user: "Suspend User"
title: "Suspend a user"
title_0: "We noticed you rejected a username"
title_1: "Notify the user of their temporary suspension"
title_notify: "Notify the user of their temporary suspension"
title_reject: "We noticed you rejected a username"
title_suspend: "Suspend User"
username: username
description_suspend: "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete."
select_duration: "Select suspension duration"
one_hour: "1 hour"
hours: "{0} hours"
days: "{0} days"
cancel: "Cancel"
suspend_user: "Suspend User"
email_message_suspend: "Dear {0},\n\nIn accordance with {1}s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}."
title_notify: "Notify the user of their temporary suspension"
notify_suspend_until: "User {0} has been temporarily suspended. This suspension will automatically end {1}."
description_notify: "Suspending this user will temporarily disable their account and hide all of their comments on the site."
write_message: "Write a message"
send: Send
reject_username:
username: username
no_cancel: "No cancel"
description_reject: "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}."
title_notify: "Notify the user of their temporary suspension"
description_notify: "Suspending this user will temporarily disable their account and hide all of their comments on the site."
title_reject: "We noticed you rejected a username"
suspend_user: "Suspend User"
yes_suspend: "Yes suspend"
email_message_reject: "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns."
write_message: "Write a message"
send: Send
thank_you: "We value your safety and feedback. A moderator will review your report."
unset_best: "Untag as Best"
user:
+26 -28
View File
@@ -4,7 +4,9 @@ es:
ban_user: "¿Quieres suspender el Usuario?"
banned_user: "Usuario Suspendido"
cancel: Cancelar
note: "Nota: Suspender a este usuario también va a colocar este comentario en la cola de Rechazados."
note: "Nota: {0}"
note_reject_comment: "Suspender a este usuario también va a colocar este comentario en la cola de Rechazados."
note_ban_user: "Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar."
yes_ban_user: "Si, Suspender al usuario"
bio_offensive: "Esta biografia es ofensiva"
cancel: Cancelar
@@ -53,7 +55,6 @@ es:
newsroom_role: "Rol en la redacción"
no_flagged_accounts: "No hay ningún nombre de usario reportado en este momento."
no_results: "No se encontraron usuarios con ese nombre o correo."
note: "Nota: Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar."
offensive: Ofensivo
other: Otro
people: Gente
@@ -353,35 +354,32 @@ es:
status: "Estado del Hilo"
stream_status: "Estado del Hilo"
suspenduser:
bio: "bio"
cancel: "Cancelar"
days: "{0} días"
description_0: "¿Quiere suspender temporalmente a este usuario por su {0}? Hacerlo va a ocultar temporalmente todos sus comentarios hasta que edite su {0}."
description_1: "Suspender a este usuario va a deshabilitar temporalmente su cuenta y ocultar todos sus comentarios en el sitio."
description_notify: "Suspender a este usuario va a deshabilitar temporalmente su cuenta y ocultar todos sus comentarios en el sitio."
description_reject: "¿Quiere suspender temporalmente a este usuario por su {0}? Hacerlo va a ocultar temporalmente sus comentarios hasta que edite su {0}."
description_suspend: "Esta suspendiendo a {0}. Este comentario va a ir a la fila de Rechazados, y a {0} no se le va a permitir gustar, marcar, responder o publicar hasta que el tiempo de suspendido halla concluido."
email: "Otro miembro de la comunidad recientemente marco su nombre de usuario para revisarlo. Por su contenido su nombre de usuario ha sido rechazado. Esto significa que no puede comentar o marcar contenido hasta que edite su nombre de usuario. Envíenos un correo si tiene preguntas o comentarios."
email_subject: "Su cuenta ha sido suspendida"
email_message_reject: "Otro miembro de la comunidad hace poco ha marcado su nombre de usuario para revisar. Y este ha sido rechazado por su contenido. Esto significa que no puede comentar, gustar o marcar contenido hasta que edite su nombre de usuario. Envíenos un correo si tiene alguna preocupación o pregunta."
email_message_suspend: "Querida {0}, De acuerdo a la guía comunitaria de {1}, su cuenta ha sido temporalmente suspendida. Durante este tiempo, no podrá comentar, marcar o involucrarse con otros comentaristas. Por favor unirse a la conversación {2}."
error_email_message_empty: "Debe especificar un mensaje de correo."
hours: "{0} horas"
no_cancel: "No cancelar"
notify_suspend_until: "Usuario {0} ha sido temporalmente suspendido. Esta suspension va a terminar automáticamente en {1}."
one_hour: "1 hora"
send: "Enviar"
select_duration: "Seleccionar la duración de la suspensión"
suspend_user: "Suspender Usuario"
title: "Suspender un usuario"
title_0: "Nos dimos cuenta que ha rechazado un nombre de usuario"
title_1: "Notificar al usuario de su suspensión temporal"
title_notify: "Notificar al usuario de su suspensión temporal"
title_reject: "Vimos que ha rechazado un nombre de usuario"
title_suspend: "Suspender Usuario"
username: nombre de usuario
description_suspend: "Esta suspendiendo a {0}. Este comentario va a ir a la fila de Rechazados, y a {0} no se le va a permitir gustar, marcar, responder o publicar hasta que el tiempo de suspendido halla concluido."
select_duration: "Seleccionar la duración de la suspensión"
one_hour: "1 hora"
hours: "{0} horas"
days: "{0} días"
cancel: "Cancelar"
suspend_user: "Suspender Usuario"
email_message_suspend: "Querido/a {0}, De acuerdo a la guía comunitaria de {1}, su cuenta ha sido temporalmente suspendida. Durante este tiempo, no podrá comentar, marcar o involucrarse con otros comentaristas. Por favor unirse a la conversación {2}."
title_notify: "Notificar al usuario de su suspensión temporal"
notify_suspend_until: "Usuario {0} ha sido temporalmente suspendido. Esta suspension va a terminar automáticamente en {1}."
description_notify: "Suspender a este usuario va a deshabilitar temporalmente su cuenta y ocultar todos sus comentarios en el sitio."
write_message: "Escribir un mensaje"
send: "Enviar"
reject_username:
username: nombre de usuario
no_cancel: "No cancelar"
description_reject: "¿Quiere suspender temporalmente a este usuario por su {0}? Hacerlo va a ocultar temporalmente sus comentarios hasta que edite su {0}."
title_notify: "Notificar al usuario de su suspensión temporal"
description_notify: "Suspender a este usuario va a deshabilitar temporalmente su cuenta y ocultar todos sus comentarios en el sitio."
title_reject: "Vimos que ha rechazado un nombre de usuario"
suspend_user: "Suspender Usuario"
yes_suspend: "Si, suspender"
email_message_reject: "Otro miembro de la comunidad hace poco ha marcado su nombre de usuario para revisar. Y este ha sido rechazado por su contenido. Esto significa que no puede comentar, gustar o marcar contenido hasta que edite su nombre de usuario. Envíenos un correo si tiene alguna preocupación o pregunta."
write_message: "Escribir un mensaje"
send: "Enviar"
thank_you: "Valoramos tanto su seguridad en este espacio como sus comentarios. Un o una moderadora va a leer su reporte."
unset_best: "Des-etiquetar como el mejor"
user: