From 39d8a5d01b8896e37b07d4a6978b758911f4d022 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 5 Jun 2017 14:20:34 -0600 Subject: [PATCH] bulk operations on User Detail comments --- client/coral-admin/src/actions/moderation.js | 7 +++ .../coral-admin/src/constants/moderation.js | 2 + client/coral-admin/src/reducers/moderation.js | 7 ++- .../routes/Moderation/components/Comment.js | 21 ++++++++- .../Moderation/components/UserDetail.css | 26 ++++++++++- .../Moderation/components/UserDetail.js | 44 ++++++++++++++++--- .../routes/Moderation/components/styles.css | 13 +++--- .../Moderation/containers/UserDetail.js | 22 ++++++++-- 8 files changed, 126 insertions(+), 16 deletions(-) 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);