Merge branch 'master' into i18n-emails

This commit is contained in:
Gabriela Rodríguez Berón
2017-06-02 13:35:09 -07:00
committed by GitHub
21 changed files with 201 additions and 36 deletions
@@ -33,3 +33,12 @@ export const setSortOrder = (order) => ({
order
});
export const changeUserDetailStatuses = (tab) => {
let statuses;
if (tab === 'all') {
statuses = ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'];
} else if (tab === 'rejected') {
statuses = ['REJECTED'];
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
};
@@ -17,11 +17,11 @@ const ActionButton = ({type = '', active, ...props}) => {
return (
<Button
className={`${typeName} ${styles.actionButton} ${active ? styles[`${typeName}__active`] : ''}`}
className={`${typeName} ${styles.actionButton} ${props.minimal ? styles.minimal : ''} ${active ? styles[`${typeName}__active`] : ''}`}
cStyle={typeName}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptComment : props.rejectComment}
>{t(`modqueue.${text}`)}</Button>
>{props.minimal ? '' : t(`modqueue.${text}`)}</Button>
);
};
@@ -189,6 +189,12 @@
width: 140px;
}
.minimal {
width: 45px;
min-width: 0;
float: left;
}
.approve__active {
box-shadow: none;
color: white;
@@ -8,3 +8,4 @@ export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER';
export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES';
@@ -8,6 +8,8 @@ const initialState = fromJS({
commentId: null,
commentStatus: null,
userDetailId: null,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
banDialog: false,
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
@@ -65,6 +67,10 @@ export default function moderation (state = initialState, action) {
return state.set('userDetailId', action.userId);
case actions.HIDE_USER_DETAIL:
return state.set('userDetailId', null);
case actions.CHANGE_USER_DETAIL_STATUSES:
return state
.set('userDetailActiveTab', action.tab)
.set('userDetailStatuses', action.statuses);
case actions.SET_SORT_ORDER:
return state.set('sortOrder', action.order);
default :
@@ -23,6 +23,7 @@ const Comment = ({
viewUserDetail,
suspectWords,
bannedWords,
minimal,
...props
}) => {
const links = linkify.getMatches(comment.body);
@@ -55,9 +56,13 @@ const Comment = ({
<div className={styles.container}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span className={styles.username} onClick={() => viewUserDetail(comment.user.id)}>
{comment.user.name}
</span>
{
!minimal && (
<span className={styles.username} onClick={() => viewUserDetail(comment.user.id)}>
{comment.user.name}
</span>
)
}
<span className={styles.created}>
{timeago(comment.created_at || Date.now() - props.index * 60 * 1000)}
</span>
@@ -91,7 +96,7 @@ const Comment = ({
</div>
<div className={styles.moderateArticle}>
Story: {comment.asset.title}
{!props.currentAsset &&
{!props.currentAsset && !minimal &&
<Link to={`/admin/moderate/${comment.asset.id}`}>{t('modqueue.moderate')}</Link>}
</div>
<div className={styles.itemBody}>
@@ -128,6 +133,7 @@ const Comment = ({
(action === 'APPROVE' && comment.status === 'ACCEPTED');
return (
<ActionButton
minimal={minimal}
key={i}
type={action}
user={comment.user}
@@ -173,6 +179,7 @@ const Comment = ({
};
Comment.propTypes = {
minimal: PropTypes.bool,
viewUserDetail: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
@@ -187,10 +187,15 @@ export default class Moderation extends Component {
{moderation.userDetailId && (
<UserDetail
id={moderation.userDetailId}
hideUserDetail={hideUserDetail} />
hideUserDetail={hideUserDetail}
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment} />
)}
</div>
);
}
}
@@ -36,6 +36,23 @@
background-color: transparent;
font-size: 16px;
position: absolute;
width: 100%;
width: 90%;
outline: none;
}
.commentStatuses {
padding: 0;
list-style: none;
li {
display: inline-block;
margin: 0 10px;
cursor: pointer;
padding: 0 10px;
}
}
.active {
font-weight: bold;
border-bottom: 3px solid #F36451;
}
@@ -2,12 +2,20 @@ import React, {PropTypes} from 'react';
import {Button, Drawer} from 'coral-ui';
import styles from './UserDetail.css';
import Slot from 'coral-framework/components/Slot';
import Comment from './Comment';
import {actionsMap} from '../helpers/moderationQueueActionsMap';
export default class UserDetail extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
hideUserDetail: PropTypes.func.isRequired,
root: PropTypes.object.isRequired,
bannedWords: PropTypes.array.isRequired,
suspectWords: PropTypes.array.isRequired,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
}
copyPermalink = () => {
@@ -20,9 +28,33 @@ export default class UserDetail extends React.Component {
}
}
changeStatus = (tab) => {
if (tab === 'all') {
this.props.changeStatus('all');
} else if (tab === 'rejected') {
this.props.changeStatus('rejected');
}
}
render () {
const {root: {user, totalComments, rejectedComments}, hideUserDetail} = this.props;
const {
root: {
user,
totalComments,
rejectedComments,
comments: {nodes}
},
moderation: {userDetailActiveTab: tab},
bannedWords,
suspectWords,
showBanUserDialog,
showSuspendUserDialog,
acceptComment,
rejectComment,
hideUserDetail
} = this.props;
const localProfile = user.profiles.find((p) => p.provider === 'local');
let profile;
if (localProfile) {
profile = localProfile.id;
@@ -62,8 +94,34 @@ export default class UserDetail extends React.Component {
<p>{`${(rejectedPercent).toFixed(1)}%`}</p>
</div>
</div>
<ul className={styles.commentStatuses}>
<li className={tab === 'all' ? styles.active : ''} onClick={this.changeStatus.bind(this, 'all')}>All</li>
<li className={tab === 'rejected' ? styles.active : ''} onClick={this.changeStatus.bind(this, 'rejected')}>Rejected</li>
</ul>
<div>
{
nodes.map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
return <Comment
key={i}
index={i}
comment={comment}
selected={false}
suspectWords={suspectWords}
bannedWords={bannedWords}
viewUserDetail={() => {}}
actions={actionsMap[status]}
showBanUserDialog={showBanUserDialog}
showSuspendUserDialog={showSuspendUserDialog}
acceptComment={acceptComment}
rejectComment={rejectComment}
currentAsset={null}
currentUserId={this.props.id}
minimal={true} />;
})
}
</div>
</Drawer>
);
}
}
@@ -247,6 +247,7 @@ span {
}
.created {
padding: 5px;
color: #262626;
font-size: 14px;
margin-left: 15px;
@@ -433,7 +434,6 @@ span {
.username {
color: blue;
text-decoration: underline;
padding: 5px;
cursor: pointer;
&:hover {
@@ -1,8 +1,25 @@
import React, {PropTypes} from 'react';
import {compose, gql} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import UserDetail from '../components/UserDetail';
import withQuery from 'coral-framework/hocs/withQuery';
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
import {getDefinitionName} from 'coral-framework/utils';
import {changeUserDetailStatuses} from 'coral-admin/src/actions/moderation';
import Comment from './Comment';
const commentConnectionFragment = gql`
fragment CoralAdmin_Moderation_CommentConnection on CommentConnection {
nodes {
...${getDefinitionName(Comment.fragments.comment)}
}
hasNextPage
startCursor
endCursor
}
${Comment.fragments.comment}
`;
const pluginFragments = getSlotsFragments([
'userProfile',
@@ -19,12 +36,12 @@ class UserDetailContainer extends React.Component {
return null;
}
return <UserDetail {...this.props}/>;
return <UserDetail changeStatus={this.props.changeUserDetailStatuses} {...this.props}/>;
}
}
export const withUserDetailQuery = withQuery(gql`
query CoralAdmin_UserDetail($author_id: ID!) {
query CoralAdmin_UserDetail($author_id: ID!, $statuses: [COMMENT_STATUS!]) {
user(id: $author_id) {
id
username
@@ -37,18 +54,35 @@ export const withUserDetailQuery = withQuery(gql`
}
totalComments: commentCount(query: {author_id: $author_id})
rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]})
comments: comments(query: {
author_id: $author_id,
statuses: $statuses
}) {
...CoralAdmin_Moderation_CommentConnection
}
${pluginFragments.spreads('root')}
}
${Comment.fragments.comment}
${pluginFragments.definitions('user')}
${pluginFragments.definitions('root')}
${commentConnectionFragment}
`, {
options: ({id}) => {
options: ({id, moderation: {userDetailStatuses: statuses}}) => {
return {
variables: {author_id: id}
variables: {author_id: id, statuses}
};
}
});
const mapStateToProps = (state) => ({
moderation: state.moderation.toJS()
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({changeUserDetailStatuses}, dispatch)
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withUserDetailQuery,
)(UserDetailContainer);
@@ -308,15 +308,6 @@ class Comment extends React.Component {
commentId={comment.id}
inline
/>
{!disableReply &&
<ActionButton>
<ReplyButton
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false}
/>
</ActionButton>}
<ActionButton>
<IfUserCanModifyBest user={currentUser}>
<BestButton
@@ -326,6 +317,15 @@ class Comment extends React.Component {
/>
</IfUserCanModifyBest>
</ActionButton>
{!disableReply &&
<ActionButton>
<ReplyButton
onClick={() => setActiveReplyBox(comment.id)}
parentCommentId={parentId || comment.id}
currentUserId={currentUser && currentUser.id}
banned={false}
/>
</ActionButton>}
<Slot
fill="commentActions"
data={this.props.data}
@@ -1,5 +1,5 @@
import React, {Component, PropTypes} from 'react';
import t from 'coral-framework/services/i18n';
import styles from './IgnoredUsers.css';
export class IgnoredUsers extends Component {
@@ -18,7 +18,7 @@ export class IgnoredUsers extends Component {
<div>
{
users.length
? <p>Because you ignored these, you do not see their comments.</p>
? <p>{t('framework.because_you_ignored')}</p>
: null
}
<dl className={styles.ignoredUserList}>
@@ -29,7 +29,7 @@ export class IgnoredUsers extends Component {
<dd className={styles.stopListening}>
<a
onClick={() => stopIgnoring({id})}
className={styles.link}>Stop ignoring</a>
className={styles.link}>{t('framework.stop_ignoring')}</a>
</dd>
</span>
))
+13 -3
View File
@@ -1,15 +1,25 @@
.drawer {
max-width: 700px;
min-width: 400px;
padding: 20px;
min-width: 550px;
position: fixed;
top: 0;
right: 0;
right: -17px;
bottom: 0;
background-color: white;
transition: transform 500ms ease-in-out;
box-shadow: -3px 0px 4px 0px rgba(0,0,0,0.15);
z-index: 10000;
.content {
position: absolute;
padding: 20px;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow-y: scroll;
overflow-x: hidden;
}
}
.closeButton {
+3 -1
View File
@@ -6,7 +6,9 @@ const Drawer = ({children, handleClickOutside}) => {
return (
<div className={styles.drawer}>
<div className={styles.closeButton} onClick={handleClickOutside}>×</div>
{children}
<div className={styles.content}>
{children}
</div>
</div>
);
};
+3
View File
@@ -148,6 +148,9 @@ enum ACTION_TYPE {
# CommentsQuery allows the ability to query comments by a specific methods.
input CommentsQuery {
# Author of the comments
author_id: ID
# Current status of a comment. Requires the `ADMIN` role.
statuses: [COMMENT_STATUS!]
+3 -1
View File
@@ -199,10 +199,11 @@ en:
flag_username: "Report username"
framework:
banned_account_msg: "Your account is currently suspended. This means that you cannot Like Report or write comments. Please contact us if you have any questions."
because_you_ignored: "Because you ignored the following commenters, their comments are hidden."
comment: comment
comment_is_ignored: "This comment is hidden because you ignored this user."
comments: comments
configure_stream: "Configure Stream"
configure_stream: "Configure"
content_not_available: "This content is not available"
edit_name:
button: Submit
@@ -215,6 +216,7 @@ en:
new_count: "View {0} new {1}"
profile: Profile
show_all_comments: "Show all comments"
stop_ignoring: "Stop ignoring"
success_bio_update: "Your biography has been updated"
success_name_update: "Your username has been updated"
success_update_settings: "The changes you have made have been applied to the comment stream on this article"
+1
View File
@@ -215,6 +215,7 @@ es:
new_count: "Ver {0} {1} nuevo"
profile: Perfil
show_all_comments: "Mostrar todos los comentarios"
stop_ignoring: "No ignorar más"
success_bio_update: "Tu biografia fue actualizada"
success_name_update: "Tu nombre de usuario ha sido actualizado"
success_update_settings: "La configuración de este articulo fue actualizada"
@@ -10,7 +10,7 @@ const UserBox = ({loggedIn, user, logout, onShowProfile}) => (
{
loggedIn ? (
<div className={styles.userBox}>
{t('sign_in.logged_in_as')}
<span className={styles.userBoxLoggedIn}>{t('sign_in.logged_in_as')}</span>
<a onClick={onShowProfile}>{user.username}</a>. {t('sign_in.not_you')}
<a className={styles.logout} onClick={() => logout()}>
{t('sign_in.logout')}
@@ -70,6 +70,10 @@ input.error{
letter-spacing: 0.1px;
}
.userBoxLoggedIn {
font-weight: bold;
}
.userBox a {
color: black;
font-weight: bold;
@@ -5,10 +5,10 @@ en:
verify_email: "Thank you for creating an account! We sent an email to the address you provided to verify your account."
verify_email2: "You must verify your account before engaging with the community."
not_you: "Not you?"
logged_in_as: "Logged in as"
logged_in_as: "Signed in as"
facebook_sign_in: "Sign in with Facebook"
facebook_sign_up: "Sign up with Facebook"
logout: "Logout"
logout: "Sign out"
sign_in: "Sign in"
sign_in_to_join: "Sign in to join the conversation"
or: "Or"