diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js
index 3f8d975df..de57a6102 100644
--- a/client/coral-admin/src/actions/moderation.js
+++ b/client/coral-admin/src/actions/moderation.js
@@ -42,3 +42,10 @@ export const changeUserDetailStatuses = (tab) => {
}
return {type: actions.CHANGE_USER_DETAIL_STATUSES, tab, statuses};
};
+
+export const toggleSelectCommentInUserDetail = (id, active) => {
+ return {
+ type: active ? actions.SELECT_USER_DETAIL_COMMENT : actions.UNSELECT_USER_DETAIL_COMMENT,
+ id
+ };
+};
diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js
index aa8a51a24..a4d75c202 100644
--- a/client/coral-admin/src/constants/moderation.js
+++ b/client/coral-admin/src/constants/moderation.js
@@ -9,3 +9,5 @@ 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';
+export const SELECT_USER_DETAIL_COMMENT = 'SELECT_USER_DETAIL_COMMENT';
+export const UNSELECT_USER_DETAIL_COMMENT = 'UNSELECT_USER_DETAIL_COMMENT';
diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js
index 4201ffb06..579800423 100644
--- a/client/coral-admin/src/reducers/moderation.js
+++ b/client/coral-admin/src/reducers/moderation.js
@@ -1,4 +1,4 @@
-import {fromJS, Map} from 'immutable';
+import {fromJS, Map, Set} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = fromJS({
@@ -10,6 +10,7 @@ const initialState = fromJS({
userDetailId: null,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
+ userDetailSelectedIds: new Set(),
banDialog: false,
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
@@ -71,6 +72,10 @@ export default function moderation (state = initialState, action) {
return state
.set('userDetailActiveTab', action.tab)
.set('userDetailStatuses', action.statuses);
+ case actions.SELECT_USER_DETAIL_COMMENT:
+ return state.update('userDetailSelectedIds', (set) => set.add(action.id));
+ case actions.UNSELECT_USER_DETAIL_COMMENT:
+ return state.update('userDetailSelectedIds', (set) => set.delete(action.id));
case actions.SET_SORT_ORDER:
return state.set('sortOrder', action.order);
default :
diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js
index 1bc2b50d7..edb474493 100644
--- a/client/coral-admin/src/routes/Moderation/components/Comment.js
+++ b/client/coral-admin/src/routes/Moderation/components/Comment.js
@@ -24,6 +24,8 @@ const Comment = ({
suspectWords,
bannedWords,
minimal,
+ selected,
+ toggleSelect,
...props
}) => {
const links = linkify.getMatches(comment.body);
@@ -48,10 +50,17 @@ const Comment = ({
})
.concat(linkText);
+ let selectionStateCSS;
+ if (minimal) {
+ selectionStateCSS = selected ? styles.minimalSelection : '';
+ } else {
+ selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp';
+ }
+
return (
@@ -63,6 +72,15 @@ const Comment = ({
)
}
+ {
+ minimal && typeof selected === 'boolean' && typeof toggleSelect === 'function' && (
+
toggleSelect(e.target.value, e.target.checked)} />
+ )
+ }
{timeago(comment.created_at || Date.now() - props.index * 60 * 1000)}
@@ -187,6 +205,7 @@ Comment.propTypes = {
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
currentUserId: PropTypes.string.isRequired,
+ toggleSelect: PropTypes.func,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
diff --git a/client/coral-admin/src/routes/Moderation/components/UserDetail.css b/client/coral-admin/src/routes/Moderation/components/UserDetail.css
index 1119be8c4..d4b5f0db7 100644
--- a/client/coral-admin/src/routes/Moderation/components/UserDetail.css
+++ b/client/coral-admin/src/routes/Moderation/components/UserDetail.css
@@ -41,8 +41,11 @@
}
.commentStatuses {
- padding: 0;
+ padding: 10px 0 0 0;
+ margin: 0;
+ height: 52px;
list-style: none;
+ box-sizing: border-box;
li {
display: inline-block;
@@ -56,3 +59,24 @@
font-weight: bold;
border-bottom: 3px solid #F36451;
}
+
+.bulkActionGroup {
+ height: 52px;
+ background-color: #efefef;
+
+ i {
+ margin-right: 0;
+ }
+
+ .bulkAction {
+ display: inline-block;
+ width: 48px;
+ height: 48px;
+ transform: scale(.7);
+ min-width: 0;
+ }
+
+ .bulkAction:last-child {
+ margin-left: -10px;
+ }
+}
diff --git a/client/coral-admin/src/routes/Moderation/components/UserDetail.js b/client/coral-admin/src/routes/Moderation/components/UserDetail.js
index c461220c8..97321c1d6 100644
--- a/client/coral-admin/src/routes/Moderation/components/UserDetail.js
+++ b/client/coral-admin/src/routes/Moderation/components/UserDetail.js
@@ -16,6 +16,9 @@ export default class UserDetail extends React.Component {
showSuspendUserDialog: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
+ changeStatus: PropTypes.func.isRequired,
+ toggleSelect: PropTypes.func.isRequired,
+ bulkSetCommentStatus: PropTypes.func.isRequired,
}
copyPermalink = () => {
@@ -44,9 +47,14 @@ export default class UserDetail extends React.Component {
rejectedComments,
comments: {nodes}
},
- moderation: {userDetailActiveTab: tab},
+ moderation: {
+ userDetailActiveTab: tab,
+ userDetailSelectedIds: selectedIds
+ },
bannedWords,
suspectWords,
+ toggleSelect,
+ bulkSetCommentStatus,
showBanUserDialog,
showSuspendUserDialog,
acceptComment,
@@ -94,14 +102,38 @@ export default class UserDetail extends React.Component {
{`${(rejectedPercent).toFixed(1)}%`}
-
+ {
+ selectedIds.length === 0
+ ? (
+
+ )
+ : (
+
+
+
+ {`${selectedIds.length} comments selected`}
+
+ )
+ }
+
{
nodes.map((comment, i) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
+ const selected = selectedIds.indexOf(comment.id) !== -1;
return ;
diff --git a/client/coral-admin/src/routes/Moderation/components/styles.css b/client/coral-admin/src/routes/Moderation/components/styles.css
index e7bea9430..d278e0ed1 100644
--- a/client/coral-admin/src/routes/Moderation/components/styles.css
+++ b/client/coral-admin/src/routes/Moderation/components/styles.css
@@ -185,10 +185,6 @@ span {
padding: 0 14px;
}
- &:hover {
- box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
- }
-
&:last-child {
border-bottom: none;
}
@@ -291,7 +287,6 @@ span {
@media (--big-viewport) {
.listItem {
- border: 1px solid #e0e0e0;
margin-bottom: 30px;
&:last-child {
@@ -460,3 +455,11 @@ span {
position: relative;
}
}
+
+.minimal {
+ margin: 0;
+}
+
+.minimalSelection {
+ background-color: #ecf4ff;
+}
diff --git a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
index 9afd10efb..5a0956161 100644
--- a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
+++ b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
@@ -6,7 +6,8 @@ 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 {changeUserDetailStatuses, toggleSelectCommentInUserDetail} from 'coral-admin/src/actions/moderation';
+import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
import Comment from './Comment';
const commentConnectionFragment = gql`
@@ -31,12 +32,23 @@ class UserDetailContainer extends React.Component {
hideUserDetail: PropTypes.func.isRequired
}
+ // status can be 'ACCEPTED' or 'REJECTED'
+ bulkSetCommentStatus = (status) => {
+ this.props.moderation.userDetailSelectedIds.forEach((commentId) => {
+ this.props.setCommentStatus({commentId, status});
+ });
+ }
+
render () {
if (!('user' in this.props.root)) {
return null;
}
- return ;
+ return ;
}
}
@@ -79,10 +91,14 @@ const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({
- ...bindActionCreators({changeUserDetailStatuses}, dispatch)
+ ...bindActionCreators({
+ changeUserDetailStatuses,
+ toggleSelectCommentInUserDetail
+ }, dispatch)
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withUserDetailQuery,
+ withSetCommentStatus,
)(UserDetailContainer);