Merge branch 'master' of github.com:coralproject/talk into add-flag-option

This commit is contained in:
David Jay
2016-12-20 16:23:39 -08:00
10 changed files with 105 additions and 137 deletions
+53 -14
View File
@@ -1,30 +1,69 @@
import coralApi from '../../../coral-framework/helpers/response';
import * as commentActions from '../constants/comments';
// Get comments to fill each of the three lists on the mod queue
export const fetchModerationQueueComments = () => {
return dispatch => {
dispatch({type: commentActions.COMMENTS_MODERATION_QUEUE_FETCH});
return Promise.all([
coralApi('/queue/comments/pending'),
coralApi('/comments?status=rejected'),
coralApi('/comments?action_type=flag')
])
.then(([pending, rejected, flagged]) => {
/* Combine seperate calls into a single object */
flagged.comments.forEach(comment => comment.flagged = true);
return {
comments: [...pending.comments, ...rejected.comments, ...flagged.comments],
users: [...pending.users, ...rejected.users, ...flagged.users],
actions: [...pending.actions, ...rejected.actions, ...flagged.actions]
};
})
.then(({comments, users}) => {
/* Post comments and users to redux store. Actions will be posted when they are needed. */
dispatch({type: commentActions.USERS_MODERATION_QUEUE_FETCH_SUCCESS, users});
dispatch({type: commentActions.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS, comments});
});
};
};
// Create a new comment
export const createComment = (name, body) => {
return dispatch => {
const comment = {body, name};
return coralApi('/comments', {method: 'POST', comment})
.then(res => dispatch({type: commentActions.COMMENT_CREATE_SUCCESS, comment: res}))
.catch(error => dispatch({type: commentActions.COMMENT_CREATE_FAILED, error}));
};
};
/**
* Action disptacher related to comments
*/
export const updateStatus = (status, id) => (dispatch, getState) => {
dispatch({type: 'COMMENT_STATUS_UPDATE', id, status});
dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)});
// 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: commentActions.COMMENT_STATUS_UPDATE, id: comment.id, status});
return coralApi(`/comments/${comment.id}/status`, {method: 'PUT', body: {status}})
.then(res => dispatch({type: commentActions.COMMENT_UPDATE_SUCCESS, res}))
.catch(error => dispatch({type: commentActions.COMMENT_UPDATE_FAILED, error}));
};
};
export const flagComment = id => (dispatch, getState) => {
dispatch({type: 'COMMENT_FLAG', id});
dispatch({type: commentActions.COMMENT_FLAG, id});
dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)});
};
export const createComment = (name, body) => dispatch => {
dispatch({type: 'COMMENT_CREATE', name, body});
};
// Dialog Actions
export const showBanUserDialog = (userId, userName, commentId) => {
return dispatch => {
dispatch({type: 'SHOW_BANUSER_DIALOG', userId, userName, commentId});
};
return {type: commentActions.SHOW_BANUSER_DIALOG, userId, userName, commentId};
};
export const hideBanUserDialog = (showDialog) => {
return dispatch => {
dispatch({type: 'HIDE_BANUSER_DIALOG', showDialog});
};
return {type: commentActions.HIDE_BANUSER_DIALOG, showDialog};
};
+8 -7
View File
@@ -1,14 +1,15 @@
import coralApi from '../../../coral-framework/helpers/response';
import * as actions from '../constants/user';
/**
* Action disptacher related to users
*/
//
// export const banUser = (status, author_id) => (dispatch) => {
// dispatch({type: 'USER_STATUS_UPDATE', author_id, status});
// };
export const banUser = (status, userId, commentId) => {
// change status of a user
export const userStatusUpdate = (status, userId, commentId) => {
return dispatch => {
dispatch({type: 'USER_BAN', status, userId, commentId});
dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH'});
dispatch({type: actions.UPDATE_STATUS_REQUEST});
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then(res => dispatch({type: actions.UPDATE_STATUS_SUCCESS, res}))
.catch(error => dispatch({type: actions.UPDATE_STATUS_FAILURE, error}));
};
};
+1 -1
View File
@@ -79,7 +79,7 @@ const getActionButton = (action, i, props) => {
cStyle={action}
icon={props.actionsMap[action].icon}
key={i}
onClick={() => props.onClickAction(props.actionsMap[action].status, comment.id)}
onClick={() => props.onClickAction(props.actionsMap[action].status, comment)}
/>
);
};
@@ -1,3 +1,13 @@
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const USER_BAN_SUCESS = 'USER_BAN_SUCESS';
export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS';
export const COMMENTS_MODERATION_QUEUE_FETCH = 'COMMENTS_MODERATION_QUEUE_FETCH';
export const COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS = 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS';
export const COMMENT_CREATE_SUCCESS = 'COMMENT_CREATE_SUCCESS';
export const COMMENT_CREATE_FAILED = 'COMMENT_CREATE_FAILED';
export const COMMENT_STREAM_FETCH_SUCCESS = 'COMMENT_STREAM_FETCH_SUCCESS';
export const COMMENT_UPDATE_SUCCESS = 'COMMENT_UPDATE_SUCCESS';
export const COMMENT_UPDATE_FAILED = 'COMMENT_UPDATE_FAILED';
export const COMMENT_STATUS_UPDATE = 'COMMENT_STATUS_UPDATE';
export const COMMENT_FLAG = 'COMMENT_FLAG';
+3
View File
@@ -0,0 +1,3 @@
export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST';
export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS';
export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE';
@@ -71,6 +71,7 @@ const updateClosedTimeout = (updateSettings, ts, isMeasure) => (event) => {
const CommentSettings = ({fetchingSettings, updateSettings, settingsError, settings, errors}) => {
if (fetchingSettings) {
/* maybe a spinner here at some point */
return <p>Loading settings...</p>;
}
@@ -6,8 +6,13 @@ import ModerationKeysModal from 'components/ModerationKeysModal';
import CommentList from 'components/CommentList';
import BanUserDialog from 'components/BanUserDialog';
import {updateStatus, showBanUserDialog, hideBanUserDialog} from 'actions/comments';
import {banUser} from 'actions/users';
import {
updateStatus,
showBanUserDialog,
hideBanUserDialog,
fetchModerationQueueComments
} from 'actions/comments';
import {userStatusUpdate} from 'actions/users';
import styles from './ModerationQueue.css';
import I18n from 'coral-framework/modules/i18n/i18n';
@@ -31,7 +36,7 @@ class ModerationQueue extends React.Component {
// Fetch comments and bind singleView key before render
componentWillMount () {
this.props.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH'});
this.props.dispatch(fetchModerationQueueComments());
key('s', () => this.setState({singleView: !this.state.singleView}));
key('shift+/', () => this.setState({modalOpen: true}));
key('esc', () => this.setState({modalOpen: false}));
@@ -47,15 +52,17 @@ class ModerationQueue extends React.Component {
// Hack for dynamic mdl tabs
componentDidMount () {
if (typeof componentHandler !== 'undefined') {
// FIXME: fix this hack
componentHandler.upgradeAllRegistered(); // eslint-disable-line no-undef
}
}
// Dispatch the update status action
onCommentAction (action, id) {
onCommentAction (action, comment) {
// If not banning then change the status to approved or flagged as action = status
this.props.dispatch(updateStatus(action, id));
this.props.dispatch(updateStatus(action, comment));
}
showBanUserDialog (userId, userName, commentId) {
@@ -67,7 +74,10 @@ class ModerationQueue extends React.Component {
}
banUser (userId, commentId) {
this.props.dispatch(banUser('banned', userId, commentId));
this.props.dispatch(userStatusUpdate('banned', userId, commentId))
.then(() => {
this.props.dispatch(fetchModerationQueueComments());
});
}
onTabClick (activeTab) {
@@ -101,7 +111,7 @@ class ModerationQueue extends React.Component {
commentIds={premodIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, commentId) => this.onCommentAction(action, commentId)}
onClickAction={(action, comment) => this.onCommentAction(action, comment)}
onClickShowBanDialog={(userId, userName, commentId) => this.showBanUserDialog(userId, userName, commentId)}
actions={['reject', 'approve', 'ban']}
loading={comments.loading} />
@@ -118,7 +128,7 @@ class ModerationQueue extends React.Component {
commentIds={rejectedIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, id) => this.onCommentAction(action, id)}
onClickAction={(action, comment) => this.onCommentAction(action, comment)}
actions={['approve']}
loading={comments.loading} />
</div>
@@ -129,7 +139,7 @@ class ModerationQueue extends React.Component {
commentIds={flaggedIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, id) => this.onCommentAction(action, id)}
onClickAction={(action, comment) => this.onCommentAction(action, comment)}
actions={['reject', 'approve']}
loading={comments.loading} />
</div>
+9 -8
View File
@@ -1,4 +1,5 @@
import * as actions from '../constants/comments';
import * as userActions from '../constants/user';
import {Map, List, fromJS} from 'immutable';
/**
@@ -23,16 +24,16 @@ const initialState = Map({
// Handle the comment actions
export default (state = initialState, action) => {
switch (action.type) {
case 'COMMENTS_MODERATION_QUEUE_FETCH': return state.set('loading', true);
case 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceComments(action, state);
case 'COMMENTS_MODERATION_QUEUE_FAILED': return state.set('loading', false);
case 'COMMENT_STATUS_UPDATE': return updateStatus(state, action);
case 'COMMENT_FLAG': return flag(state, action);
case 'COMMENT_CREATE_SUCCESS': return addComment(state, action);
case 'COMMENT_STREAM_FETCH_SUCCESS': return replaceComments(action, state);
case actions.COMMENTS_MODERATION_QUEUE_FETCH: return state.set('loading', true);
case actions.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS: return replaceComments(action, state);
case actions.COMMENTS_MODERATION_QUEUE_FAILED: return state.set('loading', false);
case actions.COMMENT_STATUS_UPDATE: return updateStatus(state, action);
case actions.COMMENT_FLAG: return flag(state, action);
case actions.COMMENT_CREATE_SUCCESS: return addComment(state, action);
case actions.COMMENT_STREAM_FETCH_SUCCESS: return replaceComments(action, state);
case actions.SHOW_BANUSER_DIALOG: return setBanUser(state, true, action);
case actions.HIDE_BANUSER_DIALOG: return setBanUser(state, false, action);
case actions.USER_BAN_SUCESS: return setBanUser(state, false, action);
case userActions.UPDATE_STATUS_SUCCESS: return setBanUser(state, false, action);
default: return state;
}
};
+1 -2
View File
@@ -2,7 +2,6 @@
import {createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import mainReducer from 'reducers';
import talkAdapter from 'services/talk-adapter';
/**
* Create the store by merging the app reducers with
@@ -14,5 +13,5 @@ import talkAdapter from 'services/talk-adapter';
export default createStore(
mainReducer,
window.devToolsExtension && window.devToolsExtension(),
applyMiddleware(thunk, talkAdapter)
applyMiddleware(thunk)
);
@@ -1,96 +0,0 @@
import coralApi from '../../../coral-framework/helpers/response';
/**
* The adapter is a redux middleware that interecepts the actions that need
* to interface with the backend, do the job and return the results.
* The idea is that if we expose the required actions to handle to devs, the
* moderation app can be platform agnostic. This same client could work not only
* for the coral but also for wordpress comments, disqus and many more.
*/
// Intercept redux actions and act over the ones we are interested
export default store => next => action => {
switch (action.type) {
case 'COMMENTS_MODERATION_QUEUE_FETCH':
fetchModerationQueueComments(store);
break;
case 'COMMENT_UPDATE':
updateComment(store, action.comment);
break;
case 'COMMENT_CREATE':
createComment(store, action.name, action.body);
break;
case 'USER_BAN':
userStatusUpdate(store, action.status, action.userId, action.commentId);
break;
}
next(action);
};
// Get comments to fill each of the three lists on the mod queue
const fetchModerationQueueComments = store =>
Promise.all([
coralApi('/queue/comments/pending'),
coralApi('/comments?status=rejected'),
coralApi('/comments?action_type=flag')
])
.then(([pending, rejected, flagged]) => {
/* Combine seperate calls into a single object */
let all = {};
all.comments = pending.comments
.concat(rejected.comments)
.concat(flagged.comments.map(comment => {
comment.flagged = true;
return comment;
}));
all.users = pending.users
.concat(rejected.users)
.concat(flagged.users);
all.actions = pending.actions
.concat(rejected.actions)
.concat(flagged.actions);
return all;
})
.then(all => {
/* Post comments and users to redux store. Actions will be posted when they are needed. */
store.dispatch({type: 'USERS_MODERATION_QUEUE_FETCH_SUCCESS',
users: all.users});
store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS',
comments: all.comments});
});
// .catch(error => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_FAILED', error}));
// Update a comment. Now to update a comment we need to send back the whole object
const updateComment = (store, comment) => {
coralApi(`/comments/${comment.get('id')}/status`, {method: 'PUT', body: {status: comment.get('status')}})
.then(res => store.dispatch({type: 'COMMENT_UPDATE_SUCCESS', res}))
.catch(error => store.dispatch({type: 'COMMENT_UPDATE_FAILED', error}));
};
// Create a new comment
const createComment = (store, name, comment) => {
const body = {
status: 'Untouched',
body: comment,
name: name,
createdAt: Date.now()
};
return coralApi('/comments', {method: 'POST', body})
.then(res => store.dispatch({type: 'COMMENT_CREATE_SUCCESS', comment: res}))
.catch(error => store.dispatch({type: 'COMMENT_CREATE_FAILED', error}));
};
// Ban a user
const userStatusUpdate = (store, status, userId, commentId) => {
return coralApi(`/users/${userId}/status`, {method: 'POST', body: {status: status, comment_id: commentId}})
.then(res => store.dispatch({type: 'USER_BAN_SUCESS', res}))
.catch(error => store.dispatch({type: 'USER_BAN_FAILED', error}));
};