mirror of
https://github.com/wassname/talk.git
synced 2026-07-01 22:24:08 +08:00
Merge branch 'master' into i18n-emails
This commit is contained in:
@@ -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>
|
||||
))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user