mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 18:49:28 +08:00
Merge branch 'master' of github.com:coralproject/talk into load-more-replies
This commit is contained in:
@@ -4,7 +4,6 @@ import {Router, Route, IndexRoute, IndexRedirect, browserHistory} from 'react-ro
|
||||
import Streams from 'containers/Streams/Streams';
|
||||
import Configure from 'containers/Configure/Configure';
|
||||
import LayoutContainer from 'containers/LayoutContainer';
|
||||
import CommentStream from 'containers/CommentStream/CommentStream';
|
||||
import InstallContainer from 'containers/Install/InstallContainer';
|
||||
import CommunityContainer from 'containers/Community/CommunityContainer';
|
||||
|
||||
@@ -16,7 +15,6 @@ const routes = (
|
||||
<Route exact path="/admin/install" component={InstallContainer}/>
|
||||
<Route path='/admin' component={LayoutContainer}>
|
||||
<IndexRoute component={ModerationContainer} />
|
||||
<Route path='embed' component={CommentStream} />
|
||||
<Route path='community' component={CommunityContainer} />
|
||||
<Route path='configure' component={Configure} />
|
||||
<Route path='streams' component={Streams} />
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
import * as commentTypes from '../constants/comments';
|
||||
import * as actionTypes from '../constants/actions';
|
||||
|
||||
function addUsersCommentsActions (dispatch, {comments, users, actions}) {
|
||||
dispatch({type: commentTypes.USERS_MODERATION_QUEUE_FETCH_SUCCESS, users});
|
||||
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS, comments});
|
||||
dispatch({type: actionTypes.ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS, actions});
|
||||
}
|
||||
|
||||
// Get comments to fill each of the three lists on the mod queue
|
||||
export const fetchModerationQueueComments = () => {
|
||||
return dispatch => {
|
||||
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
|
||||
|
||||
return Promise.all([
|
||||
coralApi('/queue/comments/premod'),
|
||||
coralApi('/queue/users/flagged'),
|
||||
coralApi('/queue/comments/rejected'),
|
||||
coralApi('/queue/comments/flagged')
|
||||
])
|
||||
.then(([premodComments, pendingUsers, rejected, flagged]) => {
|
||||
|
||||
/* Combine seperate calls into a single object */
|
||||
flagged.comments.forEach(comment => comment.flagged = true);
|
||||
return {
|
||||
comments: [...premodComments.comments, ...rejected.comments, ...flagged.comments],
|
||||
users: [...premodComments.users, ...pendingUsers.users, ...rejected.users, ...flagged.users],
|
||||
actions: [...premodComments.actions, ...pendingUsers.actions, ...rejected.actions, ...flagged.actions]
|
||||
};
|
||||
})
|
||||
.then(addUsersCommentsActions.bind(this, dispatch));
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchPremodQueue = () => {
|
||||
return dispatch => {
|
||||
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
|
||||
|
||||
return coralApi('/queue/comments/premod')
|
||||
.then(addUsersCommentsActions.bind(this, dispatch));
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchPendingUsersQueue = () => {
|
||||
return dispatch => {
|
||||
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
|
||||
|
||||
return coralApi('/queue/users/flagged')
|
||||
.then(addUsersCommentsActions.bind(this, dispatch));
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchRejectedQueue = () => {
|
||||
return dispatch => {
|
||||
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
|
||||
|
||||
return coralApi('/queue/comments/rejected')
|
||||
.then(addUsersCommentsActions.bind(this, dispatch));
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchFlaggedQueue = () => {
|
||||
return dispatch => {
|
||||
dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST});
|
||||
|
||||
return coralApi('/queue/comments/flagged')
|
||||
.then(results => {
|
||||
results.comments.forEach(comment => comment.flagged = true);
|
||||
return results;
|
||||
})
|
||||
.then(addUsersCommentsActions.bind(this, dispatch));
|
||||
};
|
||||
};
|
||||
|
||||
// Create a new comment
|
||||
export const createComment = (name, body) => {
|
||||
return (dispatch) => {
|
||||
const formData = {body, name};
|
||||
return coralApi('/comments', {method: 'POST', body: formData})
|
||||
.then(res => dispatch({type: commentTypes.COMMENT_CREATE_SUCCESS, comment: res}))
|
||||
.catch(error => dispatch({type: commentTypes.COMMENT_CREATE_FAILED, error}));
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Action disptacher related to comments
|
||||
*/
|
||||
|
||||
// Update a comment. Now to update a comment we need to send back the whole object
|
||||
export const updateStatus = (status, comment) => {
|
||||
return dispatch => {
|
||||
dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_REQUEST, id: comment.id, status});
|
||||
return coralApi(`/comments/${comment.id}/status`, {method: 'PUT', body: {status}})
|
||||
.then(res => dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_SUCCESS, res}))
|
||||
.catch(error => dispatch({type: commentTypes.COMMENT_STATUS_UPDATE_FAILURE, error}));
|
||||
};
|
||||
};
|
||||
|
||||
export const flagComment = id => (dispatch, getState) => {
|
||||
dispatch({type: commentTypes.COMMENT_FLAG, id});
|
||||
dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)});
|
||||
};
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
@custom-media --big-viewport (min-width: 780px);
|
||||
|
||||
.container {
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (--big-viewport) {
|
||||
.tab {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import React from 'react';
|
||||
import styles from './CommentStream.css';
|
||||
import {Snackbar} from 'react-mdl';
|
||||
import {connect} from 'react-redux';
|
||||
import {createComment, flagComment} from 'actions/comments';
|
||||
import ModerationList from 'components/ModerationList';
|
||||
import CommentBox from 'components/CommentBox';
|
||||
|
||||
/**
|
||||
* Renders a comment stream using a ModerationList component
|
||||
* and adds a box for adding a new comment
|
||||
*/
|
||||
|
||||
class CommentStream extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {snackbar: false, snackbarMsg: ''};
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onClickAction = this.onClickAction.bind(this);
|
||||
}
|
||||
|
||||
// Fetch the comments before mounting
|
||||
componentWillMount () {
|
||||
this.props.dispatch({type: 'COMMENT_STREAM_FETCH'});
|
||||
}
|
||||
|
||||
// Submit the new comment
|
||||
onSubmit (comment) {
|
||||
this.props.dispatch(createComment(comment.name, comment.body));
|
||||
}
|
||||
|
||||
// The only action for now is flagging
|
||||
onClickAction (action, id) {
|
||||
if (action === 'flag') {
|
||||
this.props.dispatch(flagComment(id));
|
||||
clearTimeout(this._snackTimeout);
|
||||
this.setState({snackbar: true, snackbarMsg: 'Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.'});
|
||||
this._snackTimeout = setTimeout(() => this.setState({snackbar: false}), 30000);
|
||||
}
|
||||
}
|
||||
|
||||
// Render the comment box along with the ModerationList
|
||||
render ({comments, users}, {snackbar, snackbarMsg}) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<CommentBox onSubmit={this.onSubmit} />
|
||||
<ModerationList isActive hideActive
|
||||
singleView={false}
|
||||
commentIds={comments.ids}
|
||||
comments={comments.byId}
|
||||
users={users.byId}
|
||||
onClickAction={this.onClickAction}
|
||||
actions={['flag']}
|
||||
loading={comments.loading} />
|
||||
<Snackbar active={snackbar}>{snackbarMsg}</Snackbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
comments: state.comments.toJS(),
|
||||
users: state.users.toJS()
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(CommentStream);
|
||||
@@ -9,12 +9,13 @@ const ModerationQueue = ({activeTab = 'premod', ...props}) => {
|
||||
<ul>
|
||||
{
|
||||
props.data[activeTab].map((comment, i) => {
|
||||
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
|
||||
return <Comment
|
||||
key={i}
|
||||
index={i}
|
||||
comment={comment}
|
||||
suspectWords={props.suspectWords}
|
||||
actions={actionsMap[comment.status]}
|
||||
actions={actionsMap[status]}
|
||||
showBanUserDialog={props.showBanUserDialog}
|
||||
acceptComment={props.acceptComment}
|
||||
rejectComment={props.rejectComment}
|
||||
|
||||
@@ -318,6 +318,7 @@ span {
|
||||
}
|
||||
|
||||
.flagBox {
|
||||
max-width: 480px;
|
||||
border-top: 1px solid rgba(66, 66, 66, 0.12);
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
export const actionsMap = {
|
||||
PREMOD: ['REJECT', 'APPROVE', 'BAN'],
|
||||
FLAGGED: ['REJECT', 'APPROVE'],
|
||||
FLAGGED: ['REJECT', 'APPROVE', 'BAN'],
|
||||
REJECTED: ['APPROVE']
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ query ModQueue ($asset_id: ID!) {
|
||||
}
|
||||
flagged: comments(query: {
|
||||
action_type: FLAG,
|
||||
asset_id: $asset_id
|
||||
asset_id: $asset_id,
|
||||
statuses: [NONE, PREMOD]
|
||||
}) {
|
||||
...commentView
|
||||
action_summaries {
|
||||
|
||||
@@ -161,7 +161,7 @@ class Embed extends Component {
|
||||
notification={{text: null}}
|
||||
/>
|
||||
<LoadMore
|
||||
id={asset.id}
|
||||
assetId={asset.id}
|
||||
comments={asset.comments}
|
||||
moreComments={asset.commentCount > asset.comments.length}
|
||||
loadMore={this.props.loadMore}/>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, {PropTypes} from 'react';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from 'coral-framework/translations.json';
|
||||
import {ADDTL_COMMENTS_ON_LOAD_MORE} from 'coral-framework/constants/comments';
|
||||
import {Button} from 'coral-ui';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const loadMoreComments = (id, comments, loadMore, parentId) => {
|
||||
const loadMoreComments = (assetId, comments, loadMore, parentId) => {
|
||||
|
||||
if (!comments.length) {
|
||||
return;
|
||||
@@ -18,20 +18,29 @@ const loadMoreComments = (id, comments, loadMore, parentId) => {
|
||||
loadMore({
|
||||
limit: ADDTL_COMMENTS_ON_LOAD_MORE,
|
||||
cursor,
|
||||
asset_id: id,
|
||||
assetId,
|
||||
parent_id: parentId,
|
||||
sort: parentId ? 'CHRONOLOGICAL' : 'REVERSE_CHRONOLOGICAL'
|
||||
});
|
||||
};
|
||||
|
||||
const LoadMore = ({id, comments, loadMore, moreComments, parentId}) => moreComments ?
|
||||
<Button
|
||||
className='coral-load-more'
|
||||
onClick={() => loadMoreComments(id, comments, loadMore, parentId)}>
|
||||
{
|
||||
lang.t('loadMore')
|
||||
}
|
||||
</Button>
|
||||
: null;
|
||||
const LoadMore = ({assetId, comments, loadMore, moreComments, parentId}) => (
|
||||
moreComments
|
||||
? <Button
|
||||
className='coral-load-more'
|
||||
onClick={() => loadMoreComments(assetId, comments, loadMore, parentId)}>
|
||||
{
|
||||
lang.t('loadMore')
|
||||
}
|
||||
</Button>
|
||||
: null
|
||||
);
|
||||
|
||||
LoadMore.propTypes = {
|
||||
assetId: PropTypes.string.isRequired,
|
||||
comments: PropTypes.array.isRequired,
|
||||
moreComments: PropTypes.bool.isRequired,
|
||||
loadMore: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LoadMore;
|
||||
|
||||
@@ -18,7 +18,7 @@ const getCountsByAssetID = (context, asset_ids) => {
|
||||
$in: asset_ids
|
||||
},
|
||||
status: {
|
||||
$in: [null, 'ACCEPTED']
|
||||
$in: ['NONE', 'ACCEPTED']
|
||||
},
|
||||
parent_id: null
|
||||
}
|
||||
@@ -51,7 +51,7 @@ const getCountsByParentID = (context, parent_ids) => {
|
||||
$in: parent_ids
|
||||
},
|
||||
status: {
|
||||
$in: [null, 'ACCEPTED']
|
||||
$in: ['NONE', 'ACCEPTED']
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -88,7 +88,7 @@ const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_
|
||||
} else {
|
||||
comments = comments.where({
|
||||
status: {
|
||||
$in: [null, 'ACCEPTED']
|
||||
$in: ['NONE', 'ACCEPTED']
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ const Wordlist = require('../../services/wordlist');
|
||||
* @param {String} body body of the comment
|
||||
* @param {String} asset_id asset for the comment
|
||||
* @param {String} parent_id optional parent of the comment
|
||||
* @param {String} [status=null] the status of the new comment
|
||||
* @param {String} [status='NONE'] the status of the new comment
|
||||
* @return {Promise} resolves to the created comment
|
||||
*/
|
||||
const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = null}, status = null) => {
|
||||
const createComment = ({user, loaders: {Comments}}, {body, asset_id, parent_id = null}, status = 'NONE') => {
|
||||
return CommentsService.publicCreate({
|
||||
body,
|
||||
asset_id,
|
||||
@@ -105,7 +105,7 @@ const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}) => {
|
||||
if (charCountEnable && body.length > charCount) {
|
||||
return 'REJECTED';
|
||||
}
|
||||
return moderation === 'PRE' ? 'PREMOD' : null;
|
||||
return moderation === 'PRE' ? 'PREMOD' : 'NONE';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,10 @@ type Tag {
|
||||
# The statuses that a comment may have.
|
||||
enum COMMENT_STATUS {
|
||||
|
||||
# The comment is not PREMOD, but was not applied a moderation status by a
|
||||
# moderator.
|
||||
NONE
|
||||
|
||||
# The comment has been accepted by a moderator.
|
||||
ACCEPTED
|
||||
|
||||
@@ -93,7 +97,7 @@ enum ACTION_TYPE {
|
||||
input CommentsQuery {
|
||||
|
||||
# current status of a comment.
|
||||
statuses: [COMMENT_STATUS]
|
||||
statuses: [COMMENT_STATUS!]
|
||||
|
||||
# asset that a comment is on.
|
||||
asset_id: ID
|
||||
@@ -152,7 +156,7 @@ type Comment {
|
||||
asset: Asset
|
||||
|
||||
# The current status of a comment.
|
||||
status: COMMENT_STATUS
|
||||
status: COMMENT_STATUS!
|
||||
|
||||
# The time when the comment was created
|
||||
created_at: Date!
|
||||
|
||||
+6
-2
@@ -6,7 +6,7 @@ const STATUSES = [
|
||||
'ACCEPTED',
|
||||
'REJECTED',
|
||||
'PREMOD',
|
||||
null
|
||||
'NONE'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -66,7 +66,11 @@ const CommentSchema = new Schema({
|
||||
asset_id: String,
|
||||
author_id: String,
|
||||
status_history: [StatusSchema],
|
||||
status: {type: String, default: null},
|
||||
status: {
|
||||
type: String,
|
||||
enum: STATUSES,
|
||||
default: 'NONE'
|
||||
},
|
||||
tags: [TagSchema],
|
||||
parent_id: String
|
||||
}, {
|
||||
|
||||
@@ -52,7 +52,7 @@ router.get('/', (req, res, next) => {
|
||||
if (user_id) {
|
||||
query = CommentsService.findByUserId(user_id, authorization.has(req.user, 'ADMIN'));
|
||||
} else if (status) {
|
||||
query = assetIDWrap(CommentsService.findByStatus(status === 'NEW' ? null : status));
|
||||
query = assetIDWrap(CommentsService.findByStatus(status === 'NEW' ? 'NONE' : status));
|
||||
} else if (action_type) {
|
||||
query = CommentsService
|
||||
.findIdsByActionType(action_type)
|
||||
|
||||
@@ -11,6 +11,7 @@ const STATUSES = [
|
||||
'ACCEPTED',
|
||||
'REJECTED',
|
||||
'PREMOD',
|
||||
'NONE',
|
||||
];
|
||||
|
||||
module.exports = class CommentsService {
|
||||
@@ -31,7 +32,7 @@ module.exports = class CommentsService {
|
||||
body,
|
||||
asset_id,
|
||||
parent_id,
|
||||
status = null,
|
||||
status = 'NONE',
|
||||
author_id
|
||||
} = comment;
|
||||
|
||||
@@ -146,7 +147,7 @@ module.exports = class CommentsService {
|
||||
* @param {String} status status of the comment to search for
|
||||
* @return {Promise} resovles to comment array
|
||||
*/
|
||||
static findByStatus(status = null) {
|
||||
static findByStatus(status = 'NONE') {
|
||||
return CommentModel.find({status});
|
||||
}
|
||||
|
||||
@@ -155,7 +156,7 @@ module.exports = class CommentsService {
|
||||
* @param {String} asset_id
|
||||
* @return {Promise}
|
||||
*/
|
||||
static moderationQueue(status = null, asset_id = null) {
|
||||
static moderationQueue(status = 'NONE', asset_id = null) {
|
||||
|
||||
// Fetch the comments with statuses.
|
||||
let comments = CommentModel.find({status});
|
||||
|
||||
@@ -131,7 +131,7 @@ describe('services.CommentsService', () => {
|
||||
|
||||
expect(c2).to.not.be.null;
|
||||
expect(c2.id).to.be.uuid;
|
||||
expect(c2.status).to.be.null;
|
||||
expect(c2.status).to.be.equal('NONE');
|
||||
|
||||
expect(c3).to.not.be.null;
|
||||
expect(c3.id).to.be.uuid;
|
||||
@@ -225,7 +225,7 @@ describe('services.CommentsService', () => {
|
||||
|
||||
return CommentsService.findById(comment_id)
|
||||
.then((c) => {
|
||||
expect(c.status).to.be.null;
|
||||
expect(c.status).to.be.equal('NONE');
|
||||
|
||||
return CommentsService.pushStatus(comment_id, 'REJECTED', '123');
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user