diff --git a/client/coral-admin/src/components/CommentBodyHighlighter.js b/client/coral-admin/src/components/CommentBodyHighlighter.js
new file mode 100644
index 000000000..3b3ee3318
--- /dev/null
+++ b/client/coral-admin/src/components/CommentBodyHighlighter.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import Highlighter from 'react-highlight-words';
+import Linkify from 'react-linkify';
+const linkify = new Linkify();
+
+export default ({suspectWords, bannedWords, body, ...rest}) => {
+
+ const links = linkify.getMatches(body);
+ const linkText = links ? links.map((link) => link.raw) : [];
+
+ // since words are checked against word boundaries on the backend,
+ // should be the behavior on the front end as well.
+ // currently the highlighter plugin does not support out of the box.
+ const searchWords = [...suspectWords, ...bannedWords]
+ .filter((w) => {
+ return new RegExp(`(^|\\s)${w}(\\s|$)`, 'i').test(body);
+ })
+ .concat(linkText);
+
+ return (
+
+ );
+};
diff --git a/client/coral-admin/src/components/IfHasLink.js b/client/coral-admin/src/components/IfHasLink.js
new file mode 100644
index 000000000..cd37ea951
--- /dev/null
+++ b/client/coral-admin/src/components/IfHasLink.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import Linkify from 'react-linkify';
+const linkify = new Linkify();
+
+export default ({text, children}) => {
+ const hasLinks = !!linkify.getMatches(text);
+
+ if (!hasLinks) {
+ return null;
+ }
+
+ return React.Children.only(children);
+};
diff --git a/client/coral-admin/src/components/ModerationList.css b/client/coral-admin/src/components/ModerationList.css
index 2b5989191..89e43e02a 100644
--- a/client/coral-admin/src/components/ModerationList.css
+++ b/client/coral-admin/src/components/ModerationList.css
@@ -192,7 +192,6 @@
.minimal {
width: 45px;
min-width: 0;
- float: left;
}
.approve__active {
diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js
new file mode 100644
index 000000000..6b476b861
--- /dev/null
+++ b/client/coral-admin/src/containers/UserDetail.js
@@ -0,0 +1,119 @@
+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 {getDefinitionName, getSlotFragmentSpreads} from 'coral-framework/utils';
+import {
+ changeUserDetailStatuses,
+ clearUserDetailSelections,
+ toggleSelectCommentInUserDetail
+} from 'coral-admin/src/actions/moderation';
+import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
+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 slots = [
+ 'userProfile',
+];
+
+class UserDetailContainer extends React.Component {
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ hideUserDetail: PropTypes.func.isRequired
+ }
+
+ // status can be 'ACCEPTED' or 'REJECTED'
+ bulkSetCommentStatus = (status) => {
+ const changes = this.props.moderation.userDetailSelectedIds.map((commentId) => {
+ return this.props.setCommentStatus({commentId, status});
+ });
+
+ Promise.all(changes).then(() => {
+ this.props.data.refetch(); // some comments may have moved out of this tab
+ this.props.clearUserDetailSelections(); // un-select everything
+ });
+ }
+
+ bulkReject = () => {
+ this.bulkSetCommentStatus('REJECTED');
+ }
+
+ bulkAccept = () => {
+ this.bulkSetCommentStatus('ACCEPTED');
+ }
+
+ render () {
+ if (!('user' in this.props.root)) {
+ return null;
+ }
+
+ return ;
+ }
+}
+
+export const withUserDetailQuery = withQuery(gql`
+ query CoralAdmin_UserDetail($author_id: ID!, $statuses: [COMMENT_STATUS!]) {
+ user(id: $author_id) {
+ id
+ username
+ created_at
+ profiles {
+ id
+ provider
+ }
+ ${getSlotFragmentSpreads(slots, 'user')}
+ }
+ 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
+ }
+ ${getSlotFragmentSpreads(slots, 'root')}
+ }
+ ${commentConnectionFragment}
+`, {
+ options: ({id, moderation: {userDetailStatuses: statuses}}) => {
+ return {
+ variables: {author_id: id, statuses}
+ };
+ }
+});
+
+const mapStateToProps = (state) => ({
+ moderation: state.moderation.toJS()
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ ...bindActionCreators({
+ changeUserDetailStatuses,
+ clearUserDetailSelections,
+ toggleSelectCommentInUserDetail
+ }, dispatch)
+});
+
+export default compose(
+ connect(mapStateToProps, mapDispatchToProps),
+ withUserDetailQuery,
+ withSetCommentStatus,
+)(UserDetailContainer);
diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css
deleted file mode 100644
index e69de29bb..000000000
diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js
index bca774c04..07e168ab7 100644
--- a/client/coral-admin/src/routes/Moderation/components/Comment.js
+++ b/client/coral-admin/src/routes/Moderation/components/Comment.js
@@ -1,22 +1,20 @@
import React, {PropTypes} from 'react';
import {Link} from 'react-router';
-import Linkify from 'react-linkify';
import {Icon} from 'coral-ui';
import FlagBox from './FlagBox';
import styles from './styles.css';
import CommentType from './CommentType';
-import Highlighter from 'react-highlight-words';
+import CommentAnimatedEdit from './CommentAnimatedEdit';
import Slot from 'coral-framework/components/Slot';
import {getActionSummary} from 'coral-framework/utils';
import ActionButton from 'coral-admin/src/components/ActionButton';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
+import CommentBodyHighlighter from 'coral-admin/src/components/CommentBodyHighlighter';
+import IfHasLink from 'coral-admin/src/components/IfHasLink';
import cn from 'classnames';
-import {murmur3} from 'murmurhash-js';
-import {CSSTransitionGroup} from 'react-transition-group';
-
-const linkify = new Linkify();
+import {getCommentType} from 'coral-admin/src/utils/comment';
import t, {timeago} from 'coral-framework/services/i18n';
@@ -29,41 +27,16 @@ class Comment extends React.Component {
viewUserDetail,
suspectWords,
bannedWords,
- minimal,
selected,
- toggleSelect,
className,
...props
} = this.props;
- const links = linkify.getMatches(comment.body);
- const linkText = links ? links.map((link) => link.raw) : [];
const flagActionSummaries = getActionSummary('FlagActionSummary', comment);
- const flagActions =
- comment.actions &&
- comment.actions.filter((a) => a.__typename === 'FlagAction');
- let commentType = '';
- if (comment.status === 'PREMOD') {
- commentType = 'premod';
- } else if (flagActions && flagActions.length) {
- commentType = 'flagged';
- }
+ const flagActions = comment.actions && comment.actions.filter((a) => a.__typename === 'FlagAction');
+ const commentType = getCommentType(comment);
- // since words are checked against word boundaries on the backend,
- // should be the behavior on the front end as well.
- // currently the highlighter plugin does not support out of the box.
- const searchWords = [...suspectWords, ...bannedWords]
- .filter((w) => {
- return new RegExp(`(^|\\s)${w}(\\s|$)`, 'i').test(comment.body);
- })
- .concat(linkText);
-
- let selectionStateCSS;
- if (minimal) {
- selectionStateCSS = selected ? styles.minimalSelection : '';
- } else {
- selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp';
- }
+ let selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp';
const showSuspenUserDialog = () => props.showSuspendUserDialog({
userId: comment.user.id,
@@ -81,31 +54,21 @@ class Comment extends React.Component {
return (
{
- !minimal && (
+ (
viewUserDetail(comment.user.id)}>
{comment.user.username}
)
}
- {
- minimal && typeof selected === 'boolean' && typeof toggleSelect === 'function' && (
- toggleSelect(e.target.value, e.target.checked)} />
- )
- }
- {timeago(comment.created_at || Date.now() - props.index * 60 * 1000)}
+ {timeago(comment.created_at)}
{
(comment.editing && comment.editing.edited)
@@ -125,45 +88,27 @@ class Comment extends React.Component {
}
-
+
- {comment.user.status === 'banned'
- ?
-
- {t('comment.banned_user')}
-
- : null}
-
+
Story: {comment.asset.title}
{!props.currentAsset &&
{t('modqueue.moderate')}}
-
-
+
+
-
{' '}
- {links
- ?
- Contains Link
-
- : null}
+
+
+ Contains Link
+
+
{actions.map((action, i) => {
const active =
@@ -193,7 +138,6 @@ class Comment extends React.Component {
(action === 'APPROVE' && comment.status === 'ACCEPTED');
return (
-
+
{
+ return (
+
+ {React.cloneElement(React.Children.only(children), {key: murmur3(body)})}
+
+ );
+};
diff --git a/client/coral-admin/src/routes/Moderation/components/CommentType.css b/client/coral-admin/src/routes/Moderation/components/CommentType.css
index 3144a5bd7..beafc2a7c 100644
--- a/client/coral-admin/src/routes/Moderation/components/CommentType.css
+++ b/client/coral-admin/src/routes/Moderation/components/CommentType.css
@@ -1,22 +1,19 @@
.commentType {
- position: absolute;
- right: 15px;
- top: 11px;
+ display: inline-block;
color: white;
background: grey;
height: 32px;
box-sizing: border-box;
line-height: 29px;
- padding: 2px 8px 2px 26px;
+ padding: 2px 8px;
border-radius: 2px;
font-size: 12px;
- i {
+ > i {
font-size: 14px;
- position: absolute;
- left: 6px;
- top: 8px;
+ vertical-align: text-top;
margin: 0;
+ margin-right: 4px;
}
&.premod {
diff --git a/client/coral-admin/src/routes/Moderation/components/CommentType.js b/client/coral-admin/src/routes/Moderation/components/CommentType.js
index 346b5f1e6..075ec5a78 100644
--- a/client/coral-admin/src/routes/Moderation/components/CommentType.js
+++ b/client/coral-admin/src/routes/Moderation/components/CommentType.js
@@ -1,12 +1,13 @@
import React, {PropTypes} from 'react';
import styles from './CommentType.css';
import {Icon} from 'coral-ui';
+import cn from 'classnames';
const CommentType = (props) => {
const typeData = getTypeData(props.type);
return (
-
+
{typeData.text}
);
diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js
index 693be075e..75f613cc9 100644
--- a/client/coral-admin/src/routes/Moderation/components/Moderation.js
+++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js
@@ -176,13 +176,7 @@ export default class Moderation extends Component {
{moderation.userDetailId && (
+ />
)}
p.provider === 'local');
@@ -116,8 +111,8 @@ export default class UserDetail extends React.Component {
selectedIds.length === 0
? (
- - All
- - Rejected
+ - All
+ - Rejected
)
: (
@@ -141,27 +136,23 @@ export default class UserDetail extends React.Component {
{
- nodes.map((comment, i) => {
+ nodes.map((comment) => {
const status = comment.action_summaries ? 'FLAGGED' : comment.status;
const selected = selectedIds.indexOf(comment.id) !== -1;
return {}}
actions={actionsMap[status]}
- showBanUserDialog={showBanUserDialog}
- showSuspendUserDialog={showSuspendUserDialog}
acceptComment={this.acceptThenReload}
rejectComment={this.rejectThenReload}
selected={selected}
toggleSelect={toggleSelect}
- currentAsset={null}
- currentUserId={this.props.id}
- minimal={true} />;
+ viewUserDetail={viewUserDetail}
+ />;
})
}
diff --git a/client/coral-admin/src/routes/Moderation/components/UserDetailComment.css b/client/coral-admin/src/routes/Moderation/components/UserDetailComment.css
new file mode 100644
index 000000000..0e10f00c0
--- /dev/null
+++ b/client/coral-admin/src/routes/Moderation/components/UserDetailComment.css
@@ -0,0 +1,128 @@
+.root {
+ display: block;
+ margin: 0;
+ border-bottom: 1px solid #e0e0e0;
+ width: 100%;
+ transition: all 200ms;
+ padding: 10px 0px;
+ min-height: 0;
+}
+
+.rootSelected {
+ background-color: #ecf4ff;
+}
+
+.container {
+ padding: 0px 14px;
+}
+
+.story {
+ font-size: 14px;
+ margin: 10px 0;
+ font-weight: 500;
+ line-height: 1.2;
+ max-width: 500px;
+}
+
+.story > a {
+ display: inline-block;
+ color: #063b9a;
+ text-decoration: none;
+ font-weight: 500;
+ letter-spacing: .5px;
+ margin-left: 10px;
+ font-size: 13px;
+ margin-left: 5px;
+ padding-bottom: 0px;
+ border-bottom: solid 1px;
+ line-height: 16px;
+}
+
+.bodyContainer {
+ display: flex;
+ justify-content: space-between;
+ font-size: 14px;
+ line-height: 1.5;
+ font-weight: 300;
+ margin-bottom: 8px;
+}
+
+.header {
+ position: relative;
+}
+
+.commentType {
+ position: absolute;
+ right: 0px;
+}
+
+.created {
+ padding: 5px;
+ color: #262626;
+ font-size: 14px;
+ line-height: 1px;
+ font-weight: 300;
+}
+
+.body {
+ margin-top: 0px;
+ flex: 1;
+ color: black;
+ max-width: 500px;
+ word-wrap: break-word;
+ font-weight: 300;
+ font-size: 16px;
+ max-width: 360px;
+}
+
+.sideActions {
+}
+
+.editedMarker {
+ font-style: italic;
+ color: #666;
+ font-size: 12px;
+ line-height: 1px;
+ font-weight: 300;
+}
+
+.actions {
+ display: flex;
+ justify-content: flex-end;
+}
+
+.bulkSelectInput {
+ cursor: pointer;
+}
+
+.external {
+ font-size: .7em;
+ text-decoration: none;
+ color: #063b9a;
+ cursor: pointer;
+ font-weight: normal;
+ margin-left: 10px;
+ white-space: nowrap;
+
+ &:hover {
+ text-decoration: underline;
+ opacity: .9;
+ }
+
+ > i {
+ font-size: 12px;
+ top: 2px;
+ position: relative;
+ }
+}
+
+.hasLinks {
+ color: #f00;
+ text-align: right;
+ display: flex;
+ align-items: center;
+
+ > i {
+ margin-right: 5px;
+ }
+}
diff --git a/client/coral-admin/src/routes/Moderation/components/UserDetailComment.js b/client/coral-admin/src/routes/Moderation/components/UserDetailComment.js
new file mode 100644
index 000000000..1ec181214
--- /dev/null
+++ b/client/coral-admin/src/routes/Moderation/components/UserDetailComment.js
@@ -0,0 +1,151 @@
+import React, {PropTypes} from 'react';
+import {Link} from 'react-router';
+
+import {Icon} from 'coral-ui';
+import FlagBox from './FlagBox';
+import styles from './UserDetailComment.css';
+import CommentType from './CommentType';
+import {getActionSummary} from 'coral-framework/utils';
+import ActionButton from 'coral-admin/src/components/ActionButton';
+import CommentBodyHighlighter from 'coral-admin/src/components/CommentBodyHighlighter';
+import IfHasLink from 'coral-admin/src/components/IfHasLink';
+import cn from 'classnames';
+import {getCommentType} from 'coral-admin/src/utils/comment';
+import CommentAnimatedEdit from './CommentAnimatedEdit';
+
+import t, {timeago} from 'coral-framework/services/i18n';
+
+class UserDetailComment extends React.Component {
+
+ render() {
+ const {
+ actions = [],
+ comment,
+ viewUserDetail,
+ suspectWords,
+ bannedWords,
+ selected,
+ toggleSelect,
+ className,
+ user,
+ ...props
+ } = this.props;
+
+ const flagActionSummaries = getActionSummary('FlagActionSummary', comment);
+ const flagActions = comment.actions && comment.actions.filter((a) => a.__typename === 'FlagAction');
+ const commentType = getCommentType(comment);
+
+ return (
+
+
+
+ toggleSelect(e.target.value, e.target.checked)} />
+
+ {timeago(comment.created_at)}
+
+ {
+ (comment.editing && comment.editing.edited)
+ ? ({t('comment.edited')})
+ : null
+ }
+
+
+
+ Story: {comment.asset.title}
+ {{t('modqueue.moderate')}}
+
+
+
+
+
+ {' '}
+
+ {t('comment.view_context')}
+
+
+
+
+
+ Contains Link
+
+
+
+ {actions.map((action, i) => {
+ const active =
+ (action === 'REJECT' && comment.status === 'REJECTED') ||
+ (action === 'APPROVE' && comment.status === 'ACCEPTED');
+ return (
+
+ (comment.status === 'ACCEPTED'
+ ? null
+ : props.acceptComment({commentId: comment.id}))}
+ rejectComment={() =>
+ (comment.status === 'REJECTED'
+ ? null
+ : props.rejectComment({commentId: comment.id}))}
+ />
+ );
+ })}
+
+
+
+
+
+ {flagActions && flagActions.length
+ ?
+ : null}
+
+ );
+ }
+}
+
+UserDetailComment.propTypes = {
+ user: PropTypes.object.isRequired,
+ viewUserDetail: PropTypes.func.isRequired,
+ acceptComment: PropTypes.func.isRequired,
+ rejectComment: PropTypes.func.isRequired,
+ className: PropTypes.string,
+ suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
+ bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
+ toggleSelect: PropTypes.func,
+ comment: PropTypes.shape({
+ body: PropTypes.string.isRequired,
+ action_summaries: PropTypes.array,
+ actions: PropTypes.array,
+ created_at: PropTypes.string.isRequired,
+ asset: PropTypes.shape({
+ title: PropTypes.string,
+ url: PropTypes.string,
+ id: PropTypes.string
+ })
+ })
+};
+
+export default UserDetailComment;
diff --git a/client/coral-admin/src/routes/Moderation/components/styles.css b/client/coral-admin/src/routes/Moderation/components/styles.css
index 1228c61a4..67b7a6d34 100644
--- a/client/coral-admin/src/routes/Moderation/components/styles.css
+++ b/client/coral-admin/src/routes/Moderation/components/styles.css
@@ -213,11 +213,12 @@ span {
.author {
font-weight: 300;
- min-width: 230px;
+ width: 100%;
display: flex;
align-items: center;
color: #262626;
font-size: 16px;
+ position: relative;
}
}
@@ -457,18 +458,6 @@ span {
}
}
-.minimal {
- margin: 0;
-}
-
-.minimalSelection {
- background-color: #ecf4ff;
-}
-
-.bulkSelectInput {
- cursor: pointer;
-}
-
.emptyCardContainer {
margin-top: 16px;
}
@@ -528,3 +517,8 @@ span {
position: relative;
top: .3em;
}
+
+.commentType {
+ position: absolute;
+ right: 0px;
+}
diff --git a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
index 6b476b861..16f0916d3 100644
--- a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
+++ b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js
@@ -5,24 +5,25 @@ import {bindActionCreators} from 'redux';
import UserDetail from '../components/UserDetail';
import withQuery from 'coral-framework/hocs/withQuery';
import {getDefinitionName, getSlotFragmentSpreads} from 'coral-framework/utils';
+import {viewUserDetail, hideUserDetail} from 'actions/moderation';
import {
changeUserDetailStatuses,
clearUserDetailSelections,
- toggleSelectCommentInUserDetail
+ toggleSelectCommentInUserDetail,
} from 'coral-admin/src/actions/moderation';
import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
-import Comment from './Comment';
+import UserDetailComment from './UserDetailComment';
const commentConnectionFragment = gql`
fragment CoralAdmin_Moderation_CommentConnection on CommentConnection {
nodes {
- ...${getDefinitionName(Comment.fragments.comment)}
+ ...${getDefinitionName(UserDetailComment.fragments.comment)}
}
hasNextPage
startCursor
endCursor
}
- ${Comment.fragments.comment}
+ ${UserDetailComment.fragments.comment}
`;
const slots = [
@@ -32,12 +33,11 @@ const slots = [
class UserDetailContainer extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
- hideUserDetail: PropTypes.func.isRequired
}
// status can be 'ACCEPTED' or 'REJECTED'
bulkSetCommentStatus = (status) => {
- const changes = this.props.moderation.userDetailSelectedIds.map((commentId) => {
+ const changes = this.props.selectedIds.map((commentId) => {
return this.props.setCommentStatus({commentId, status});
});
@@ -55,6 +55,14 @@ class UserDetailContainer extends React.Component {
this.bulkSetCommentStatus('ACCEPTED');
}
+ acceptComment = ({commentId}) => {
+ return this.props.setCommentStatus({commentId, status: 'ACCEPTED'});
+ }
+
+ rejectComment = ({commentId}) => {
+ return this.props.setCommentStatus({commentId, status: 'REJECTED'});
+ }
+
render () {
if (!('user' in this.props.root)) {
return null;
@@ -65,6 +73,8 @@ class UserDetailContainer extends React.Component {
bulkAccept={this.bulkAccept}
changeStatus={this.props.changeUserDetailStatuses}
toggleSelect={this.props.toggleSelectCommentInUserDetail}
+ acceptComment={this.acceptComment}
+ rejectComment={this.rejectComment}
{...this.props} />;
}
}
@@ -93,7 +103,7 @@ export const withUserDetailQuery = withQuery(gql`
}
${commentConnectionFragment}
`, {
- options: ({id, moderation: {userDetailStatuses: statuses}}) => {
+ options: ({id, statuses}) => {
return {
variables: {author_id: id, statuses}
};
@@ -101,14 +111,20 @@ export const withUserDetailQuery = withQuery(gql`
});
const mapStateToProps = (state) => ({
- moderation: state.moderation.toJS()
+ selectedIds: state.moderation.toJS().userDetailSelectedIds,
+ statuses: state.moderation.toJS().userDetailStatuses,
+ activeTab: state.moderation.toJS().userDetailActiveTab,
+ bannedWords: state.settings.toJS().wordlist.banned,
+ suspectWords: state.settings.toJS().wordlist.suspect,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
changeUserDetailStatuses,
clearUserDetailSelections,
- toggleSelectCommentInUserDetail
+ toggleSelectCommentInUserDetail,
+ viewUserDetail,
+ hideUserDetail,
}, dispatch)
});
diff --git a/client/coral-admin/src/routes/Moderation/containers/UserDetailComment.js b/client/coral-admin/src/routes/Moderation/containers/UserDetailComment.js
new file mode 100644
index 000000000..a70a9394d
--- /dev/null
+++ b/client/coral-admin/src/routes/Moderation/containers/UserDetailComment.js
@@ -0,0 +1,39 @@
+import {gql} from 'react-apollo';
+import UserDetailComment from '../components/UserDetailComment';
+import withFragments from 'coral-framework/hocs/withFragments';
+
+export default withFragments({
+ comment: gql`
+ fragment CoralAdmin_UserDetail_comment on Comment {
+ id
+ body
+ created_at
+ status
+ asset {
+ id
+ title
+ url
+ }
+ action_summaries {
+ count
+ ... on FlagActionSummary {
+ reason
+ }
+ }
+ actions {
+ ... on FlagAction {
+ id
+ reason
+ message
+ user {
+ id
+ username
+ }
+ }
+ }
+ editing {
+ edited
+ }
+ }
+ `
+})(UserDetailComment);
diff --git a/client/coral-admin/src/utils/comment.js b/client/coral-admin/src/utils/comment.js
new file mode 100644
index 000000000..26530f6c9
--- /dev/null
+++ b/client/coral-admin/src/utils/comment.js
@@ -0,0 +1,9 @@
+export function getCommentType(comment) {
+ let commentType = '';
+ if (comment.status === 'PREMOD') {
+ commentType = 'premod';
+ } else if (comment.actions && comment.actions.some((a) => a.__typename === 'FlagAction')) {
+ commentType = 'flagged';
+ }
+ return commentType;
+}